blob: 15139223997a361e2f6ded9e6581633649b497fd [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.apache.jackrabbit.oak.plugins.index.elastic.query;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.core.JsonFactory;
import com.fasterxml.jackson.core.JsonFactoryBuilder;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.apache.jackrabbit.oak.plugins.index.search.FieldNames;
import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexPlanner;
import org.apache.jackrabbit.oak.plugins.index.search.spi.query.FulltextIndexPlanner.PlanResult;
import org.apache.jackrabbit.oak.spi.query.Filter;
import org.elasticsearch.client.Response;
import org.elasticsearch.search.SearchHit;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.IOException;
import java.util.Map;
/**
* Class to process Elastic response objects.
*/
public class ElasticResponseHandler {
private static final Logger LOG = LoggerFactory.getLogger(ElasticResponseHandler.class);
private static final ObjectMapper JSON_MAPPER;
static {
// disable String.intern
// https://github.com/elastic/elasticsearch/issues/39890
// https://github.com/FasterXML/jackson-core/issues/332
JsonFactoryBuilder factoryBuilder = new JsonFactoryBuilder();
factoryBuilder.disable(JsonFactory.Feature.INTERN_FIELD_NAMES);
JSON_MAPPER = new ObjectMapper(factoryBuilder.build());
JSON_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
}
private final PlanResult planResult;
private final Filter filter;
ElasticResponseHandler(@NotNull FulltextIndexPlanner.PlanResult planResult, @NotNull Filter filter) {
this.planResult = planResult;
this.filter = filter;
}
public String getPath(SearchResponseHit hit) {
return transformPath((String) hit.source.get(FieldNames.PATH));
}
public String getPath(SearchHit hit) {
Map<String, Object> sourceMap = hit.getSourceAsMap();
return transformPath((String) sourceMap.get(FieldNames.PATH));
}
private String transformPath(String path) {
if ("".equals(path)) {
path = "/";
}
if (planResult.isPathTransformed()) {
String originalPath = path;
path = planResult.transformPath(path);
if (path == null) {
LOG.trace("Ignoring path {} : Transformation returned null", originalPath);
return null;
}
}
return path;
}
public boolean isAccessible(String path) {
return filter.isAccessible(path);
}
public SearchResponse parse(Response response) throws IOException {
return JSON_MAPPER.readValue(response.getEntity().getContent(), SearchResponse.class);
}
// POJO for Elastic json deserialization
public static class SearchResponse {
public final long took;
public final boolean timedOut;
public final SearchResponseHits hits;
public final Map<String, AggregationBuckets> aggregations;
@JsonCreator
public SearchResponse(@JsonProperty("took") long took, @JsonProperty("timed_out") boolean timedOut,
@JsonProperty("hits") SearchResponseHits hits,
@JsonProperty("aggregations") Map<String, AggregationBuckets> aggregations) {
this.took = took;
this.timedOut = timedOut;
this.hits = hits;
this.aggregations = aggregations;
}
}
public static class SearchResponseHits {
public final SearchResponseHitsTotal total;
public final SearchResponseHit[] hits;
@JsonCreator
public SearchResponseHits(@JsonProperty("total") SearchResponseHitsTotal total,
@JsonProperty("hits") SearchResponseHit[] hits) {
this.total = total;
this.hits = hits;
}
}
public static class SearchResponseHit {
public final Map<String, Object> source;
public final Object[] sort;
public final double score;
@JsonCreator
public SearchResponseHit(@JsonProperty("_source") Map<String, Object> source,
@JsonProperty("sort") Object[] sort, @JsonProperty("_score") double score) {
this.source = source;
this.sort = sort;
this.score = score;
}
}
public static class SearchResponseHitsTotal {
public final long value;
@JsonCreator
public SearchResponseHitsTotal(@JsonProperty("value") long value) {
this.value = value;
}
}
public static class AggregationBuckets {
public final AggregationBucket[] buckets;
@JsonCreator
public AggregationBuckets(@JsonProperty("buckets") AggregationBucket[] buckets) {
this.buckets = buckets;
}
}
public static class AggregationBucket {
public final Object key;
public final int count;
@JsonCreator
public AggregationBucket(@JsonProperty("key") Object key, @JsonProperty("doc_count") int count) {
this.key = key;
this.count = count;
}
}
}