All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.apache.solr.client.solrj.response.QueryResponse Maven / Gradle / Ivy

/*
 * 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.client.solrj.response;

import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import org.apache.solr.client.solrj.SolrClient;
import org.apache.solr.client.solrj.beans.DocumentObjectBinder;
import org.apache.solr.client.solrj.response.json.NestableJsonFacet;
import org.apache.solr.common.SolrDocumentList;
import org.apache.solr.common.params.CursorMarkParams;
import org.apache.solr.common.util.NamedList;
import org.apache.solr.common.util.SimpleOrderedMap;

/**
 * @since solr 1.3
 */
@SuppressWarnings("unchecked")
public class QueryResponse extends SolrResponseBase {
  // Direct pointers to known types
  private NamedList _header = null;
  private SolrDocumentList _results = null;

  @SuppressWarnings({"rawtypes"})
  private NamedList _sortvalues = null;

  private NamedList _facetInfo = null;
  private NamedList _debugInfo = null;
  private NamedList _highlightingInfo = null;
  private NamedList _spellInfo = null;
  private List> _clusterInfo = null;
  private NamedList _jsonFacetingInfo = null;
  private NamedList> _suggestInfo = null;
  private NamedList _statsInfo = null;
  private NamedList> _termsInfo = null;
  private NamedList _moreLikeThisInfo = null;
  private NamedList _tasksInfo = null;
  private String _cancellationInfo = null;
  private String _taskStatusCheckInfo = null;
  private String _cursorMarkNext = null;

  // Grouping response
  private NamedList _groupedInfo = null;
  private GroupResponse _groupResponse = null;

  private NamedList _expandedInfo = null;
  private Map _expandedResults = null;

  // Facet stuff
  private Map _facetQuery = null;
  private List _facetFields = null;
  private List _limitingFacets = null;
  private List _facetDates = null;

  @SuppressWarnings({"rawtypes"})
  private List _facetRanges = null;

  private NamedList> _facetPivot = null;
  private List _intervalFacets = null;

  // Highlight Info
  private Map>> _highlighting = null;

  // SpellCheck Response
  private SpellCheckResponse _spellResponse = null;

  // Clustering Response
  private ClusteringResponse _clusterResponse = null;

  // Json Faceting Response
  private NestableJsonFacet _jsonFacetingResponse = null;

  // Suggester Response
  private SuggesterResponse _suggestResponse = null;

  // Terms Response
  private TermsResponse _termsResponse = null;

  // Field stats Response
  private Map _fieldStatsInfo = null;

  // Debug Info
  private Map _debugMap = null;
  private Map _explainMap = null;

  // utility variable used for automatic binding -- it should not be serialized
  private final transient SolrClient solrClient;

  public QueryResponse() {
    solrClient = null;
  }

  /** Utility constructor to set the solrServer and namedList */
  public QueryResponse(NamedList res, SolrClient solrClient) {
    this.setResponse(res);
    this.solrClient = solrClient;
  }

  public QueryResponse(SolrClient solrClient) {
    this.solrClient = solrClient;
  }

  @Override
  @SuppressWarnings({"rawtypes"})
  public void setResponse(NamedList res) {
    super.setResponse(res);

    // Look for known things
    for (int i = 0; i < res.size(); i++) {
      String n = res.getName(i);
      if ("responseHeader".equals(n)) {
        _header = (NamedList) res.getVal(i);
      } else if ("response".equals(n)) {
        _results = (SolrDocumentList) res.getVal(i);
      } else if ("sort_values".equals(n)) {
        _sortvalues = (NamedList) res.getVal(i);
      } else if ("facet_counts".equals(n)) {
        _facetInfo = (NamedList) res.getVal(i);
        // extractFacetInfo inspects _results, so defer calling it
        // in case it hasn't been populated yet.
      } else if ("debug".equals(n)) {
        _debugInfo = (NamedList) res.getVal(i);
        extractDebugInfo(_debugInfo);
      } else if ("grouped".equals(n)) {
        _groupedInfo = (NamedList) res.getVal(i);
        extractGroupedInfo(_groupedInfo);
      } else if ("expanded".equals(n)) {
        NamedList map = (NamedList) res.getVal(i);
        _expandedResults = map.asMap(1);
      } else if ("highlighting".equals(n)) {
        _highlightingInfo = (NamedList) res.getVal(i);
        extractHighlightingInfo(_highlightingInfo);
      } else if ("spellcheck".equals(n)) {
        _spellInfo = (NamedList) res.getVal(i);
        extractSpellCheckInfo(_spellInfo);
      } else if ("clusters".equals(n)) {
        _clusterInfo = (ArrayList>) res.getVal(i);
        extractClusteringInfo(_clusterInfo);
      } else if ("facets".equals(n)) {
        _jsonFacetingInfo = (NamedList) res.getVal(i);
        // Don't call extractJsonFacetingInfo(_jsonFacetingInfo) here in an effort to do it lazily
      } else if ("suggest".equals(n)) {
        _suggestInfo = (NamedList>) res.getVal(i);
        extractSuggesterInfo(_suggestInfo);
      } else if ("stats".equals(n)) {
        _statsInfo = (NamedList) res.getVal(i);
        extractStatsInfo(_statsInfo);
      } else if ("terms".equals(n)) {
        _termsInfo = (NamedList>) res.getVal(i);
        extractTermsInfo(_termsInfo);
      } else if ("moreLikeThis".equals(n)) {
        _moreLikeThisInfo = (NamedList) res.getVal(i);
      } else if ("taskList".equals(n)) {
        _tasksInfo = (NamedList) res.getVal(i);
      } else if ("cancellationResult".equals(n)) {
        _cancellationInfo = (String) res.getVal(i);
      } else if ("taskResult".equals(n)) {
        _taskStatusCheckInfo = (String) res.getVal(i);
      } else if (CursorMarkParams.CURSOR_MARK_NEXT.equals(n)) {
        _cursorMarkNext = (String) res.getVal(i);
      }
    }
    if (_facetInfo != null) extractFacetInfo(_facetInfo);
  }

  private void extractSpellCheckInfo(NamedList spellInfo) {
    _spellResponse = new SpellCheckResponse(spellInfo);
  }

  private void extractClusteringInfo(List> clusterInfo) {
    _clusterResponse = new ClusteringResponse(clusterInfo);
  }

  private void extractJsonFacetingInfo(NamedList facetInfo) {
    _jsonFacetingResponse = new NestableJsonFacet(facetInfo);
  }

  private void extractSuggesterInfo(NamedList> suggestInfo) {
    _suggestResponse = new SuggesterResponse(suggestInfo);
  }

  private void extractTermsInfo(NamedList> termsInfo) {
    _termsResponse = new TermsResponse(termsInfo);
  }

  private void extractStatsInfo(NamedList info) {
    _fieldStatsInfo = extractFieldStatsInfo(info);
  }

  private Map extractFieldStatsInfo(NamedList info) {
    if (info != null) {
      Map fieldStatsInfoMap = new TreeMap<>();
      NamedList> ff = (NamedList>) info.get("stats_fields");
      if (ff != null) {
        for (Map.Entry> entry : ff) {
          NamedList v = entry.getValue();
          if (v != null) {
            fieldStatsInfoMap.put(entry.getKey(), new FieldStatsInfo(v, entry.getKey()));
          }
        }
      }
      return fieldStatsInfoMap;
    }
    return null;
  }

  private void extractDebugInfo(NamedList debug) {
    _debugMap = new LinkedHashMap<>(); // keep the order
    for (Map.Entry info : debug) {
      _debugMap.put(info.getKey(), info.getValue());
    }

    // Parse out interesting bits from the debug info
    _explainMap = new HashMap<>();
    NamedList explain = (NamedList) _debugMap.get("explain");
    if (explain != null) {
      for (Map.Entry info : explain) {
        String key = info.getKey();
        _explainMap.put(key, info.getValue());
      }
    }
  }

  private void extractGroupedInfo(NamedList info) {
    if (info != null) {
      _groupResponse = new GroupResponse();
      int size = info.size();
      for (int i = 0; i < size; i++) {
        String fieldName = info.getName(i);
        Object fieldGroups = info.getVal(i);
        SimpleOrderedMap simpleOrderedMap = (SimpleOrderedMap) fieldGroups;

        Object oMatches = simpleOrderedMap.get("matches");
        Object oNGroups = simpleOrderedMap.get("ngroups");
        Object oGroups = simpleOrderedMap.get("groups");
        Object queryCommand = simpleOrderedMap.get("doclist");
        if (oMatches == null) {
          continue;
        }

        if (oGroups != null) {
          Integer iMatches = (Integer) oMatches;
          ArrayList groupsArr = (ArrayList) oGroups;
          GroupCommand groupedCommand;
          if (oNGroups != null) {
            Integer iNGroups = (Integer) oNGroups;
            groupedCommand = new GroupCommand(fieldName, iMatches, iNGroups);
          } else {
            groupedCommand = new GroupCommand(fieldName, iMatches);
          }

          for (Object oGrp : groupsArr) {
            @SuppressWarnings({"rawtypes"})
            SimpleOrderedMap grpMap = (SimpleOrderedMap) oGrp;
            Object sGroupValue = grpMap.get("groupValue");
            SolrDocumentList doclist = (SolrDocumentList) grpMap.get("doclist");
            Group group = new Group(sGroupValue != null ? sGroupValue.toString() : null, doclist);
            groupedCommand.add(group);
          }

          _groupResponse.add(groupedCommand);
        } else if (queryCommand != null) {
          Integer iMatches = (Integer) oMatches;
          GroupCommand groupCommand;
          if (oNGroups != null) {
            Integer iNGroups = (Integer) oNGroups;
            groupCommand = new GroupCommand(fieldName, iMatches, iNGroups);
          } else {
            groupCommand = new GroupCommand(fieldName, iMatches);
          }
          SolrDocumentList docList = (SolrDocumentList) queryCommand;
          groupCommand.add(new Group(fieldName, docList));
          _groupResponse.add(groupCommand);
        }
      }
    }
  }

  private void extractHighlightingInfo(NamedList info) {
    _highlighting = new HashMap<>();
    for (Map.Entry doc : info) {
      Map> fieldMap = new HashMap<>();
      _highlighting.put(doc.getKey(), fieldMap);

      NamedList> fnl = (NamedList>) doc.getValue();
      for (Map.Entry> field : fnl) {
        fieldMap.put(field.getKey(), field.getValue());
      }
    }
  }

  @SuppressWarnings({"rawtypes"})
  private void extractFacetInfo(NamedList info) {
    // Parse the queries
    _facetQuery = new LinkedHashMap<>();
    NamedList fq = (NamedList) info.get("facet_queries");
    if (fq != null) {
      for (Map.Entry entry : fq) {
        _facetQuery.put(entry.getKey(), entry.getValue());
      }
    }

    // Parse the facet info into fields
    // TODO?? The list could be  or ?  If always  then we can switch to 
    NamedList> ff = (NamedList>) info.get("facet_fields");
    if (ff != null) {
      _facetFields = new ArrayList<>(ff.size());
      _limitingFacets = new ArrayList<>(ff.size());

      long minsize = _results == null ? Long.MAX_VALUE : _results.getNumFound();
      for (Map.Entry> facet : ff) {
        FacetField f = new FacetField(facet.getKey());
        for (Map.Entry entry : facet.getValue()) {
          f.add(entry.getKey(), entry.getValue().longValue());
        }

        _facetFields.add(f);
        FacetField nl = f.getLimitingFields(minsize);
        if (nl.getValueCount() > 0) {
          _limitingFacets.add(nl);
        }
      }
    }

    // Parse range facets
    NamedList> rf = (NamedList>) info.get("facet_ranges");
    if (rf != null) {
      _facetRanges = extractRangeFacets(rf);
    }

    // Parse pivot facets
    NamedList pf = (NamedList) info.get("facet_pivot");
    if (pf != null) {
      _facetPivot = new NamedList<>();
      for (int i = 0; i < pf.size(); i++) {
        _facetPivot.add(pf.getName(i), readPivots((List) pf.getVal(i)));
      }
    }

    // Parse interval facets
    NamedList> intervalsNL =
        (NamedList>) info.get("facet_intervals");
    if (intervalsNL != null) {
      _intervalFacets = new ArrayList<>(intervalsNL.size());
      for (Map.Entry> intervalField : intervalsNL) {
        String field = intervalField.getKey();
        List counts =
            new ArrayList(intervalField.getValue().size());
        for (Map.Entry interval : intervalField.getValue()) {
          counts.add(new IntervalFacet.Count(interval.getKey(), (Integer) interval.getValue()));
        }
        _intervalFacets.add(new IntervalFacet(field, counts));
      }
    }
  }

  @SuppressWarnings({"rawtypes"})
  private List extractRangeFacets(NamedList> rf) {
    List facetRanges = new ArrayList<>(rf.size());

    for (Map.Entry> facet : rf) {
      NamedList values = facet.getValue();
      Object rawGap = values.get("gap");

      RangeFacet rangeFacet;
      if (rawGap instanceof Number) {
        Number gap = (Number) rawGap;
        Number start = (Number) values.get("start");
        Number end = (Number) values.get("end");

        Number before = (Number) values.get("before");
        Number after = (Number) values.get("after");
        Number between = (Number) values.get("between");

        rangeFacet =
            new RangeFacet.Numeric(facet.getKey(), start, end, gap, before, after, between);
      } else if (rawGap instanceof String && values.get("start") instanceof Date) {
        String gap = (String) rawGap;
        Date start = (Date) values.get("start");
        Date end = (Date) values.get("end");

        Number before = (Number) values.get("before");
        Number after = (Number) values.get("after");
        Number between = (Number) values.get("between");

        rangeFacet = new RangeFacet.Date(facet.getKey(), start, end, gap, before, after, between);
      } else {
        String gap = (String) rawGap;
        String start = (String) values.get("start");
        String end = (String) values.get("end");

        Number before = (Number) values.get("before");
        Number after = (Number) values.get("after");
        Number between = (Number) values.get("between");

        rangeFacet =
            new RangeFacet.Currency(facet.getKey(), start, end, gap, before, after, between);
      }

      NamedList counts = (NamedList) values.get("counts");
      for (Map.Entry entry : counts) {
        rangeFacet.addCount(entry.getKey(), entry.getValue());
      }

      facetRanges.add(rangeFacet);
    }
    return facetRanges;
  }

  @SuppressWarnings({"rawtypes"})
  protected List readPivots(List list) {
    ArrayList values = new ArrayList<>(list.size());
    for (NamedList nl : list) {
      // NOTE, this is cheating, but we know the order they are written in, so no need to check
      assert "field".equals(nl.getName(0));
      String f = (String) nl.getVal(0);
      assert "value".equals(nl.getName(1));
      Object v = nl.getVal(1);
      assert "count".equals(nl.getName(2));
      int cnt = ((Integer) nl.getVal(2)).intValue();

      List subPivots = null;
      Map fieldStatsInfos = null;
      Map queryCounts = null;
      List ranges = null;

      if (4 <= nl.size()) {
        for (int index = 3; index < nl.size(); index++) {
          final String key = nl.getName(index);
          final Object val = nl.getVal(index);
          switch (key) {
            case "pivot":
              {
                assert null != val : "Server sent back 'null' for sub pivots?";
                assert val instanceof List : "Server sent non-List for sub pivots?";

                subPivots = readPivots((List) val);
                break;
              }
            case "stats":
              {
                assert null != val : "Server sent back 'null' for stats?";
                assert val instanceof NamedList : "Server sent non-NamedList for stats?";

                fieldStatsInfos = extractFieldStatsInfo((NamedList) val);
                break;
              }
            case "queries":
              {
                // Parse the queries
                queryCounts = new LinkedHashMap<>();
                NamedList fq = (NamedList) val;
                if (fq != null) {
                  for (Map.Entry entry : fq) {
                    queryCounts.put(entry.getKey(), entry.getValue());
                  }
                }
                break;
              }
            case "ranges":
              {
                ranges = extractRangeFacets((NamedList>) val);
                break;
              }
            default:
              throw new RuntimeException("unknown key in pivot: " + key + " [" + val + "]");
          }
        }
      }

      values.add(new PivotField(f, v, cnt, subPivots, fieldStatsInfos, queryCounts, ranges));
    }
    return values;
  }

  // ------------------------------------------------------
  // ------------------------------------------------------

  /** Remove the field facet info */
  public void removeFacets() {
    _facetFields = new ArrayList<>();
  }

  // ------------------------------------------------------
  // ------------------------------------------------------

  public NamedList getHeader() {
    return _header;
  }

  public SolrDocumentList getResults() {
    return _results;
  }

  @SuppressWarnings({"rawtypes"})
  public NamedList getSortValues() {
    return _sortvalues;
  }

  public Map getDebugMap() {
    return _debugMap;
  }

  public Map getExplainMap() {
    return _explainMap;
  }

  public Map getFacetQuery() {
    return _facetQuery;
  }

  /**
   * @return map with each group value as key and the expanded documents that belong to the group as
   *     value. There is no guarantee on the order of the keys obtained via an iterator.
   */
  public Map getExpandedResults() {
    return this._expandedResults;
  }

  /**
   * Returns the {@link GroupResponse} containing the group commands. A group command can be the
   * result of one of the following parameters:
   *
   * 
    *
  • group.field *
  • group.func *
  • group.query *
* * @return the {@link GroupResponse} containing the group commands */ public GroupResponse getGroupResponse() { return _groupResponse; } public Map>> getHighlighting() { return _highlighting; } public SpellCheckResponse getSpellCheckResponse() { return _spellResponse; } public ClusteringResponse getClusteringResponse() { return _clusterResponse; } public NestableJsonFacet getJsonFacetingResponse() { if (_jsonFacetingInfo != null && _jsonFacetingResponse == null) extractJsonFacetingInfo(_jsonFacetingInfo); return _jsonFacetingResponse; } public SuggesterResponse getSuggesterResponse() { return _suggestResponse; } public TermsResponse getTermsResponse() { return _termsResponse; } public NamedList getMoreLikeThis() { return _moreLikeThisInfo; } public NamedList getTasksInfo() { return _tasksInfo; } public String getCancellationInfo() { return _cancellationInfo; } public String getTaskStatusCheckInfo() { return _taskStatusCheckInfo; } /** See also: {@link #getLimitingFacets()} */ public List getFacetFields() { return _facetFields; } public List getFacetDates() { return _facetDates; } @SuppressWarnings({"rawtypes"}) public List getFacetRanges() { return _facetRanges; } public NamedList> getFacetPivot() { return _facetPivot; } public List getIntervalFacets() { return _intervalFacets; } /** * get * * @param name the name of the * @return the FacetField by name or null if it does not exist */ public FacetField getFacetField(String name) { if (_facetFields == null) return null; for (FacetField f : _facetFields) { if (f.getName().equals(name)) return f; } return null; } public FacetField getFacetDate(String name) { if (_facetDates == null) return null; for (FacetField f : _facetDates) if (f.getName().equals(name)) return f; return null; } /** * @return a list of FacetFields where the count is less then then #getResults() {@link * SolrDocumentList#getNumFound()} *

If you want all results exactly as returned by solr, use: {@link #getFacetFields()} */ public List getLimitingFacets() { return _limitingFacets; } public List getBeans(Class type) { return solrClient == null ? new DocumentObjectBinder().getBeans(type, _results) : solrClient.getBinder().getBeans(type, _results); } public Map getFieldStatsInfo() { return _fieldStatsInfo; } public String getNextCursorMark() { return _cursorMarkNext; } }