| /* |
| * 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.util.LinkedHashMap; |
| import java.util.Map; |
| |
| import org.apache.solr.common.SolrException; |
| import org.apache.solr.common.params.ShardParams; |
| import org.apache.solr.common.params.StatsParams; |
| import org.apache.solr.common.util.NamedList; |
| import org.apache.solr.common.util.SimpleOrderedMap; |
| import org.apache.solr.search.DocSet; |
| |
| /** |
| * Stats component calculates simple statistics on numeric field values |
| * @since solr 1.4 |
| */ |
| public class StatsComponent extends SearchComponent { |
| |
| public static final String COMPONENT_NAME = "stats"; |
| |
| @Override |
| public void prepare(ResponseBuilder rb) throws IOException { |
| if (rb.req.getParams().getBool(StatsParams.STATS, false)) { |
| rb.setNeedDocSet(true); |
| rb.doStats = true; |
| rb._statsInfo = new StatsInfo(rb); |
| for (StatsField statsField : rb._statsInfo.getStatsFields()) { |
| if (statsField.getSchemaField() != null && statsField.getSchemaField().getType().isPointField() && !statsField.getSchemaField().hasDocValues()) { |
| throw new SolrException(SolrException.ErrorCode.BAD_REQUEST, |
| "Can't calculate stats on a PointField without docValues"); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void process(ResponseBuilder rb) throws IOException { |
| if (!rb.doStats) return; |
| Map<String, StatsValues> statsValues = new LinkedHashMap<>(); |
| |
| for (StatsField statsField : rb._statsInfo.getStatsFields()) { |
| DocSet docs = statsField.computeBaseDocSet(); |
| statsValues.put(statsField.getOutputKey(), statsField.computeLocalStatsValues(docs)); |
| } |
| |
| rb.rsp.add("stats", convertToResponse(statsValues)); |
| } |
| |
| @Override |
| public int distributedProcess(ResponseBuilder rb) throws IOException { |
| return ResponseBuilder.STAGE_DONE; |
| } |
| |
| @Override |
| public void modifyRequest(ResponseBuilder rb, SearchComponent who, ShardRequest sreq) { |
| if (!rb.doStats) return; |
| |
| if ((sreq.purpose & ShardRequest.PURPOSE_GET_TOP_IDS) != 0) { |
| sreq.purpose |= ShardRequest.PURPOSE_GET_STATS; |
| } else { |
| |
| |
| // turn off stats on other requests |
| sreq.params.set(StatsParams.STATS, "false"); |
| // we could optionally remove stats params |
| } |
| } |
| |
| @Override |
| @SuppressWarnings({"unchecked", "rawtypes"}) |
| public void handleResponses(ResponseBuilder rb, ShardRequest sreq) { |
| if (!rb.doStats || (sreq.purpose & ShardRequest.PURPOSE_GET_STATS) == 0) return; |
| |
| Map<String, StatsValues> allStatsValues = rb._statsInfo.getAggregateStatsValues(); |
| |
| for (ShardResponse srsp : sreq.responses) { |
| NamedList stats = null; |
| try { |
| stats = (NamedList<NamedList<NamedList<?>>>) |
| srsp.getSolrResponse().getResponse().get("stats"); |
| } catch (Exception e) { |
| if (ShardParams.getShardsTolerantAsBool(rb.req.getParams())) { |
| continue; // looks like a shard did not return anything |
| } |
| throw new SolrException(SolrException.ErrorCode.SERVER_ERROR, |
| "Unable to read stats info for shard: " + srsp.getShard(), e); |
| } |
| |
| NamedList stats_fields = unwrapStats(stats); |
| if (stats_fields != null) { |
| for (int i = 0; i < stats_fields.size(); i++) { |
| String key = stats_fields.getName(i); |
| StatsValues stv = allStatsValues.get(key); |
| @SuppressWarnings({"rawtypes"}) |
| NamedList shardStv = (NamedList) stats_fields.get(key); |
| stv.accumulate(shardStv); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void finishStage(ResponseBuilder rb) { |
| if (!rb.doStats || rb.stage != ResponseBuilder.STAGE_GET_FIELDS) return; |
| // wait until STAGE_GET_FIELDS |
| // so that "result" is already stored in the response (for aesthetics) |
| |
| Map<String, StatsValues> allStatsValues = rb._statsInfo.getAggregateStatsValues(); |
| rb.rsp.add("stats", convertToResponse(allStatsValues)); |
| |
| rb._statsInfo = null; // free some objects |
| } |
| |
| /** |
| * Helper to pull the "stats_fields" out of the extra "stats" wrapper |
| */ |
| public static NamedList<NamedList<?>> unwrapStats(NamedList<NamedList<NamedList<?>>> stats) { |
| if (null == stats) return null; |
| |
| return stats.get("stats_fields"); |
| } |
| |
| /** |
| * Given a map of {@link StatsValues} using the appropriate response key, |
| * builds up the necessary "stats" data structure for including in the response -- |
| * including the esoteric "stats_fields" wrapper. |
| */ |
| public static NamedList<NamedList<NamedList<?>>> convertToResponse |
| (Map<String, StatsValues> statsValues) { |
| |
| NamedList<NamedList<NamedList<?>>> stats = new SimpleOrderedMap<>(); |
| NamedList<NamedList<?>> stats_fields = new SimpleOrderedMap<>(); |
| stats.add("stats_fields", stats_fields); |
| |
| for (Map.Entry<String, StatsValues> entry : statsValues.entrySet()) { |
| String key = entry.getKey(); |
| @SuppressWarnings({"rawtypes"}) |
| NamedList stv = entry.getValue().getStatsValues(); |
| stats_fields.add(key, stv); |
| } |
| return stats; |
| } |
| |
| ///////////////////////////////////////////// |
| /// SolrInfoBean |
| //////////////////////////////////////////// |
| |
| @Override |
| public String getDescription() { |
| return "Calculate Statistics"; |
| } |
| } |
| |