blob: f11f0d0d447e999e0dd8d7a49678329ef3e0d2f9 [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.solr.handler.component;
import java.io.IOException;
import java.lang.invoke.MethodHandles;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.Map;
import org.apache.solr.common.params.FacetParams;
import org.apache.solr.common.params.ModifiableSolrParams;
import org.apache.solr.common.params.SolrParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;
import org.apache.solr.search.DocSet;
import org.apache.solr.search.facet.FacetHeatmap;
import org.apache.solr.search.facet.FacetMerger;
import org.apache.solr.search.facet.FacetRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/** A 2D spatial faceting summary of a rectangular region. Used by {@link org.apache.solr.handler.component.FacetComponent}
* and {@link org.apache.solr.request.SimpleFacets}.
* @see FacetHeatmap
*/
public class SpatialHeatmapFacets {
private static final Logger log = LoggerFactory.getLogger(MethodHandles.lookup().lookupClass());
//underneath facet_counts we put this here:
public static final String RESPONSE_KEY = "facet_heatmaps";
/** Called by {@link org.apache.solr.request.SimpleFacets} to compute heatmap facets. */
@SuppressWarnings({"unchecked", "rawtypes"})
public static NamedList<Object> getHeatmapForField(String fieldKey, String fieldName, ResponseBuilder rb, SolrParams params, DocSet docSet) throws IOException {
final FacetRequest facetRequest = createHeatmapRequest(fieldKey, fieldName, rb, params);
return (NamedList) facetRequest.process(rb.req, docSet);
}
private static FacetRequest createHeatmapRequest(String fieldKey, String fieldName, ResponseBuilder rb, SolrParams params) {
Map<String, Object> jsonFacet = new HashMap<>();
jsonFacet.put("type", "heatmap");
jsonFacet.put("field", fieldName);
// jsonFacets has typed values, unlike SolrParams which is all string
jsonFacet.put(FacetHeatmap.GEOM_PARAM, params.getFieldParam(fieldKey, FacetParams.FACET_HEATMAP_GEOM));
jsonFacet.put(FacetHeatmap.LEVEL_PARAM, params.getFieldInt(fieldKey, FacetParams.FACET_HEATMAP_LEVEL));
jsonFacet.put(FacetHeatmap.DIST_ERR_PCT_PARAM, params.getFieldDouble(fieldKey, FacetParams.FACET_HEATMAP_DIST_ERR_PCT));
jsonFacet.put(FacetHeatmap.DIST_ERR_PARAM, params.getFieldDouble(fieldKey, FacetParams.FACET_HEATMAP_DIST_ERR));
jsonFacet.put(FacetHeatmap.MAX_CELLS_PARAM, params.getFieldInt(fieldKey, FacetParams.FACET_HEATMAP_MAX_CELLS));
jsonFacet.put(FacetHeatmap.FORMAT_PARAM, params.getFieldParam(fieldKey, FacetParams.FACET_HEATMAP_FORMAT));
return FacetRequest.parseOneFacetReq(rb.req, jsonFacet);
}
//
// Distributed Support
//
/** Parses request to "HeatmapFacet" instances. */
public static LinkedHashMap<String,HeatmapFacet> distribParse(SolrParams params, ResponseBuilder rb) {
final LinkedHashMap<String, HeatmapFacet> heatmapFacets = new LinkedHashMap<>();
final String[] heatmapFields = params.getParams(FacetParams.FACET_HEATMAP);
if (heatmapFields != null) {
for (String heatmapField : heatmapFields) {
HeatmapFacet facet = new HeatmapFacet(rb, heatmapField);
heatmapFacets.put(facet.getKey(), facet);
}
}
return heatmapFacets;
}
/** Called by FacetComponent's impl of
* {@link org.apache.solr.handler.component.SearchComponent#modifyRequest(ResponseBuilder, SearchComponent, ShardRequest)}. */
public static void distribModifyRequest(ShardRequest sreq, LinkedHashMap<String, HeatmapFacet> heatmapFacets) {
// Set the format to PNG because it's compressed and it's the only format we have code to read at the moment.
// We re-write the facet.heatmap list with PNG format in local-params where it has highest precedence.
//Remove existing heatmap field param vals; we will rewrite
sreq.params.remove(FacetParams.FACET_HEATMAP);
for (HeatmapFacet facet : heatmapFacets.values()) {
//add heatmap field param
ModifiableSolrParams newLocalParams = new ModifiableSolrParams();
if (facet.localParams != null) {
newLocalParams.add(facet.localParams);
}
// Set format to PNG; it's the only one we parse
newLocalParams.set(FacetParams.FACET_HEATMAP_FORMAT, FacetHeatmap.FORMAT_PNG);
sreq.params.add(FacetParams.FACET_HEATMAP,
newLocalParams.toLocalParamsString() + facet.facetOn);
}
}
/** Called by FacetComponent.countFacets which is in turn called by FC's impl of
* {@link org.apache.solr.handler.component.SearchComponent#handleResponses(ResponseBuilder, ShardRequest)}. */
@SuppressWarnings("unchecked")
public static void distribHandleResponse(LinkedHashMap<String, HeatmapFacet> heatmapFacets, @SuppressWarnings({"rawtypes"})NamedList srsp_facet_counts) {
NamedList<NamedList<Object>> facet_heatmaps = (NamedList<NamedList<Object>>) srsp_facet_counts.get(RESPONSE_KEY);
if (facet_heatmaps == null) {
return;
}
// (should the caller handle the above logic? Arguably yes.)
for (Map.Entry<String, NamedList<Object>> entry : facet_heatmaps) {
String fieldKey = entry.getKey();
NamedList<Object> shardNamedList = entry.getValue();
final HeatmapFacet facet = heatmapFacets.get(fieldKey);
if (facet == null) {
log.error("received heatmap for field/key {} that we weren't expecting", fieldKey);
continue;
}
facet.jsonFacetMerger.merge(shardNamedList, null);//merge context not needed (null)
}
}
/** Called by FacetComponent's impl of
* {@link org.apache.solr.handler.component.SearchComponent#finishStage(ResponseBuilder)}. */
@SuppressWarnings({"unchecked", "rawtypes"})
public static NamedList distribFinish(LinkedHashMap<String, HeatmapFacet> heatmapInfos, ResponseBuilder rb) {
NamedList<NamedList<Object>> result = new SimpleOrderedMap<>();
for (Map.Entry<String, HeatmapFacet> entry : heatmapInfos.entrySet()) {
final HeatmapFacet facet = entry.getValue();
result.add(entry.getKey(), (NamedList<Object>) facet.jsonFacetMerger.getMergedResult());
}
return result;
}
/** Goes in {@link org.apache.solr.handler.component.FacetComponent.FacetInfo#heatmapFacets}, created by
* {@link #distribParse(org.apache.solr.common.params.SolrParams, ResponseBuilder)}. */
public static class HeatmapFacet extends FacetComponent.FacetBase {
//note: 'public' following-suit with FacetBase & existing subclasses... though should this really be?
public FacetMerger jsonFacetMerger;
public HeatmapFacet(ResponseBuilder rb, String facetStr) {
super(rb, FacetParams.FACET_HEATMAP, facetStr);
//note: logic in super (FacetBase) is partially redundant with SimpleFacet.parseParams :-(
final SolrParams params = SolrParams.wrapDefaults(localParams, rb.req.getParams());
final FacetRequest heatmapRequest = createHeatmapRequest(getKey(), facetOn, rb, params);
jsonFacetMerger = heatmapRequest.createFacetMerger(null);
}
}
// Note: originally there was a lot more code here but it migrated to the JSON Facet API as "FacetHeatmap"
}