001/* 002 * This library is part of OpenCms - 003 * the Open Source Content Management System 004 * 005 * Copyright (c) Alkacon Software GmbH & Co. KG (http://www.alkacon.com) 006 * 007 * This library is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * This library is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU 015 * Lesser General Public License for more details. 016 * 017 * For further information about Alkacon Software, please see the 018 * company website: http://www.alkacon.com 019 * 020 * For further information about OpenCms, please see the 021 * project website: http://www.opencms.org 022 * 023 * You should have received a copy of the GNU Lesser General Public 024 * License along with this library; if not, write to the Free Software 025 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 026 */ 027 028package org.opencms.jsp.search.result; 029 030import org.opencms.file.CmsObject; 031import org.opencms.jsp.search.controller.I_CmsSearchControllerDidYouMean; 032import org.opencms.jsp.search.controller.I_CmsSearchControllerFacetField; 033import org.opencms.jsp.search.controller.I_CmsSearchControllerFacetQuery; 034import org.opencms.jsp.search.controller.I_CmsSearchControllerFacetRange; 035import org.opencms.jsp.search.controller.I_CmsSearchControllerMain; 036import org.opencms.search.CmsSearchException; 037import org.opencms.search.CmsSearchResource; 038import org.opencms.search.solr.CmsSolrQuery; 039import org.opencms.search.solr.CmsSolrResultList; 040import org.opencms.util.CmsCollectionsGenericWrapper; 041 042import java.util.ArrayList; 043import java.util.Collection; 044import java.util.HashMap; 045import java.util.List; 046import java.util.Map; 047 048import org.apache.commons.collections.Transformer; 049import org.apache.solr.client.solrj.response.FacetField; 050import org.apache.solr.client.solrj.response.RangeFacet; 051import org.apache.solr.client.solrj.response.SpellCheckResponse.Suggestion; 052 053/** Wrapper for the whole search result. Also allowing to access the search form controller. */ 054public class CmsSearchResultWrapper implements I_CmsSearchResultWrapper { 055 056 /** The result list as returned normally. */ 057 final CmsSolrResultList m_solrResultList; 058 /** The collection of found resources/documents, already wrapped as {@code I_CmsSearchResourceBean}. */ 059 private Collection<I_CmsSearchResourceBean> m_foundResources; 060 /** The first index of the documents displayed. */ 061 private final Long m_start; 062 /** The last index of the documents displayed. */ 063 private final int m_end; 064 /** The number of found results. */ 065 private final long m_numFound; 066 /** The maximal score of the results. */ 067 private final Float m_maxScore; 068 /** The main controller for the search form. */ 069 final I_CmsSearchControllerMain m_controller; 070 /** Map from field facet names to the facets as given by the search result. */ 071 private Map<String, FacetField> m_fieldFacetMap; 072 /** Map from range facet names to the facets as given by the search result. */ 073 @SuppressWarnings("rawtypes") 074 private Map<String, RangeFacet> m_rangeFacetMap; 075 /** Map from facet names to the facet entries checked, but not part of the result. */ 076 private Map<String, List<String>> m_missingFieldFacetEntryMap; 077 /** Map from facet names to the facet entries checked, but not part of the result. */ 078 private Map<String, List<String>> m_missingRangeFacetEntryMap; 079 /** Query facet items that are checked, but not part of the result. */ 080 private List<String> m_missingQueryFacetEntries; 081 /** Map with the facet items of the query facet and their counts. */ 082 private Map<String, Integer> m_facetQuery; 083 /** CmsObject. */ 084 private final CmsObject m_cmsObject; 085 /** Search exception, if one occurs. */ 086 private final CmsSearchException m_exception; 087 /** The search query sent to Solr. */ 088 private final CmsSolrQuery m_query; 089 090 /** Constructor taking the main search form controller and the result list as normally returned. 091 * @param controller The main search form controller. 092 * @param resultList The result list as returned from OpenCms' embedded Solr server. 093 * @param query The complete query send to Solr. 094 * @param cms The Cms object used to access XML contents, if wanted. 095 * @param exception Search exception, or <code>null</code> if no exception occurs. 096 */ 097 @SuppressWarnings("rawtypes") 098 public CmsSearchResultWrapper( 099 final I_CmsSearchControllerMain controller, 100 final CmsSolrResultList resultList, 101 final CmsSolrQuery query, 102 final CmsObject cms, 103 final CmsSearchException exception) { 104 105 m_controller = controller; 106 m_solrResultList = resultList; 107 m_cmsObject = cms; 108 m_exception = exception; 109 m_query = query; 110 if (resultList != null) { 111 convertSearchResults(resultList); 112 final long l = resultList.getStart() == null ? 1 : resultList.getStart().longValue() + 1; 113 m_start = Long.valueOf(l); 114 m_end = resultList.getEnd(); 115 m_numFound = resultList.getNumFound(); 116 m_maxScore = resultList.getMaxScore(); 117 if (resultList.getFacetQuery() != null) { 118 Map<String, Integer> originalMap = resultList.getFacetQuery(); 119 m_facetQuery = new HashMap<String, Integer>(originalMap.size()); 120 for (String q : resultList.getFacetQuery().keySet()) { 121 m_facetQuery.put(removeLocalParamPrefix(q), originalMap.get(q)); 122 } 123 } 124 List<RangeFacet> rangeFacets = resultList.getFacetRanges(); 125 if (null != rangeFacets) { 126 m_rangeFacetMap = new HashMap<String, RangeFacet>(rangeFacets.size()); 127 for (RangeFacet facet : rangeFacets) { 128 m_rangeFacetMap.put(facet.getName(), facet); 129 } 130 } 131 } else { 132 m_start = null; 133 m_end = 0; 134 m_numFound = 0; 135 m_maxScore = null; 136 } 137 if (null == m_rangeFacetMap) { 138 m_rangeFacetMap = new HashMap<String, RangeFacet>(); 139 } 140 } 141 142 /** 143 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getController() 144 */ 145 @Override 146 public I_CmsSearchControllerMain getController() { 147 148 return m_controller; 149 } 150 151 /** 152 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getDidYouMeanCollated() 153 */ 154 public String getDidYouMeanCollated() { 155 156 String suggestion = null; 157 I_CmsSearchControllerDidYouMean didYouMeanController = getController().getDidYouMean(); 158 if ((null != didYouMeanController) && didYouMeanController.getConfig().getCollate()) { 159 if ((m_solrResultList != null) && (m_solrResultList.getSpellCheckResponse() != null)) { 160 suggestion = m_solrResultList.getSpellCheckResponse().getCollatedResult(); 161 } 162 } 163 return suggestion; 164 } 165 166 /** 167 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getDidYouMeanSuggestion() 168 */ 169 public Suggestion getDidYouMeanSuggestion() { 170 171 I_CmsSearchControllerDidYouMean didYouMeanController = getController().getDidYouMean(); 172 Suggestion usedSuggestion = null; 173 if ((null != didYouMeanController) 174 && ((m_solrResultList != null) && (m_solrResultList.getSpellCheckResponse() != null))) { 175 // find most suitable suggestion 176 List<Suggestion> suggestionList = m_solrResultList.getSpellCheckResponse().getSuggestions(); 177 int queryLength = m_controller.getDidYouMean().getState().getQuery().length(); 178 int minDistance = queryLength + 1; 179 for (Suggestion suggestion : suggestionList) { 180 int currentDistance = Math.abs(queryLength - suggestion.getToken().length()); 181 if (currentDistance < minDistance) { 182 usedSuggestion = suggestion; 183 minDistance = currentDistance; 184 } 185 } 186 } 187 return usedSuggestion; 188 } 189 190 /** 191 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getEmptyStateParameters() 192 */ 193 public I_CmsSearchStateParameters getEmptyStateParameters() { 194 195 Map<String, String[]> parameters = new HashMap<String, String[]>(); 196 return new CmsSearchStateParameters(this, parameters); 197 } 198 199 /** 200 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getEnd() 201 */ 202 @Override 203 public int getEnd() { 204 205 return m_end; 206 } 207 208 /** 209 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getException() 210 */ 211 public CmsSearchException getException() { 212 213 return m_exception; 214 } 215 216 /** 217 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getFacetQuery() 218 */ 219 @Override 220 public Map<String, Integer> getFacetQuery() { 221 222 return m_facetQuery; 223 } 224 225 /** 226 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getFieldFacet() 227 */ 228 @Override 229 public Map<String, FacetField> getFieldFacet() { 230 231 if (m_fieldFacetMap == null) { 232 m_fieldFacetMap = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() { 233 234 @Override 235 public Object transform(final Object fieldName) { 236 237 return m_solrResultList == null ? null : m_solrResultList.getFacetField(fieldName.toString()); 238 } 239 }); 240 } 241 return m_fieldFacetMap; 242 } 243 244 /** 245 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getFieldFacets() 246 */ 247 @Override 248 public Collection<FacetField> getFieldFacets() { 249 250 return m_solrResultList == null ? null : m_solrResultList.getFacetFields(); 251 } 252 253 /** 254 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getFinalQuery() 255 */ 256 public CmsSolrQuery getFinalQuery() { 257 258 return m_query; 259 } 260 261 /** 262 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getHighlighting() 263 */ 264 @Override 265 public Map<String, Map<String, List<String>>> getHighlighting() { 266 267 return m_solrResultList == null ? null : m_solrResultList.getHighLighting(); 268 } 269 270 /** 271 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getMaxScore() 272 */ 273 @Override 274 public Float getMaxScore() { 275 276 return m_maxScore; 277 } 278 279 /** 280 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getMissingSelectedFieldFacetEntries() 281 */ 282 @Override 283 public Map<String, List<String>> getMissingSelectedFieldFacetEntries() { 284 285 if (m_missingFieldFacetEntryMap == null) { 286 m_missingFieldFacetEntryMap = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() { 287 288 @Override 289 public Object transform(final Object fieldName) { 290 291 FacetField facetResult = m_solrResultList == null 292 ? null 293 : m_solrResultList.getFacetField(fieldName.toString()); 294 I_CmsSearchControllerFacetField facetController = m_controller.getFieldFacets().getFieldFacetController().get( 295 fieldName.toString()); 296 List<String> result = new ArrayList<String>(); 297 298 if (null != facetController) { 299 300 List<String> checkedEntries = facetController.getState().getCheckedEntries(); 301 if (null != facetResult) { 302 List<String> returnedValues = new ArrayList<String>(facetResult.getValues().size()); 303 for (FacetField.Count value : facetResult.getValues()) { 304 returnedValues.add(value.getName()); 305 } 306 for (String checked : checkedEntries) { 307 if (!returnedValues.contains(checked)) { 308 result.add(checked); 309 } 310 } 311 } else { 312 result = checkedEntries; 313 } 314 } 315 return result; 316 } 317 }); 318 } 319 return m_missingFieldFacetEntryMap; 320 } 321 322 /** 323 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getMissingSelectedQueryFacetEntries() 324 */ 325 public List<String> getMissingSelectedQueryFacetEntries() { 326 327 if (null == m_missingQueryFacetEntries) { 328 329 Collection<String> returnedValues = getFacetQuery().keySet(); 330 331 I_CmsSearchControllerFacetQuery facetController = m_controller.getQueryFacet(); 332 333 m_missingQueryFacetEntries = new ArrayList<String>(); 334 335 if (null != facetController) { 336 337 List<String> checkedEntries = facetController.getState().getCheckedEntries(); 338 if (null != returnedValues) { 339 for (String checked : checkedEntries) { 340 if (!returnedValues.contains(checked)) { 341 m_missingQueryFacetEntries.add(checked); 342 } 343 } 344 } else { 345 m_missingQueryFacetEntries = checkedEntries; 346 } 347 } 348 } 349 return m_missingQueryFacetEntries; 350 } 351 352 /** 353 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getMissingSelectedRangeFacetEntries() 354 */ 355 public Map<String, List<String>> getMissingSelectedRangeFacetEntries() { 356 357 if (m_missingRangeFacetEntryMap == null) { 358 m_missingRangeFacetEntryMap = CmsCollectionsGenericWrapper.createLazyMap(new Transformer() { 359 360 @Override 361 public Object transform(final Object fieldName) { 362 363 @SuppressWarnings("rawtypes") 364 RangeFacet facetResult = m_rangeFacetMap.get(fieldName); 365 I_CmsSearchControllerFacetRange facetController = m_controller.getRangeFacets().getRangeFacetController().get( 366 fieldName.toString()); 367 List<String> result = new ArrayList<String>(); 368 369 if (null != facetController) { 370 371 List<String> checkedEntries = facetController.getState().getCheckedEntries(); 372 if (null != facetResult) { 373 List<String> returnedValues = new ArrayList<String>(facetResult.getCounts().size()); 374 for (Object value : facetResult.getCounts()) { 375 //TODO: Should yield RangeFacet.Count - but somehow does not!?!? 376 // Hence, the cast should not be necessary at all. 377 returnedValues.add(((RangeFacet.Count)value).getValue()); 378 } 379 for (String checked : checkedEntries) { 380 if (!returnedValues.contains(checked)) { 381 result.add(checked); 382 } 383 } 384 } else { 385 result = checkedEntries; 386 } 387 } 388 return result; 389 } 390 }); 391 } 392 return m_missingRangeFacetEntryMap; 393 394 } 395 396 /** 397 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getNumFound() 398 */ 399 @Override 400 public long getNumFound() { 401 402 return m_numFound; 403 } 404 405 /** 406 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getNumMaxReturned() 407 */ 408 public long getNumMaxReturned() { 409 410 long maxReturnedResults = Integer.valueOf( 411 m_controller.getCommon().getConfig().getMaxReturnedResults()).longValue(); 412 return (maxReturnedResults < 0) || (maxReturnedResults > getNumFound()) ? getNumFound() : maxReturnedResults; 413 } 414 415 /** 416 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getNumPages() 417 */ 418 @Override 419 public int getNumPages() { 420 421 return m_solrResultList == null ? 1 : m_controller.getPagination().getConfig().getNumPages(getNumMaxReturned()); 422 } 423 424 /** 425 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getPageNavFirst() 426 */ 427 @Override 428 public int getPageNavFirst() { 429 430 final int page = m_controller.getPagination().getState().getCurrentPage() 431 - ((m_controller.getPagination().getConfig().getPageNavLength() - 1) / 2); 432 return page < 1 ? 1 : page; 433 } 434 435 /** 436 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getPageNavLast() 437 */ 438 @Override 439 public int getPageNavLast() { 440 441 final int page = m_controller.getPagination().getState().getCurrentPage() 442 + ((m_controller.getPagination().getConfig().getPageNavLength()) / 2); 443 return page > getNumPages() ? getNumPages() : page; 444 } 445 446 /** 447 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getRangeFacet() 448 */ 449 @SuppressWarnings("rawtypes") 450 public Map<String, RangeFacet> getRangeFacet() { 451 452 return m_rangeFacetMap; 453 } 454 455 /** 456 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getRangeFacets() 457 */ 458 @SuppressWarnings("rawtypes") 459 public Collection<RangeFacet> getRangeFacets() { 460 461 return m_rangeFacetMap.values(); 462 } 463 464 /** 465 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getSearchResults() 466 */ 467 @Override 468 public Collection<I_CmsSearchResourceBean> getSearchResults() { 469 470 return m_foundResources; 471 } 472 473 /** 474 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getStart() 475 */ 476 @Override 477 public Long getStart() { 478 479 return m_start; 480 } 481 482 /** 483 * @see org.opencms.jsp.search.result.I_CmsSearchResultWrapper#getStateParameters() 484 */ 485 public CmsSearchStateParameters getStateParameters() { 486 487 Map<String, String[]> parameters = new HashMap<String, String[]>(); 488 m_controller.addParametersForCurrentState(parameters); 489 return new CmsSearchStateParameters(this, parameters); 490 } 491 492 /** Converts the search results from CmsSearchResource to CmsSearchResourceBean. 493 * @param searchResults The collection of search results to transform. 494 */ 495 protected void convertSearchResults(final Collection<CmsSearchResource> searchResults) { 496 497 m_foundResources = new ArrayList<I_CmsSearchResourceBean>(); 498 for (final CmsSearchResource searchResult : searchResults) { 499 m_foundResources.add(new CmsSearchResourceBean(searchResult, m_cmsObject)); 500 } 501 } 502 503 /** Removes the !{ex=...} prefix from the query. 504 * @param q the original query 505 * @return the query with the prefix !{ex=...} removed. 506 */ 507 private String removeLocalParamPrefix(final String q) { 508 509 int index = q.indexOf('}'); 510 if (index >= 0) { 511 return q.substring(index + 1); 512 } 513 return q; 514 } 515 516}