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.SearchField; 036import org.ametys.cms.search.SearchResult; 037import org.ametys.cms.search.SearchResults; 038import org.ametys.cms.search.SearchResultsIterable; 039import org.ametys.cms.search.solr.SearcherFactory; 040import org.ametys.cms.search.solr.SolrSearchResults; 041import org.ametys.plugins.repository.AmetysObject; 042import org.ametys.plugins.repository.AmetysObjectIterable; 043 044/** 045 * {@link SearcherFactory} for creating a searcher for {@link AbstractSearchGenerator} 046 */ 047public class FrontOfficeSearcherFactory extends SearcherFactory 048{ 049 /** The component role. */ 050 @SuppressWarnings("hiding") 051 public static final String ROLE = FrontOfficeSearcherFactory.class.getName(); 052 053 @Override 054 public FrontOfficeSearcher create() 055 { 056 return new FrontOfficeSearcher(getLogger()); 057 } 058 059 /** 060 * Searcher for {@link AbstractSearchGenerator} 061 */ 062 public class FrontOfficeSearcher extends Searcher 063 { 064 private Collection<QueryFacet> _queryFacets; 065 private Collection<String> _queryFacetValues; 066 067 /** 068 * Build a Searcher with default values. 069 * @param logger The logger. 070 */ 071 protected FrontOfficeSearcher(Logger logger) 072 { 073 super(logger); 074 _queryFacets = new ArrayList<>(); 075 _queryFacetValues = new HashSet<>(); 076 } 077 078 /** 079 * Sets the query facets (facet.query parameters) 080 * @param queryFacets The facets 081 * @return The Searcher object itself. 082 */ 083 public FrontOfficeSearcher withQueryFacets(Collection<QueryFacet> queryFacets) 084 { 085 _queryFacets = new ArrayList<>(queryFacets); 086 return this; 087 } 088 089 /** 090 * Set the facet.query values 091 * @param queryFacetValues The values 092 * @return The Searcher object itself. 093 */ 094 public FrontOfficeSearcher withQueryFacetValues(Collection<String> queryFacetValues) 095 { 096 _queryFacetValues = new HashSet<>(queryFacetValues); 097 return this; 098 } 099 100 @Override 101 protected void modifySolrQuery(JsonQueryRequest solrQuery) 102 { 103 _queryFacets.forEach(facet -> 104 { 105 String keyName = facet.getKeyName(); 106 String excludedTagName = facet.getExcludedTagName(); 107 String facetQuery = facet.getFacetQuery(); 108 109 boolean queryFacetValue = _queryFacetValues.contains(keyName); 110 111 if (queryFacetValue) 112 { 113 solrQuery.withFilter(Map.of("#" + excludedTagName, facetQuery)); 114 } 115 116 QueryFacetMap facetMap = new QueryFacetMap(facetQuery).withDomain(new DomainMap().withTagsToExclude(excludedTagName)); 117 118 solrQuery.withFacet(keyName, facetMap); 119 }); 120 } 121 122 @Override 123 public <A extends AmetysObject> SearchResults<A> _buildResults(QueryResponse response, List<SearchField> facets) throws Exception 124 { 125 SearchResults<A> wrappedRes = super._buildResults(response, facets); 126 Collection<String> queryFacetKeys = _queryFacets.stream().map(QueryFacet::getKeyName).collect(Collectors.toList()); 127 128 Map<String, Integer> facetQueryResults = new HashMap<>(); 129 NestableJsonFacet facetingResponse = response.getJsonFacetingResponse(); 130 if (facetingResponse != null) 131 { 132 for (String query : facetingResponse.getQueryFacetNames()) 133 { 134 if (queryFacetKeys.contains(query)) 135 { 136 facetQueryResults.put(query, (int) facetingResponse.getQueryFacet(query).getCount()); 137 } 138 } 139 } 140 141 return new FrontOfficeSolrSearchResults<>(wrappedRes, response, facetQueryResults); 142 } 143 } 144 145 /** 146 * Wraps a 'facet.query' parameter 147 */ 148 public static class QueryFacet 149 { 150 private final String _keyName; 151 private final String _excludedTagName; 152 private final String _facetQuery; 153 154 /** 155 * Creates a QueryFacet 156 * @param keyName The name of the query facet 157 * @param excludedTagName The tag for exclusion for facet counting 158 * @param facetQuery The value of the query 159 */ 160 public QueryFacet(String keyName, String excludedTagName, String facetQuery) 161 { 162 _keyName = keyName; 163 _excludedTagName = excludedTagName; 164 _facetQuery = facetQuery; 165 } 166 167 /** 168 * Gets the keyName 169 * @return the keyName 170 */ 171 public String getKeyName() 172 { 173 return _keyName; 174 } 175 176 /** 177 * Gets the excludedTagName 178 * @return the excludedTagName 179 */ 180 public String getExcludedTagName() 181 { 182 return _excludedTagName; 183 } 184 185 /** 186 * Gets the facetQuery 187 * @return the _acetQuery 188 */ 189 public String getFacetQuery() 190 { 191 return _facetQuery; 192 } 193 } 194 195 /** 196 * Wrapper of a {@link SolrSearchResults} for {@link AbstractSearchGenerator} (add the possibility to add facet.queries) 197 * @param <A> the actual type of {@link AmetysObject}s. 198 */ 199 public static class FrontOfficeSolrSearchResults<A extends AmetysObject>/* extends SolrSearchResults<A>*/implements SearchResults<A> 200 { 201 private Map<String, Integer> _facetQueryResults; 202 private SearchResults<A> _searchResults; 203 204 /** 205 * Build a FrontOfficeSolrSearchResults object. 206 * @param searchResults The wrapped {@link SearchResults} object 207 * @param response the solr search response. 208 * @param facetQueryResults The facet.query results. 209 */ 210 public FrontOfficeSolrSearchResults(SearchResults<A> searchResults, 211 QueryResponse response, 212 Map<String, Integer> facetQueryResults) 213 { 214 _searchResults = searchResults; 215 _facetQueryResults = facetQueryResults; 216 } 217 218 @Override 219 public SearchResultsIterable<SearchResult<A>> getResults() 220 { 221 return _searchResults.getResults(); 222 } 223 224 @Override 225 public AmetysObjectIterable<A> getObjects() 226 { 227 return _searchResults.getObjects(); 228 } 229 230 @Override 231 public Iterable<String> getObjectIds() 232 { 233 return _searchResults.getObjectIds(); 234 } 235 236 @Override 237 public Map<String, Map<String, Integer>> getFacetResults() 238 { 239 return _searchResults.getFacetResults(); 240 } 241 242 /** 243 * Gets the facet.query results as a Map 244 * @return the facet.query results 245 */ 246 public Map<String, Integer> getFacetQueryResults() 247 { 248 return Collections.unmodifiableMap(_facetQueryResults); 249 } 250 251 @Override 252 public long getTotalCount() 253 { 254 return _searchResults.getTotalCount(); 255 } 256 257 @Override 258 public float getMaxScore() 259 { 260 return _searchResults.getMaxScore(); 261 } 262 263 @Override 264 public Optional<Map<String, Object>> getDebugMap() 265 { 266 return _searchResults.getDebugMap(); 267 } 268 } 269}