diff --git a/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticFeatureReader.java b/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticFeatureReader.java index d4aefd5..046ebb7 100644 --- a/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticFeatureReader.java +++ b/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticFeatureReader.java @@ -104,6 +104,7 @@ private String nextHit() { final ElasticHit hit = searchHitIterator.next(); final SimpleFeatureType type = getFeatureType(); final Map source = hit.getSource(); + final Map> highlights = hit.getHighlight(); final Float score; final Float relativeScore; @@ -120,6 +121,12 @@ private String nextHit() { final String sourceName = (String) descriptor.getUserData().get(FULL_NAME); List values = hit.field(sourceName); + + if (values == null && highlights != null && highlights.containsKey(sourceName)) { + // read field from source + values = Collections.unmodifiableList(highlights.get(sourceName)); + } + if (values == null && source != null) { // read field from source values = parserUtil.readField(source, sourceName); diff --git a/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticFeatureSource.java b/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticFeatureSource.java index 0135940..63b58c5 100644 --- a/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticFeatureSource.java +++ b/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticFeatureSource.java @@ -6,6 +6,7 @@ import java.io.IOException; +import java.util.Collections; import java.util.List; import java.util.Map; import java.util.logging.Level; @@ -218,6 +219,18 @@ private ElasticRequest prepareSearchRequest(Query query, boolean scroll) throws searchRequest.setSize(0); } + if (filterToElastic.gethighlights() != null) { + searchRequest.setHighlights(filterToElastic.gethighlights()); + + List source = searchRequest.getSourceIncludes(); + for (String key : filterToElastic.gethighlights().keySet()) { + if (source.contains(key)) + { + source.remove(key); + } + } + } + return searchRequest; } diff --git a/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticHit.java b/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticHit.java index f57876d..2812591 100644 --- a/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticHit.java +++ b/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticHit.java @@ -30,6 +30,8 @@ public class ElasticHit { private Map> fields; + private Map> highlight; + public String getIndex() { return index; } @@ -78,4 +80,11 @@ public List field(String name) { return this.fields != null ? this.fields.get(name) : null; } + public Map> getHighlight() { + return highlight; + } + + public void setHighlight(Map> highlight) { + this.highlight = highlight; + } } diff --git a/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticRequest.java b/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticRequest.java index f5fb193..44e058d 100644 --- a/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticRequest.java +++ b/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/ElasticRequest.java @@ -15,6 +15,8 @@ public class ElasticRequest { private Map>> aggregations; + private Map highlights; + private Integer size; private Integer from; @@ -49,6 +51,14 @@ public void setAggregations(Map>> aggregati this.aggregations = aggregations; } + public Map getHighlights() { + return highlights; + } + + public void setHighlights(Map highlights) { + this.highlights = highlights; + } + public Integer getSize() { return size; } diff --git a/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/FilterToElastic.java b/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/FilterToElastic.java index 8750367..1f2e99c 100644 --- a/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/FilterToElastic.java +++ b/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/FilterToElastic.java @@ -117,9 +117,9 @@ /** * Encodes an OGC {@link Filter} and creates a filter for an Elasticsearch query. - * Optionally applies SQL View parameters from {@link Query} defining Elasticsearch + * Optionally applies SQL View parameters from {@link Query} defining Elasticsearch * query directly. - * + * * Based on org.geotools.data.jdbc.FilterToSQL in the GeoTools library/jdbc module. */ public class FilterToElastic implements FilterVisitor, ExpressionVisitor { @@ -156,6 +156,8 @@ public class FilterToElastic implements FilterVisitor, ExpressionVisitor { Map>> aggregations; + private Map highlights; + private FilterToElasticHelper helper; private String key; @@ -225,9 +227,9 @@ public void encode(Query query) throws FilterToElasticException { /** * Sets the featuretype the encoder is encoding for. *

- * This is used for context for attribute expressions. + * This is used for context for attribute expressions. *

- * + * * @param featureType */ public void setFeatureType(SimpleFeatureType featureType) { @@ -245,12 +247,12 @@ protected Capabilities createCapabilities() { /** * Describes the capabilities of this encoder. - * + * *

* Performs lazy creation of capabilities. *

- * - * If you're extending this class, override {@link #createCapabilities()} to declare which capabilities you + * + * If you're extending this class, override {@link #createCapabilities()} to declare which capabilities you * support. Don't use this method. * * @return The capabilities supported by this encoder. @@ -268,7 +270,7 @@ public synchronized final Capabilities getCapabilities() { /** * Writes the FilterBuilder for the ExcludeFilter. - * + * * @param filter the filter to be visited */ public Object visit(ExcludeFilter filter, Object extraData) { @@ -278,9 +280,9 @@ public Object visit(ExcludeFilter filter, Object extraData) { /** * Writes the FilterBuilder for the IncludeFilter. - * + * * @param filter the filter to be visited - * + * */ public Object visit(IncludeFilter filter, Object extraData) { queryBuilder = MATCH_ALL; @@ -399,10 +401,10 @@ public Object visit(PropertyIsLike filter, Object extraData) { /** * Write the FilterBuilder for an And filter - * + * * @param filter the filter to visit * @param extraData extra data (unused by this method) - * + * */ public Object visit(And filter, Object extraData) { return visit((BinaryLogicOperator)filter, "AND"); @@ -410,10 +412,10 @@ public Object visit(And filter, Object extraData) { /** * Write the FilterBuilder for a Not filter - * + * * @param filter the filter to visit * @param extraData extra data (unused by this method) - * + * */ public Object visit(Not filter, Object extraData) { if(filter.getFilter() instanceof PropertyIsNull) { @@ -433,10 +435,10 @@ public Object visit(Not filter, Object extraData) { /** * Write the FilterBuilder for an Or filter - * + * * @param filter the filter to visit * @param extraData extra data (unused by this method) - * + * */ public Object visit(Or filter, Object extraData) { return visit((BinaryLogicOperator)filter, "OR"); @@ -469,10 +471,10 @@ protected Object visit(BinaryLogicOperator filter, Object extraData) { /** * Write the FilterBuilder for this kind of filter - * + * * @param filter the filter to visit * @param extraData extra data (unused by this method) - * + * */ public Object visit(PropertyIsEqualTo filter, Object extraData) { visitBinaryComparisonOperator((BinaryComparisonOperator)filter, "="); @@ -481,10 +483,10 @@ public Object visit(PropertyIsEqualTo filter, Object extraData) { /** * Write the FilterBuilder for this kind of filter - * + * * @param filter the filter to visit * @param extraData extra data (unused by this method) - * + * */ public Object visit(PropertyIsGreaterThanOrEqualTo filter, Object extraData) { visitBinaryComparisonOperator((BinaryComparisonOperator)filter, ">="); @@ -493,10 +495,10 @@ public Object visit(PropertyIsGreaterThanOrEqualTo filter, Object extraData) { /** * Write the FilterBuilder for this kind of filter - * + * * @param filter the filter to visit * @param extraData extra data (unused by this method) - * + * */ public Object visit(PropertyIsGreaterThan filter, Object extraData) { visitBinaryComparisonOperator((BinaryComparisonOperator)filter, ">"); @@ -505,10 +507,10 @@ public Object visit(PropertyIsGreaterThan filter, Object extraData) { /** * Write the FilterBuilder for this kind of filter - * + * * @param filter the filter to visit * @param extraData extra data (unused by this method) - * + * */ public Object visit(PropertyIsLessThan filter, Object extraData) { visitBinaryComparisonOperator((BinaryComparisonOperator)filter, "<"); @@ -517,10 +519,10 @@ public Object visit(PropertyIsLessThan filter, Object extraData) { /** * Write the FilterBuilder for this kind of filter - * + * * @param filter the filter to visit * @param extraData extra data (unused by this method) - * + * */ public Object visit(PropertyIsLessThanOrEqualTo filter, Object extraData) { visitBinaryComparisonOperator((BinaryComparisonOperator)filter, "<="); @@ -529,10 +531,10 @@ public Object visit(PropertyIsLessThanOrEqualTo filter, Object extraData) { /** * Write the FilterBuilder for this kind of filter - * + * * @param filter the filter to visit * @param extraData extra data (unused by this method) - * + * */ public Object visit(PropertyIsNotEqualTo filter, Object extraData) { visitBinaryComparisonOperator((BinaryComparisonOperator)filter, "!="); @@ -585,10 +587,10 @@ protected void visitBinaryComparisonOperator(BinaryComparisonOperator filter, Ob //case sensitivity if ( !filter.isMatchingCase() ) { //we only do for = and != - if ( filter instanceof PropertyIsEqualTo || + if ( filter instanceof PropertyIsEqualTo || filter instanceof PropertyIsNotEqualTo ) { //and only for strings - if ( String.class.equals( leftContext ) + if ( String.class.equals( leftContext ) || String.class.equals( rightContext ) ) { //matchCase = false; LOGGER.fine("Case insensitive filter not supported"); @@ -601,11 +603,11 @@ protected void visitBinaryComparisonOperator(BinaryComparisonOperator filter, Ob if (left instanceof PropertyName) { left.accept(this, null); key = (String) field; - right.accept(this, rightContext); + right.accept(this, rightContext); } else { right.accept(this, null); key = (String) field; - left.accept(this, leftContext); + left.accept(this, leftContext); } if (nested) { @@ -757,7 +759,7 @@ protected Object visitBinaryTemporalOperator(BinaryTemporalOperator filter, if (e1 instanceof PropertyName && e2 instanceof Literal) { //call the "regular" method - return visitBinaryTemporalOperator(filter, (PropertyName)e1, (Literal)e2, + return visitBinaryTemporalOperator(filter, (PropertyName)e1, (Literal)e2, filter.getExpression1() instanceof Literal, extraData); } else { @@ -769,12 +771,12 @@ protected Object visitBinaryTemporalOperator(BinaryTemporalOperator filter, /** * Handles the common case of a PropertyName,Literal geometry binary temporal operator. *

- * Subclasses should override if they support more temporal operators than what is handled in - * this base class. + * Subclasses should override if they support more temporal operators than what is handled in + * this base class. *

*/ - protected Object visitBinaryTemporalOperator(BinaryTemporalOperator filter, - PropertyName property, Literal temporal, boolean swapped, Object extraData) { + protected Object visitBinaryTemporalOperator(BinaryTemporalOperator filter, + PropertyName property, Literal temporal, boolean swapped, Object extraData) { AttributeDescriptor attType = (AttributeDescriptor)property.evaluate(featureType); @@ -807,11 +809,11 @@ protected Object visitBinaryTemporalOperator(BinaryTemporalOperator filter, } //ensure the time period is the correct argument - if ((filter instanceof Begins || filter instanceof Ends || filter instanceof During) && + if ((filter instanceof Begins || filter instanceof Ends || filter instanceof During) && swapped) { throw new IllegalArgumentException("Time period must be second argument of Filter"); } - if ((filter instanceof BegunBy || filter instanceof EndedBy || filter instanceof TContains) && + if ((filter instanceof BegunBy || filter instanceof EndedBy || filter instanceof TContains) && !swapped) { throw new IllegalArgumentException("Time period must be first argument of Filter"); } @@ -835,7 +837,7 @@ protected Object visitBinaryTemporalOperator(BinaryTemporalOperator filter, temporal.accept(this, typeContext); } } - else if (filter instanceof Begins || filter instanceof Ends || + else if (filter instanceof Begins || filter instanceof Ends || filter instanceof BegunBy || filter instanceof EndedBy ) { property.accept(this, extraData); key = (String) field; @@ -881,7 +883,7 @@ else if (filter instanceof TEquals) { } } } - else if (filter instanceof Begins || filter instanceof Ends || + else if (filter instanceof Begins || filter instanceof Ends || filter instanceof BegunBy || filter instanceof EndedBy ) { queryBuilder = ImmutableMap.of("term", ImmutableMap.of(key, field)); @@ -911,11 +913,11 @@ void visitEnd(Period p, Object extraData) { /** * Handles the general case of two expressions in a binary temporal filter. *

- * Subclasses should override if they support more temporal operators than what is handled in - * this base class. + * Subclasses should override if they support more temporal operators than what is handled in + * this base class. *

*/ - protected Object visitBinaryTemporalOperator(BinaryTemporalOperator filter, Expression e1, + protected Object visitBinaryTemporalOperator(BinaryTemporalOperator filter, Expression e1, Expression e2, Object extraData) { throw new UnsupportedOperationException("Join version of binary temporal operator not supported"); } @@ -937,7 +939,7 @@ public Object visitNullFilter(Object extraData) { /** * Writes the FilterBuilder for the attribute Expression. - * + * * @param expression the attribute. * */ @@ -955,7 +957,7 @@ public Object visit(PropertyName expression, Object extraData) { //first evaluate expression against feature type get the attribute, AttributeDescriptor attType = (AttributeDescriptor) expression.evaluate(featureType); - String encodedField; + String encodedField; if ( attType != null ) { encodedField = attType.getLocalName(); if(target != null && target.isAssignableFrom(attType.getType().getBinding())) { @@ -1071,7 +1073,7 @@ protected void writeLiteral(Object literal) { if (Date.class.isAssignableFrom(literal.getClass())) { field = dateFormatter.print(((Date) literal).getTime()); } - } + } protected void visitLiteralTimePeriod(Period expression) { throw new UnsupportedOperationException("Time periods not supported, subclasses must implement this " + @@ -1164,7 +1166,7 @@ protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, swapped, extraData); } - protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, + protected Object visitBinarySpatialOperator(BinarySpatialOperator filter, Expression e1, Expression e2, Object extraData) { return helper.visitBinarySpatialOperator(filter, e1, e2, extraData); } @@ -1190,7 +1192,7 @@ protected void updateDateFormatter(AttributeDescriptor attType) { * helper to do a safe convesion of expression to a number */ Number safeConvertToNumber(Expression expression, Class target) { - return (Number) Converters.convert(expression.evaluate(null), target, + return (Number) Converters.convert(expression.evaluate(null), target, new Hints(ConverterFactory.SAFE_CONVERSION, true)); } @@ -1238,12 +1240,26 @@ protected void addViewParams(Query query) { throw new FilterToElasticException("Unable to parse aggregation", e); } } + } else if (entry.getKey().equalsIgnoreCase("highlight")) { + final ObjectMapper mapper = new ObjectMapper(); + final TypeReference> type; + type = new TypeReference>() {}; + final String value = entry.getValue(); + try { + this.highlights = mapper.readValue(value, type); + } catch (Exception e) { + try { + this.highlights = mapper.readValue(ElasticParserUtil.urlDecode(value), type); + } catch (Exception e2) { + throw new FilterToElasticException("Unable to parse aggregation", e); + } + } } } } } - public static String convertToQueryString(char escape, char multi, char single, + public static String convertToQueryString(char escape, char multi, char single, boolean matchCase, String pattern ) { StringBuffer result = new StringBuffer(pattern.length()+5); @@ -1260,7 +1276,7 @@ public static String convertToQueryString(char escape, char multi, char single, result.append('?'); } else if (chr == multi) { result.append('*'); - } else { + } else { result.append(chr); } } @@ -1268,7 +1284,7 @@ public static String convertToQueryString(char escape, char multi, char single, return result.toString(); } - public static String convertToRegex(char escape, char multi, char single, + public static String convertToRegex(char escape, char multi, char single, boolean matchCase, String pattern) { StringBuffer result = new StringBuffer(pattern.length()+5); @@ -1285,7 +1301,7 @@ public static String convertToRegex(char escape, char multi, char single, result.append('.'); } else if (chr == multi) { result.append(".*"); - } else { + } else { result.append(chr); } } @@ -1314,7 +1330,7 @@ public Map getQueryBuilder() { } else if (this.queryBuilder.equals(MATCH_ALL)) { queryBuilder = nativeQueryBuilder; } else { - queryBuilder = ImmutableMap.of("bool", + queryBuilder = ImmutableMap.of("bool", ImmutableMap.of("must", ImmutableList.of(nativeQueryBuilder, this.queryBuilder))); } return queryBuilder; @@ -1324,4 +1340,7 @@ public Map>> getAggregations() { return aggregations; } + public Map gethighlights() { + return highlights; + } } diff --git a/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/RestElasticClient.java b/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/RestElasticClient.java index b761e69..010a20f 100644 --- a/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/RestElasticClient.java +++ b/gt-elasticsearch/src/main/java/mil/nga/giat/data/elasticsearch/RestElasticClient.java @@ -179,6 +179,10 @@ public ElasticResponse search(String searchIndices, String type, ElasticRequest requestBody.put("aggregations", request.getAggregations()); } + if (request.getHighlights() != null) { + requestBody.put("highlight", request.getHighlights()); + } + if (LOGGER.isLoggable(Level.FINE)) { LOGGER.fine("Elasticsearch request:\n" + mapper.writerWithDefaultPrettyPrinter().writeValueAsString(requestBody)); }