View Javadoc
1   /*
2    * Copyright 2012-2025 CodeLibs Project and the Others.
3    *
4    * Licensed under the Apache License, Version 2.0 (the "License");
5    * you may not use this file except in compliance with the License.
6    * You may obtain a copy of the License at
7    *
8    *     http://www.apache.org/licenses/LICENSE-2.0
9    *
10   * Unless required by applicable law or agreed to in writing, software
11   * distributed under the License is distributed on an "AS IS" BASIS,
12   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
13   * either express or implied. See the License for the specific language
14   * governing permissions and limitations under the License.
15   */
16  package org.codelibs.fess.util;
17  
18  import java.nio.charset.StandardCharsets;
19  import java.util.ArrayList;
20  import java.util.LinkedHashMap;
21  import java.util.List;
22  import java.util.Map;
23  
24  import org.codelibs.fess.Constants;
25  import org.opensearch.search.aggregations.Aggregations;
26  import org.opensearch.search.aggregations.bucket.filter.Filter;
27  import org.opensearch.search.aggregations.bucket.terms.Terms;
28  
29  import com.google.common.io.BaseEncoding;
30  
31  /**
32   * Response object for faceted search results containing query counts and field facets.
33   * This class processes OpenSearch aggregations to provide structured facet information
34   * for search result filtering and navigation.
35   */
36  public class FacetResponse {
37      /**
38       * Map containing query facet counts, where keys are decoded query strings
39       * and values are document counts for each query.
40       */
41      protected Map<String, Long> queryCountMap = new LinkedHashMap<>();
42  
43      /**
44       * List of field facets containing aggregated field values and their counts.
45       */
46      protected List<Field> fieldList = new ArrayList<>();
47  
48      /**
49       * Constructs a FacetResponse from OpenSearch aggregations.
50       * Processes both field facets and query facets from the aggregation results.
51       *
52       * @param aggregations the OpenSearch aggregations containing facet data, may be null
53       */
54      public FacetResponse(final Aggregations aggregations) {
55          if (aggregations != null) {
56              aggregations.forEach(aggregation -> {
57                  if (aggregation.getName().startsWith(Constants.FACET_FIELD_PREFIX)) {
58                      final Terms termFacet = (Terms) aggregation;
59                      fieldList.add(new Field(termFacet));
60                  } else if (aggregation.getName().startsWith(Constants.FACET_QUERY_PREFIX)) {
61                      final Filter queryFacet = (Filter) aggregation;
62                      final String encodedQuery = queryFacet.getName().substring(Constants.FACET_QUERY_PREFIX.length());
63                      queryCountMap.put(new String(BaseEncoding.base64().decode(encodedQuery), StandardCharsets.UTF_8),
64                              queryFacet.getDocCount());
65                  }
66  
67              });
68          }
69      }
70  
71      /**
72       * Checks if this response contains any facet information.
73       *
74       * @return true if either query count map or field list is not null
75       */
76      public boolean hasFacetResponse() {
77          return queryCountMap != null || fieldList != null;
78      }
79  
80      /**
81       * Represents a field facet with its name and value counts.
82       * Each field facet contains multiple values with their respective document counts.
83       */
84      public static class Field {
85          /**
86           * Map containing field values and their document counts.
87           * Keys are field values as strings, values are document counts.
88           */
89          protected Map<String, Long> valueCountMap = new LinkedHashMap<>();
90  
91          /**
92           * The decoded name of the field.
93           */
94          protected String name;
95  
96          /**
97           * Constructs a Field from OpenSearch Terms aggregation.
98           * Decodes the field name and processes all term buckets to extract
99           * field values and their document counts.
100          *
101          * @param termFacet the OpenSearch Terms aggregation containing field facet data
102          */
103         public Field(final Terms termFacet) {
104             final String encodedField = termFacet.getName().substring(Constants.FACET_FIELD_PREFIX.length());
105             name = new String(BaseEncoding.base64().decode(encodedField), StandardCharsets.UTF_8);
106             for (final Terms.Bucket tfEntry : termFacet.getBuckets()) {
107                 valueCountMap.put(tfEntry.getKeyAsString(), tfEntry.getDocCount());
108             }
109         }
110 
111         /**
112          * Gets the map of field values and their document counts.
113          *
114          * @return the valueCountMap containing field values and counts
115          */
116         public Map<String, Long> getValueCountMap() {
117             return valueCountMap;
118         }
119 
120         /**
121          * Gets the decoded name of this field facet.
122          *
123          * @return the field name
124          */
125         public String getName() {
126             return name;
127         }
128 
129     }
130 
131     /**
132      * Gets the map of query facet counts.
133      *
134      * @return the queryCountMap containing decoded query strings and their counts
135      */
136     public Map<String, Long> getQueryCountMap() {
137         return queryCountMap;
138     }
139 
140     /**
141      * Gets the list of field facets.
142      *
143      * @return the fieldList containing all field facet information
144      */
145     public List<Field> getFieldList() {
146         return fieldList;
147     }
148 
149     @Override
150     public String toString() {
151         return "FacetResponse [queryCountMap=" + queryCountMap + ", fieldList=" + fieldList + "]";
152     }
153 
154 }