001/* 002 * Copyright 2017 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.web.frontoffice; 017 018import java.util.ArrayList; 019import java.util.Collection; 020import java.util.Collections; 021import java.util.HashMap; 022import java.util.HashSet; 023import java.util.List; 024import java.util.Map; 025import java.util.Optional; 026import java.util.stream.Collectors; 027 028import org.apache.solr.client.solrj.request.json.DomainMap; 029import org.apache.solr.client.solrj.request.json.JsonQueryRequest; 030import org.apache.solr.client.solrj.request.json.QueryFacetMap; 031import org.apache.solr.client.solrj.response.QueryResponse; 032import org.apache.solr.client.solrj.response.json.NestableJsonFacet; 033import org.slf4j.Logger; 034 035import org.ametys.cms.search.SearchResult; 036import org.ametys.cms.search.SearchResults; 037import org.ametys.cms.search.SearchResultsIterable; 038import org.ametys.cms.search.solr.SearcherFactory; 039import org.ametys.cms.search.solr.SolrSearchResults; 040import org.ametys.plugins.repository.AmetysObject; 041import org.ametys.plugins.repository.AmetysObjectIterable; 042 043/** 044 * {@link SearcherFactory} for creating a searcher for {@link AbstractSearchGenerator} 045 */ 046public class FrontOfficeSearcherFactory extends SearcherFactory 047{ 048 /** The component role. */ 049 @SuppressWarnings("hiding") 050 public static final String ROLE = FrontOfficeSearcherFactory.class.getName(); 051 052 @Override 053 public FrontOfficeSearcher create() 054 { 055 return new FrontOfficeSearcher(getLogger()); 056 } 057 058 /** 059 * Searcher for {@link AbstractSearchGenerator} 060 */ 061 public class FrontOfficeSearcher extends Searcher 062 { 063 private Collection<QueryFacet> _queryFacets; 064 private Collection<String> _queryFacetValues; 065 066 /** 067 * Build a Searcher with default values. 068 * @param logger The logger. 069 */ 070 protected FrontOfficeSearcher(Logger logger) 071 { 072 super(logger); 073 _queryFacets = new ArrayList<>(); 074 _queryFacetValues = new HashSet<>(); 075 } 076 077 /** 078 * Sets the query facets (facet.query parameters) 079 * @param queryFacets The facets 080 * @return The Searcher object itself. 081 */ 082 public FrontOfficeSearcher withQueryFacets(Collection<QueryFacet> queryFacets) 083 { 084 _queryFacets = new ArrayList<>(queryFacets); 085 return this; 086 } 087 088 /** 089 * Set the facet.query values 090 * @param queryFacetValues The values 091 * @return The Searcher object itself. 092 */ 093 public FrontOfficeSearcher withQueryFacetValues(Collection<String> queryFacetValues) 094 { 095 _queryFacetValues = new HashSet<>(queryFacetValues); 096 return this; 097 } 098 099 @Override 100 protected void modifySolrQuery(JsonQueryRequest solrQuery) 101 { 102 _queryFacets.forEach(facet -> 103 { 104 String keyName = facet.getKeyName(); 105 String excludedTagName = facet.getExcludedTagName(); 106 String facetQuery = facet.getFacetQuery(); 107 108 boolean queryFacetValue = _queryFacetValues.contains(keyName); 109 110 if (queryFacetValue) 111 { 112 solrQuery.withFilter(Map.of("#" + excludedTagName, facetQuery)); 113 } 114 115 QueryFacetMap facetMap = new QueryFacetMap(facetQuery).withDomain(new DomainMap().withTagsToExclude(excludedTagName)); 116 117 solrQuery.withFacet(keyName, facetMap); 118 }); 119 } 120 121 @Override 122 public <A extends AmetysObject> SearchResults<A> _buildResults(QueryResponse response, List<FacetDefinition> facets) throws Exception 123 { 124 SearchResults<A> wrappedRes = super._buildResults(response, facets); 125 Collection<String> queryFacetKeys = _queryFacets.stream().map(QueryFacet::getKeyName).collect(Collectors.toList()); 126 127 Map<String, Integer> facetQueryResults = new HashMap<>(); 128 NestableJsonFacet facetingResponse = response.getJsonFacetingResponse(); 129 if (facetingResponse != null) 130 { 131 for (String query : facetingResponse.getQueryFacetNames()) 132 { 133 if (queryFacetKeys.contains(query)) 134 { 135 facetQueryResults.put(query, (int) facetingResponse.getQueryFacet(query).getCount()); 136 } 137 } 138 } 139 140 return new FrontOfficeSolrSearchResults<>(wrappedRes, response, facetQueryResults); 141 } 142 } 143 144 /** 145 * Wraps a 'facet.query' parameter 146 */ 147 public static class QueryFacet 148 { 149 private final String _keyName; 150 private final String _excludedTagName; 151 private final String _facetQuery; 152 153 /** 154 * Creates a QueryFacet 155 * @param keyName The name of the query facet 156 * @param excludedTagName The tag for exclusion for facet counting 157 * @param facetQuery The value of the query 158 */ 159 public QueryFacet(String keyName, String excludedTagName, String facetQuery) 160 { 161 _keyName = keyName; 162 _excludedTagName = excludedTagName; 163 _facetQuery = facetQuery; 164 } 165 166 /** 167 * Gets the keyName 168 * @return the keyName 169 */ 170 public String getKeyName() 171 { 172 return _keyName; 173 } 174 175 /** 176 * Gets the excludedTagName 177 * @return the excludedTagName 178 */ 179 public String getExcludedTagName() 180 { 181 return _excludedTagName; 182 } 183 184 /** 185 * Gets the facetQuery 186 * @return the _acetQuery 187 */ 188 public String getFacetQuery() 189 { 190 return _facetQuery; 191 } 192 } 193 194 /** 195 * Wrapper of a {@link SolrSearchResults} for {@link AbstractSearchGenerator} (add the possibility to add facet.queries) 196 * @param <A> the actual type of {@link AmetysObject}s. 197 */ 198 public static class FrontOfficeSolrSearchResults<A extends AmetysObject>/* extends SolrSearchResults<A>*/implements SearchResults<A> 199 { 200 private Map<String, Integer> _facetQueryResults; 201 private SearchResults<A> _searchResults; 202 203 /** 204 * Build a FrontOfficeSolrSearchResults object. 205 * @param searchResults The wrapped {@link SearchResults} object 206 * @param response the solr search response. 207 * @param facetQueryResults The facet.query results. 208 */ 209 public FrontOfficeSolrSearchResults(SearchResults<A> searchResults, 210 QueryResponse response, 211 Map<String, Integer> facetQueryResults) 212 { 213 _searchResults = searchResults; 214 _facetQueryResults = facetQueryResults; 215 } 216 217 @Override 218 public SearchResultsIterable<SearchResult<A>> getResults() 219 { 220 return _searchResults.getResults(); 221 } 222 223 @Override 224 public AmetysObjectIterable<A> getObjects() 225 { 226 return _searchResults.getObjects(); 227 } 228 229 @Override 230 public Iterable<String> getObjectIds() 231 { 232 return _searchResults.getObjectIds(); 233 } 234 235 @Override 236 public Map<String, Map<String, Integer>> getFacetResults() 237 { 238 return _searchResults.getFacetResults(); 239 } 240 241 /** 242 * Gets the facet.query results as a Map 243 * @return the facet.query results 244 */ 245 public Map<String, Integer> getFacetQueryResults() 246 { 247 return Collections.unmodifiableMap(_facetQueryResults); 248 } 249 250 @Override 251 public long getTotalCount() 252 { 253 return _searchResults.getTotalCount(); 254 } 255 256 @Override 257 public float getMaxScore() 258 { 259 return _searchResults.getMaxScore(); 260 } 261 262 @Override 263 public Optional<Map<String, Object>> getDebugMap() 264 { 265 return _searchResults.getDebugMap(); 266 } 267 } 268}