From fa49623fd893c9a26e48e179b3873b86e8c73db4 Mon Sep 17 00:00:00 2001 From: Gethin James Date: Thu, 1 Jun 2017 11:13:25 +0000 Subject: [PATCH] Merged searchrep (5.2.1) to 5.2.N (5.2.1) 136989 gjames: SEARCH-430: Combining pivot with range git-svn-id: https://svn.alfresco.com/repos/alfresco-enterprise/alfresco/BRANCHES/DEV/5.2.N/root@137080 c4b6b30b-aa2e-2d43-bbcb-ca4b014f7261 --- .../search/impl/lucene/SolrJSONResultSet.java | 197 ++++++++++-------- .../search/impl/solr/SolrQueryHTTPClient.java | 29 ++- .../facetsresponse/RangeResultMapper.java | 133 ++++++++++++ .../impl/solr/SolrQueryHTTPClientTest.java | 15 +- 4 files changed, 280 insertions(+), 94 deletions(-) create mode 100644 source/java/org/alfresco/repo/search/impl/solr/facet/facetsresponse/RangeResultMapper.java diff --git a/source/java/org/alfresco/repo/search/impl/lucene/SolrJSONResultSet.java b/source/java/org/alfresco/repo/search/impl/lucene/SolrJSONResultSet.java index ea56cc57c3..7231ae08e4 100644 --- a/source/java/org/alfresco/repo/search/impl/lucene/SolrJSONResultSet.java +++ b/source/java/org/alfresco/repo/search/impl/lucene/SolrJSONResultSet.java @@ -1,66 +1,68 @@ -/* - * #%L - * Alfresco Repository - * %% - * Copyright (C) 2005 - 2016 Alfresco Software Limited - * %% - * This file is part of the Alfresco software. - * If the software was purchased under a paid Alfresco license, the terms of - * the paid license agreement will prevail. Otherwise, the software is - * provided under the following open source license terms: - * - * Alfresco is free software: you can redistribute it and/or modify - * it under the terms of the GNU Lesser General Public License as published by - * the Free Software Foundation, either version 3 of the License, or - * (at your option) any later version. - * - * Alfresco is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU Lesser General Public License for more details. - * - * You should have received a copy of the GNU Lesser General Public License - * along with Alfresco. If not, see . - * #L% - */ +/* + * #%L + * Alfresco Repository + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ package org.alfresco.repo.search.impl.lucene; -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; -import java.util.stream.Collectors; - -import org.alfresco.repo.domain.node.NodeDAO; -import org.alfresco.repo.search.SimpleResultSetMetaData; -import org.alfresco.repo.search.impl.solr.facet.facetsresponse.GenericBucket; -import org.alfresco.repo.search.impl.solr.facet.facetsresponse.GenericFacetResponse; -import org.alfresco.repo.search.impl.solr.facet.facetsresponse.GenericFacetResponse.FACET_TYPE; -import org.alfresco.repo.search.impl.solr.facet.facetsresponse.ListMetric; -import org.alfresco.repo.search.impl.solr.facet.facetsresponse.Metric; -import org.alfresco.repo.search.impl.solr.facet.facetsresponse.Metric.METRIC_TYPE; -import org.alfresco.repo.search.impl.solr.facet.facetsresponse.PercentileMetric; -import org.alfresco.repo.search.impl.solr.facet.facetsresponse.SimpleMetric; -import org.alfresco.service.cmr.repository.ChildAssociationRef; -import org.alfresco.service.cmr.repository.NodeRef; -import org.alfresco.service.cmr.repository.NodeService; -import org.alfresco.service.cmr.search.LimitBy; -import org.alfresco.service.cmr.search.PermissionEvaluationMode; -import org.alfresco.service.cmr.search.ResultSet; -import org.alfresco.service.cmr.search.ResultSetMetaData; -import org.alfresco.service.cmr.search.ResultSetRow; -import org.alfresco.service.cmr.search.SearchParameters; -import org.alfresco.service.cmr.search.SpellCheckResult; -import org.alfresco.util.Pair; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; -import org.json.JSONArray; -import org.json.JSONException; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.stream.Collectors; + +import org.alfresco.repo.domain.node.NodeDAO; +import org.alfresco.repo.search.SimpleResultSetMetaData; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.GenericBucket; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.GenericFacetResponse; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.GenericFacetResponse.FACET_TYPE; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.ListMetric; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.Metric; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.Metric.METRIC_TYPE; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.PercentileMetric; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.RangeResultMapper; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.SimpleMetric; +import org.alfresco.service.cmr.repository.ChildAssociationRef; +import org.alfresco.service.cmr.repository.NodeRef; +import org.alfresco.service.cmr.repository.NodeService; +import org.alfresco.service.cmr.search.LimitBy; +import org.alfresco.service.cmr.search.PermissionEvaluationMode; +import org.alfresco.service.cmr.search.RangeParameters; +import org.alfresco.service.cmr.search.ResultSet; +import org.alfresco.service.cmr.search.ResultSetMetaData; +import org.alfresco.service.cmr.search.ResultSetRow; +import org.alfresco.service.cmr.search.SearchParameters; +import org.alfresco.service.cmr.search.SpellCheckResult; +import org.alfresco.util.Pair; +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; +import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; /** @@ -296,34 +298,17 @@ public class SolrJSONResultSet implements ResultSet, JSONResult for(Iterator it = facet_pivot.keys(); it.hasNext(); /**/) { String pivotName = (String)it.next(); - pivotFacets.addAll(buildPivot(facet_pivot, pivotName)); + pivotFacets.addAll(buildPivot(facet_pivot, pivotName, searchParameters.getRanges())); } } if(facet_counts.has("facet_ranges")) { JSONObject facet_ranges = facet_counts.getJSONObject("facet_ranges"); - for(Iterator it = facet_ranges.keys(); it.hasNext();) - { - String fieldName = (String) it.next(); - String end = facet_ranges.getJSONObject(fieldName).getString("end"); - JSONArray rangeCollection = facet_ranges.getJSONObject(fieldName).getJSONArray("counts"); - List> buckets = new ArrayList>(); - for(int i = 0; i < rangeCollection.length(); i+=2) - { - Map rangeMap = new HashMap(3); - String rangeFrom = rangeCollection.getString(i); - String facetRangeCount = rangeCollection.getString(i+1); - String rangeTo = (i+2 < rangeCollection.length() ? rangeCollection.getString(i+2):end); - String label = rangeFrom + " - " + rangeTo; - rangeMap.put(GenericFacetResponse.LABEL, label); - rangeMap.put(GenericFacetResponse.COUNT, facetRangeCount); - rangeMap.put(GenericFacetResponse.START, rangeFrom); - rangeMap.put(GenericFacetResponse.END, rangeTo); - buckets.add(rangeMap); - } - facetRanges.put(fieldName, buckets); - } + Map>> builtRanges = buildRanges(facet_ranges); + builtRanges.forEach((pKey, buckets) -> { + facetRanges.put(pKey, buckets); + }); } } @@ -378,6 +363,35 @@ public class SolrJSONResultSet implements ResultSet, JSONResult PermissionEvaluationMode.EAGER, searchParameters); } + protected Map>> buildRanges(JSONObject facet_ranges) throws JSONException + { + Map>> ranges = new HashMap<>(); + + for(Iterator it = facet_ranges.keys(); it.hasNext();) + { + String fieldName = (String) it.next(); + String end = facet_ranges.getJSONObject(fieldName).getString("end"); + JSONArray rangeCollection = facet_ranges.getJSONObject(fieldName).getJSONArray("counts"); + List> buckets = new ArrayList>(); + for(int i = 0; i < rangeCollection.length(); i+=2) + { + Map rangeMap = new HashMap(3); + String rangeFrom = rangeCollection.getString(i); + String facetRangeCount = rangeCollection.getString(i+1); + String rangeTo = (i+2 < rangeCollection.length() ? rangeCollection.getString(i+2):end); + String label = rangeFrom + " - " + rangeTo; + rangeMap.put(GenericFacetResponse.LABEL, label); + rangeMap.put(GenericFacetResponse.COUNT, facetRangeCount); + rangeMap.put(GenericFacetResponse.START, rangeFrom); + rangeMap.put(GenericFacetResponse.END, rangeTo); + buckets.add(rangeMap); + } + ranges.put(fieldName, buckets); + } + + return ranges; + } + protected Map> buildStats(JSONObject statsObj) throws JSONException { if(statsObj.has("stats_fields")) @@ -401,7 +415,7 @@ public class SolrJSONResultSet implements ResultSet, JSONResult return Collections.emptyMap(); } - protected List buildPivot(JSONObject facet_pivot, String pivotName) throws JSONException + protected List buildPivot(JSONObject facet_pivot, String pivotName, List rangeParameters) throws JSONException { if (!facet_pivot.has(pivotName)) return Collections.emptyList(); @@ -412,6 +426,7 @@ public class SolrJSONResultSet implements ResultSet, JSONResult { JSONObject piv = pivots.getJSONObject(i); Set metrics = new HashSet<>(1); + List nested = new ArrayList<>(); String field = piv.getString("field"); String value = piv.getString("value"); if (piv.has("stats")) @@ -422,10 +437,20 @@ public class SolrJSONResultSet implements ResultSet, JSONResult metrics.addAll(getMetrics(pVal)); }); } + Integer count = Integer.parseInt(piv.getString("count")); metrics.add(new SimpleMetric(METRIC_TYPE.count,count)); - List innerPivot = buildPivot(piv, "pivot"); - GenericBucket buck = new GenericBucket(piv.getString("value"), field+":"+value, null, metrics, innerPivot); + nested.addAll(buildPivot(piv, "pivot", rangeParameters)); + + if (piv.has("ranges")) + { + JSONObject ranges = piv.getJSONObject("ranges"); + Map>> builtRanges = buildRanges(ranges); + List rangefacets = RangeResultMapper.getGenericFacetsForRanges(builtRanges,rangeParameters); + nested.addAll(rangefacets); + } + + GenericBucket buck = new GenericBucket(value, field+":"+value, null, metrics, nested); List listBucks = pivotBuckets.containsKey(field)?pivotBuckets.get(field):new ArrayList<>(); listBucks.add(buck); pivotBuckets.put(field, listBucks); diff --git a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java index 4b717ad9b0..edf9063565 100644 --- a/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java +++ b/source/java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClient.java @@ -41,6 +41,7 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.Optional; import java.util.Set; import java.util.StringJoiner; @@ -819,20 +820,42 @@ public class SolrQueryHTTPClient implements BeanFactoryAware, InitializingBean List pivotsList = new ArrayList<>(); pivotsList.addAll(pivotKeys); url.append("&facet.pivot="); + + StringBuilder prefix = new StringBuilder("{! "); + if (searchParameters.getStats() != null && !searchParameters.getStats().isEmpty()) { for (StatsRequestParameters aStat:searchParameters.getStats()) { if (pivotKeys.contains(aStat.getLabel())) { - url.append(encoder.encode("{!stats=", "UTF-8")) - .append(encoder.encode(aStat.getLabel(), "UTF-8")) - .append(encoder.encode("}", "UTF-8")); + prefix.append("stats="+aStat.getLabel()+" "); pivotsList.remove(aStat.getLabel()); break; //only do it once } } } + + if (searchParameters.getRanges() != null && !searchParameters.getRanges().isEmpty()) + { + for (RangeParameters aRange:searchParameters.getRanges()) + { + Optional found = pivotKeys.stream().filter(aKey -> aRange.getTags().contains(aKey)).findFirst(); + + if (found.isPresent()) + { + prefix.append("range="+found.get()+" "); + pivotsList.remove(found.get()); + break; //only do it once + } + } + } + + if (prefix.length() > 3) //We have add something + { + url.append(encoder.encode(prefix.toString().trim(), "UTF-8")); + url.append(encoder.encode("}", "UTF-8")); + } url.append(encoder.encode(String.join(",", pivotsList), "UTF-8")); } } diff --git a/source/java/org/alfresco/repo/search/impl/solr/facet/facetsresponse/RangeResultMapper.java b/source/java/org/alfresco/repo/search/impl/solr/facet/facetsresponse/RangeResultMapper.java new file mode 100644 index 0000000000..65bc0d6a72 --- /dev/null +++ b/source/java/org/alfresco/repo/search/impl/solr/facet/facetsresponse/RangeResultMapper.java @@ -0,0 +1,133 @@ +/*- + * #%L + * Alfresco Remote API + * %% + * Copyright (C) 2005 - 2016 Alfresco Software Limited + * %% + * This file is part of the Alfresco software. + * If the software was purchased under a paid Alfresco license, the terms of + * the paid license agreement will prevail. Otherwise, the software is + * provided under the following open source license terms: + * + * Alfresco is free software: you can redistribute it and/or modify + * it under the terms of the GNU Lesser General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Alfresco is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License + * along with Alfresco. If not, see . + * #L% + */ + +package org.alfresco.repo.search.impl.solr.facet.facetsresponse; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.GenericBucket; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.GenericFacetResponse; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.GenericFacetResponse.FACET_TYPE; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.Metric; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.Metric.METRIC_TYPE; +import org.alfresco.repo.search.impl.solr.facet.facetsresponse.SimpleMetric; +import org.alfresco.service.cmr.search.RangeParameters; + +/**Helper to map range results. + * + * @author Michael Suzuki + */ +public class RangeResultMapper +{ + /** + * Transforms the facet range response into generic facet response. + * @param facetFields + * @param searchQuery + * @return GenericFacetResponse + */ + public static List getGenericFacetsForRanges(Map>> facetFields, List ranges) + { + List ffcs = new ArrayList<>(facetFields.size()); + if (facetFields != null && !facetFields.isEmpty()) + { + for (Entry>> facet : facetFields.entrySet()) + { + List buckets = new ArrayList<>(); + facet.getValue().forEach(action -> buckets.add(buildGenericBucketFromRange(facet.getKey(), + (Map) action, ranges))); + ffcs.add(new GenericFacetResponse(FACET_TYPE.range, facet.getKey(), buckets)); + } + } + return ffcs; + } + + /** + * Builds the generic facet response out of range results. + * @param facetField + * @param facet + * @return + */ + private static GenericBucket buildGenericBucketFromRange(String facetField, Map facet, List ranges) + { + String start = facet.get(GenericFacetResponse.START); + String end = facet.get(GenericFacetResponse.END); + boolean startInclusive = true; + boolean endInclusive = false; + + if (ranges!= null) { + for(RangeParameters range : ranges) + { + if(range.getField().equalsIgnoreCase(facetField)) + { + List includes = range.getInclude(); + if(includes != null && !includes.isEmpty()) + { + startInclusive = range.isRangeStartInclusive(); + endInclusive = range.isRangeEndInclusive(); + } + } + } + } + + facet.put(GenericFacetResponse.START_INC.toString(), Boolean.toString(startInclusive)); + facet.put(GenericFacetResponse.END_INC.toString(), Boolean.toString(endInclusive)); + + facet.remove(GenericFacetResponse.LABEL); + StringBuilder filterQ = new StringBuilder(); + filterQ.append(facetField).append(":") + .append(startInclusive ? "[" :"<") + .append(start).append(" TO ") + .append(end) + .append(endInclusive ? "]" :">"); + + Set metrics = new HashSet( + Arrays.asList(new SimpleMetric( + METRIC_TYPE.count,facet.get( + GenericFacetResponse.COUNT)))); + facet.remove("count"); + + StringBuilder label = new StringBuilder(); + label.append(startInclusive ? "[" :"(") + .append(start) + .append(" - ") + .append(end) + .append(endInclusive ? "]" :")"); + + return new GenericBucket(label.toString(), + filterQ.toString(), + null, + metrics, + null, + facet); + + } +} diff --git a/source/test-java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClientTest.java b/source/test-java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClientTest.java index bb161f242f..55c0a6f590 100644 --- a/source/test-java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClientTest.java +++ b/source/test-java/org/alfresco/repo/search/impl/solr/SolrQueryHTTPClientTest.java @@ -404,6 +404,13 @@ public class SolrQueryHTTPClientTest SearchParameters params = new SearchParameters(); params.setSearchTerm("bob"); params.addPivots(Arrays.asList("creator")); + params.setStats(Arrays.asList( + new StatsRequestParameters("created", "piv1", null, null, null,null, null, null, null, + null, null, null, null,null, null, null) + )); + List ranges = new ArrayList(); + ranges.add(new RangeParameters("content.size", "0", "1000000", "10000", true, Collections.emptyList(), Collections.emptyList(), Arrays.asList("csize"), null)); + params.setRanges(ranges); StringBuilder urlBuilder = new StringBuilder(); @@ -413,7 +420,7 @@ public class SolrQueryHTTPClientTest assertTrue(url.contains("&facet=true")); assertTrue(url.contains("facet.pivot=creator")); - params.addPivots(Arrays.asList("cm:name", "{!stats=piv1}cat")); + params.addPivots(Arrays.asList("cm:name", "piv1", "csize")); urlBuilder = new StringBuilder(); client.buildPivotParameters(params, encoder, urlBuilder); @@ -421,7 +428,7 @@ public class SolrQueryHTTPClientTest assertNotNull(url); assertTrue(url.contains("&facet=true")); assertTrue(url.contains("facet.pivot="+ encoder.encode("creator", "UTF-8"))); - assertTrue(url.contains("facet.pivot="+ encoder.encode("cm:name,{!stats=piv1}cat", "UTF-8"))); + assertTrue(url.contains("facet.pivot="+ encoder.encode("{! stats=piv1 range=csize}cm:name", "UTF-8"))); } @Test @@ -467,7 +474,7 @@ public class SolrQueryHTTPClientTest client.buildRangeParameters(params, encoder, urlBuilder); String url2 = urlBuilder.toString(); assertTrue(url2.contains("&facet=true")); - assertTrue(url2.contains("&facet.range=content.size")); + assertTrue(url2.contains("&facet.range="+encoder.encode("{!tag=dt tag=doc }", "UTF-8")+"content.size")); assertTrue(url2.contains("&f.content.size.facet.range.start=0")); assertTrue(url2.contains("&f.content.size.facet.range.end=1000000")); assertTrue(url2.contains("&f.content.size.facet.range.gap=10000")); @@ -476,8 +483,6 @@ public class SolrQueryHTTPClientTest assertFalse(url2.contains("&f.content.size.facet.range.other=before")); assertTrue(url2.contains("&f.content.size.facet.range.hardend=true")); assertTrue(url2.contains("&range.field={!ex=bart,homer}")); - assertTrue(url2.contains("&fq={!tag=dt}dt")); - assertTrue(url2.contains("&fq={!tag=doc}doc")); } @Test public void testBuildMulitRange() throws UnsupportedEncodingException