You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

Facets.java 9.0KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228
  1. /*
  2. * SonarQube
  3. * Copyright (C) 2009-2023 SonarSource SA
  4. * mailto:info AT sonarsource DOT com
  5. *
  6. * This program is free software; you can redistribute it and/or
  7. * modify it under the terms of the GNU Lesser General Public
  8. * License as published by the Free Software Foundation; either
  9. * version 3 of the License, or (at your option) any later version.
  10. *
  11. * This program is distributed in the hope that it will be useful,
  12. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  13. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  14. * Lesser General Public License for more details.
  15. *
  16. * You should have received a copy of the GNU Lesser General Public License
  17. * along with this program; if not, write to the Free Software Foundation,
  18. * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  19. */
  20. package org.sonar.server.es;
  21. import java.time.ZoneId;
  22. import java.util.ArrayList;
  23. import java.util.Collections;
  24. import java.util.Date;
  25. import java.util.LinkedHashMap;
  26. import java.util.List;
  27. import java.util.Map;
  28. import java.util.Set;
  29. import javax.annotation.CheckForNull;
  30. import org.apache.commons.lang.builder.ReflectionToStringBuilder;
  31. import org.apache.commons.lang.builder.ToStringStyle;
  32. import org.elasticsearch.action.search.SearchResponse;
  33. import org.elasticsearch.search.aggregations.Aggregation;
  34. import org.elasticsearch.search.aggregations.Aggregations;
  35. import org.elasticsearch.search.aggregations.HasAggregations;
  36. import org.elasticsearch.search.aggregations.bucket.MultiBucketsAggregation;
  37. import org.elasticsearch.search.aggregations.bucket.filter.Filter;
  38. import org.elasticsearch.search.aggregations.bucket.histogram.Histogram;
  39. import org.elasticsearch.search.aggregations.bucket.missing.Missing;
  40. import org.elasticsearch.search.aggregations.bucket.nested.ReverseNested;
  41. import org.elasticsearch.search.aggregations.bucket.terms.Terms;
  42. import org.elasticsearch.search.aggregations.metrics.Sum;
  43. import static org.sonar.api.utils.DateUtils.parseDateTime;
  44. import static org.sonarqube.ws.client.issue.IssuesWsParameters.FACET_MODE_EFFORT;
  45. public class Facets {
  46. public static final String SELECTED_SUB_AGG_NAME_SUFFIX = "_selected";
  47. public static final String TOTAL = "total";
  48. private static final String NO_DATA_PREFIX = "no_data_";
  49. private static final String FILTER_SUFFIX = "_filter";
  50. private static final String FILTER_BY_RULE_PREFIX = "filter_by_rule_types_";
  51. private final LinkedHashMap<String, LinkedHashMap<String, Long>> facetsByName;
  52. private final ZoneId timeZone;
  53. public Facets(LinkedHashMap<String, LinkedHashMap<String, Long>> facetsByName, ZoneId timeZone) {
  54. this.facetsByName = facetsByName;
  55. this.timeZone = timeZone;
  56. }
  57. public Facets(SearchResponse response, ZoneId timeZone) {
  58. this.facetsByName = new LinkedHashMap<>();
  59. this.timeZone = timeZone;
  60. Aggregations aggregations = response.getAggregations();
  61. if (aggregations != null) {
  62. for (Aggregation facet : aggregations) {
  63. processAggregation(facet);
  64. }
  65. }
  66. }
  67. private void processAggregation(Aggregation aggregation) {
  68. if (Missing.class.isAssignableFrom(aggregation.getClass())) {
  69. processMissingAggregation((Missing) aggregation);
  70. } else if (Terms.class.isAssignableFrom(aggregation.getClass())) {
  71. processTermsAggregation((Terms) aggregation);
  72. } else if (Filter.class.isAssignableFrom(aggregation.getClass())) {
  73. processSubAggregations((Filter) aggregation);
  74. } else if (HasAggregations.class.isAssignableFrom(aggregation.getClass())) {
  75. processSubAggregations((HasAggregations) aggregation);
  76. } else if (Histogram.class.isAssignableFrom(aggregation.getClass())) {
  77. processDateHistogram((Histogram) aggregation);
  78. } else if (Sum.class.isAssignableFrom(aggregation.getClass())) {
  79. processSum((Sum) aggregation);
  80. } else if (MultiBucketsAggregation.class.isAssignableFrom(aggregation.getClass())) {
  81. processMultiBucketAggregation((MultiBucketsAggregation) aggregation);
  82. } else {
  83. throw new IllegalArgumentException("Aggregation type not supported yet: " + aggregation.getClass());
  84. }
  85. }
  86. private void processMissingAggregation(Missing aggregation) {
  87. long docCount = aggregation.getDocCount();
  88. if (docCount > 0L) {
  89. LinkedHashMap<String, Long> facet = getOrCreateFacet(aggregation.getName().replace("_missing", ""));
  90. if (aggregation.getAggregations().getAsMap().containsKey(FACET_MODE_EFFORT)) {
  91. facet.put("", Math.round(((Sum) aggregation.getAggregations().get(FACET_MODE_EFFORT)).getValue()));
  92. } else {
  93. facet.put("", docCount);
  94. }
  95. }
  96. }
  97. private void processTermsAggregation(Terms aggregation) {
  98. String facetName = aggregation.getName();
  99. // TODO document this naming convention
  100. if (facetName.contains("__") && !facetName.startsWith("__")) {
  101. facetName = facetName.substring(0, facetName.indexOf("__"));
  102. }
  103. facetName = facetName.replace(SELECTED_SUB_AGG_NAME_SUFFIX, "");
  104. LinkedHashMap<String, Long> facet = getOrCreateFacet(facetName);
  105. for (Terms.Bucket value : aggregation.getBuckets()) {
  106. List<Aggregation> aggregationList = value.getAggregations().asList();
  107. if (aggregationList.size() == 1) {
  108. facet.put(value.getKeyAsString(), Math.round(((Sum) aggregationList.get(0)).getValue()));
  109. } else {
  110. facet.put(value.getKeyAsString(), value.getDocCount());
  111. }
  112. }
  113. }
  114. private void processSubAggregations(HasAggregations aggregation) {
  115. if (Filter.class.isAssignableFrom(aggregation.getClass())) {
  116. Filter filter = (Filter) aggregation;
  117. if (filter.getName().startsWith(NO_DATA_PREFIX)) {
  118. LinkedHashMap<String, Long> facet = getOrCreateFacet(filter.getName().replaceFirst(NO_DATA_PREFIX, ""));
  119. facet.put("NO_DATA", ((Filter) aggregation).getDocCount());
  120. }
  121. }
  122. for (Aggregation sub : getOrderedAggregations(aggregation)) {
  123. processAggregation(sub);
  124. }
  125. }
  126. private static List<Aggregation> getOrderedAggregations(HasAggregations topAggregation) {
  127. String topAggregationName = ((Aggregation) topAggregation).getName();
  128. List<Aggregation> orderedAggregations = new ArrayList<>();
  129. for (Aggregation aggregation : topAggregation.getAggregations()) {
  130. if (isNameMatchingTopAggregation(topAggregationName, aggregation.getName())) {
  131. orderedAggregations.add(0, aggregation);
  132. } else {
  133. orderedAggregations.add(aggregation);
  134. }
  135. }
  136. return orderedAggregations;
  137. }
  138. private static boolean isNameMatchingTopAggregation(String topAggregationName, String aggregationName) {
  139. return aggregationName.equals(topAggregationName) ||
  140. aggregationName.equals(FILTER_BY_RULE_PREFIX + topAggregationName.replace(FILTER_SUFFIX, ""));
  141. }
  142. private void processDateHistogram(Histogram aggregation) {
  143. LinkedHashMap<String, Long> facet = getOrCreateFacet(aggregation.getName());
  144. for (Histogram.Bucket value : aggregation.getBuckets()) {
  145. String day = dateTimeToDate(value.getKeyAsString(), timeZone);
  146. if (value.getAggregations().getAsMap().containsKey(FACET_MODE_EFFORT)) {
  147. facet.put(day, Math.round(((Sum) value.getAggregations().get(FACET_MODE_EFFORT)).getValue()));
  148. } else {
  149. facet.put(day, value.getDocCount());
  150. }
  151. }
  152. }
  153. private static String dateTimeToDate(String timestamp, ZoneId timeZone) {
  154. Date date = parseDateTime(timestamp);
  155. return date.toInstant().atZone(timeZone).toLocalDate().toString();
  156. }
  157. private void processSum(Sum aggregation) {
  158. getOrCreateFacet(aggregation.getName()).put(TOTAL, Math.round(aggregation.getValue()));
  159. }
  160. private void processMultiBucketAggregation(MultiBucketsAggregation aggregation) {
  161. LinkedHashMap<String, Long> facet = getOrCreateFacet(aggregation.getName());
  162. aggregation.getBuckets().forEach(bucket -> {
  163. if (!bucket.getAggregations().asList().isEmpty()) {
  164. Aggregation next = bucket.getAggregations().iterator().next();
  165. if (next instanceof ReverseNested reverseNestedBucket) {
  166. facet.put(bucket.getKeyAsString(), reverseNestedBucket.getDocCount());
  167. }
  168. } else {
  169. facet.put(bucket.getKeyAsString(), bucket.getDocCount());
  170. }
  171. });
  172. }
  173. public boolean contains(String facetName) {
  174. return facetsByName.containsKey(facetName);
  175. }
  176. /**
  177. * The buckets of the given facet. Null if the facet does not exist
  178. */
  179. @CheckForNull
  180. public LinkedHashMap<String, Long> get(String facetName) {
  181. return facetsByName.get(facetName);
  182. }
  183. public Map<String, LinkedHashMap<String, Long>> getAll() {
  184. return facetsByName;
  185. }
  186. public Set<String> getBucketKeys(String facetName) {
  187. LinkedHashMap<String, Long> facet = facetsByName.get(facetName);
  188. if (facet != null) {
  189. return facet.keySet();
  190. }
  191. return Collections.emptySet();
  192. }
  193. public Set<String> getNames() {
  194. return facetsByName.keySet();
  195. }
  196. @Override
  197. public String toString() {
  198. return ReflectionToStringBuilder.toString(this, ToStringStyle.SIMPLE_STYLE);
  199. }
  200. private LinkedHashMap<String, Long> getOrCreateFacet(String facetName) {
  201. return facetsByName.computeIfAbsent(facetName, n -> new LinkedHashMap<>());
  202. }
  203. }