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 }