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.ui.apps.lists; 029 030import org.opencms.file.CmsObject; 031import org.opencms.jsp.search.controller.I_CmsSearchControllerFacetField; 032import org.opencms.jsp.search.controller.I_CmsSearchControllerFacetRange; 033import org.opencms.jsp.search.result.CmsSearchResultWrapper; 034import org.opencms.main.CmsLog; 035import org.opencms.relations.CmsCategory; 036import org.opencms.relations.CmsCategoryService; 037import org.opencms.search.solr.CmsSolrResultList; 038import org.opencms.ui.A_CmsUI; 039import org.opencms.ui.CmsVaadinUtils; 040import org.opencms.ui.apps.Messages; 041import org.opencms.ui.components.OpenCmsTheme; 042import org.opencms.util.CmsStringUtil; 043 044import java.util.ArrayList; 045import java.util.Collection; 046import java.util.Collections; 047import java.util.HashMap; 048import java.util.List; 049import java.util.Locale; 050import java.util.Map; 051 052import org.apache.commons.logging.Log; 053import org.apache.solr.client.solrj.response.FacetField; 054import org.apache.solr.client.solrj.response.FacetField.Count; 055import org.apache.solr.client.solrj.response.RangeFacet; 056 057import com.vaadin.ui.Button; 058import com.vaadin.ui.Button.ClickEvent; 059import com.vaadin.ui.Button.ClickListener; 060import com.vaadin.ui.Component; 061import com.vaadin.ui.GridLayout; 062import com.vaadin.ui.Label; 063import com.vaadin.ui.Panel; 064import com.vaadin.ui.UI; 065import com.vaadin.ui.VerticalLayout; 066import com.vaadin.ui.themes.ValoTheme; 067 068/** 069 * Displays search result facets.<p> 070 */ 071public class CmsResultFacets extends VerticalLayout { 072 073 /** The logger for this class. */ 074 private static final Log LOG = CmsLog.getLog(CmsResultFacets.class.getName()); 075 076 /** Style name indicating a facet is selected. */ 077 private static final String SELECTED_STYLE = ValoTheme.LABEL_BOLD; 078 079 /** The serial version id. */ 080 private static final long serialVersionUID = 7190928063356086124L; 081 082 /** The list manager instance. */ 083 private CmsListManager m_manager; 084 085 /** The selected field facets. */ 086 private Map<String, List<String>> m_selectedFieldFacets; 087 088 /** The selected folders. */ 089 private List<String> m_selectedFolders; 090 091 /** The selected range facets. */ 092 private Map<String, List<String>> m_selectedRangeFacets; 093 094 /** The use full category paths flag. */ 095 private boolean m_useFullPathCategories; 096 097 /** 098 * Constructor.<p> 099 * 100 * @param manager the list manager instance 101 */ 102 public CmsResultFacets(CmsListManager manager) { 103 104 m_manager = manager; 105 m_selectedFieldFacets = new HashMap<String, List<String>>(); 106 m_selectedRangeFacets = new HashMap<String, List<String>>(); 107 m_selectedFolders = new ArrayList<String>(); 108 m_useFullPathCategories = true; 109 addStyleName("v-scrollable"); 110 setMargin(true); 111 setSpacing(true); 112 } 113 114 /** 115 * Displays the result facets.<p> 116 * 117 * @param solrResultList the search result 118 * @param resultWrapper the result wrapper 119 */ 120 public void displayFacetResult(CmsSolrResultList solrResultList, CmsSearchResultWrapper resultWrapper) { 121 122 removeAllComponents(); 123 Component categories = prepareCategoryFacets(solrResultList, resultWrapper); 124 if (categories != null) { 125 addComponent(categories); 126 } 127 Component folders = prepareFolderFacets(solrResultList, resultWrapper); 128 if (folders != null) { 129 addComponent(folders); 130 } 131 Component dates = prepareDateFacets(solrResultList, resultWrapper); 132 if (dates != null) { 133 addComponent(dates); 134 } 135 } 136 137 /** 138 * Resets the selected facets.<p> 139 */ 140 public void resetFacets() { 141 142 m_selectedFieldFacets.clear(); 143 m_selectedRangeFacets.clear(); 144 } 145 146 /** 147 * Returns the selected field facets.<p> 148 * 149 * @return the selected field facets 150 */ 151 protected Map<String, List<String>> getSelectedFieldFacets() { 152 153 return m_selectedFieldFacets; 154 } 155 156 /** 157 * Returns the selected range facets.<p> 158 * 159 * @return the selected range facets 160 */ 161 protected Map<String, List<String>> getSelectedRangeFactes() { 162 163 return m_selectedRangeFacets; 164 } 165 166 /** 167 * Returns whether the given component is selected.<p> 168 * 169 * @param component the component 170 * 171 * @return <code>true</code> in case selected 172 */ 173 boolean isSelected(Component component) { 174 175 return component.getStyleName().contains(SELECTED_STYLE); 176 } 177 178 /** 179 * Resets the selected facets and triggers a new search.<p> 180 */ 181 void resetFacetsAndSearch() { 182 183 resetFacets(); 184 m_manager.search(m_selectedFieldFacets, m_selectedRangeFacets); 185 } 186 187 /** 188 * Selects the given field facet.<p> 189 * 190 * @param field the field name 191 * @param value the value 192 */ 193 void selectFieldFacet(String field, String value) { 194 195 m_selectedFieldFacets.clear(); 196 m_selectedRangeFacets.clear(); 197 m_selectedFieldFacets.put(field, Collections.singletonList(value)); 198 m_manager.search(m_selectedFieldFacets, m_selectedRangeFacets); 199 } 200 201 /** 202 * Selects the given range facet.<p> 203 * 204 * @param field the field name 205 * @param value the value 206 */ 207 void selectRangeFacet(String field, String value) { 208 209 m_selectedFieldFacets.clear(); 210 m_selectedRangeFacets.clear(); 211 m_selectedRangeFacets.put(field, Collections.singletonList(value)); 212 m_manager.search(m_selectedFieldFacets, m_selectedRangeFacets); 213 } 214 215 /** 216 * Filters the available folder facets.<p> 217 * 218 * @param folderFacets the folder facets 219 * 220 * @return the filtered facets 221 */ 222 private Collection<Count> filterFolderFacets(Collection<Count> folderFacets) { 223 224 String siteRoot = A_CmsUI.getCmsObject().getRequestContext().getSiteRoot(); 225 if (!siteRoot.endsWith("/")) { 226 siteRoot += "/"; 227 } 228 Collection<Count> result = new ArrayList<Count>(); 229 for (Count value : folderFacets) { 230 if (value.getName().startsWith(siteRoot) && (value.getName().length() > siteRoot.length())) { 231 if (m_selectedFolders.isEmpty()) { 232 result.add(value); 233 } else { 234 for (String folder : m_selectedFolders) { 235 if (value.getName().startsWith(folder)) { 236 result.add(value); 237 break; 238 } 239 } 240 } 241 } 242 } 243 return result; 244 } 245 246 /** 247 * Returns the label for the given category.<p> 248 * 249 * @param categoryPath the category 250 * 251 * @return the label 252 */ 253 private String getCategoryLabel(String categoryPath) { 254 255 CmsObject cms = A_CmsUI.getCmsObject(); 256 String result = ""; 257 if (CmsStringUtil.isEmptyOrWhitespaceOnly(categoryPath)) { 258 return result; 259 } 260 Locale locale = UI.getCurrent().getLocale(); 261 CmsCategoryService catService = CmsCategoryService.getInstance(); 262 263 try { 264 if (m_useFullPathCategories) { 265 //cut last slash 266 categoryPath = categoryPath.substring(0, categoryPath.length() - 1); 267 268 String currentPath = ""; 269 boolean isFirst = true; 270 for (String part : categoryPath.split("/")) { 271 currentPath += part + "/"; 272 CmsCategory cat = catService.localizeCategory( 273 cms, 274 catService.readCategory(cms, currentPath, "/"), 275 locale); 276 if (!isFirst) { 277 result += " / "; 278 } else { 279 isFirst = false; 280 } 281 result += cat.getTitle(); 282 } 283 284 } else { 285 286 CmsCategory cat = catService.localizeCategory( 287 cms, 288 catService.readCategory(cms, categoryPath, "/"), 289 locale); 290 result = cat.getTitle(); 291 } 292 } catch (Exception e) { 293 LOG.error("Error reading category " + categoryPath + ".", e); 294 } 295 return result; 296 } 297 298 /** 299 * Returns the label for the given folder.<p> 300 * 301 * @param path The folder path 302 * 303 * @return the label 304 */ 305 private String getFolderLabel(String path) { 306 307 CmsObject cms = A_CmsUI.getCmsObject(); 308 return cms.getRequestContext().removeSiteRoot(path); 309 } 310 311 /** 312 * Prepares the category facets for the given search result.<p> 313 * 314 * @param solrResultList the search result list 315 * @param resultWrapper the result wrapper 316 * 317 * @return the category facets component 318 */ 319 private Component prepareCategoryFacets(CmsSolrResultList solrResultList, CmsSearchResultWrapper resultWrapper) { 320 321 FacetField categoryFacets = solrResultList.getFacetField(CmsListManager.FIELD_CATEGORIES); 322 I_CmsSearchControllerFacetField facetController = resultWrapper.getController().getFieldFacets().getFieldFacetController().get( 323 CmsListManager.FIELD_CATEGORIES); 324 if ((categoryFacets != null) && (categoryFacets.getValueCount() > 0)) { 325 VerticalLayout catLayout = new VerticalLayout(); 326 for (final Count value : categoryFacets.getValues()) { 327 Button cat = new Button(getCategoryLabel(value.getName()) + " (" + value.getCount() + ")"); 328 cat.addStyleName(ValoTheme.BUTTON_TINY); 329 cat.addStyleName(ValoTheme.BUTTON_BORDERLESS); 330 Boolean selected = facetController.getState().getIsChecked().get(value.getName()); 331 if ((selected != null) && selected.booleanValue()) { 332 cat.addStyleName(SELECTED_STYLE); 333 } 334 cat.addClickListener(new ClickListener() { 335 336 private static final long serialVersionUID = 1L; 337 338 public void buttonClick(ClickEvent event) { 339 340 if (isSelected(event.getComponent())) { 341 resetFacetsAndSearch(); 342 } else { 343 selectFieldFacet(CmsListManager.FIELD_CATEGORIES, value.getName()); 344 } 345 } 346 }); 347 catLayout.addComponent(cat); 348 } 349 Panel catPanel = new Panel(CmsVaadinUtils.getMessageText(Messages.GUI_LISTMANAGER_FACET_CATEGORIES_0)); 350 catPanel.setContent(catLayout); 351 return catPanel; 352 } else { 353 return null; 354 } 355 } 356 357 /** 358 * Prepares the date facets for the given search result.<p> 359 * 360 * @param solrResultList the search result list 361 * @param resultWrapper the result wrapper 362 * 363 * @return the date facets component 364 */ 365 private Component prepareDateFacets(CmsSolrResultList solrResultList, CmsSearchResultWrapper resultWrapper) { 366 367 RangeFacet<?, ?> dateFacets = resultWrapper.getRangeFacet().get(CmsListManager.FIELD_DATE_FACET_NAME); 368 I_CmsSearchControllerFacetRange facetController = resultWrapper.getController().getRangeFacets().getRangeFacetController().get( 369 CmsListManager.FIELD_DATE_FACET_NAME); 370 if ((dateFacets != null) && (dateFacets.getCounts().size() > 0)) { 371 GridLayout dateLayout = new GridLayout(); 372 dateLayout.setWidth("100%"); 373 dateLayout.setColumns(6); 374 String currentYear = null; 375 int row = -2; 376 for (final RangeFacet.Count value : dateFacets.getCounts()) { 377 String[] dateParts = value.getValue().split("-"); 378 if (!dateParts[0].equals(currentYear)) { 379 row += 2; 380 dateLayout.setRows(row + 2); 381 currentYear = dateParts[0]; 382 Label year = new Label(currentYear); 383 year.addStyleName(OpenCmsTheme.PADDING_HORIZONTAL); 384 dateLayout.addComponent(year, 0, row, 5, row); 385 row++; 386 } 387 int month = Integer.parseInt(dateParts[1]) - 1; 388 389 Button date = new Button(CmsListManager.MONTHS[month] + " (" + value.getCount() + ")"); 390 date.addStyleName(ValoTheme.BUTTON_TINY); 391 date.addStyleName(ValoTheme.BUTTON_BORDERLESS); 392 Boolean selected = facetController.getState().getIsChecked().get(value.getValue()); 393 if ((selected != null) && selected.booleanValue()) { 394 date.addStyleName(SELECTED_STYLE); 395 } 396 date.addClickListener(new ClickListener() { 397 398 private static final long serialVersionUID = 1L; 399 400 public void buttonClick(ClickEvent event) { 401 402 if (isSelected(event.getComponent())) { 403 resetFacetsAndSearch(); 404 } else { 405 selectRangeFacet(CmsListManager.FIELD_DATE_FACET_NAME, value.getValue()); 406 } 407 } 408 }); 409 int targetColumn; 410 int targetRow; 411 if (month < 6) { 412 targetColumn = month; 413 targetRow = row; 414 } else { 415 targetColumn = month - 6; 416 targetRow = row + 1; 417 dateLayout.setRows(row + 2); 418 } 419 dateLayout.addComponent(date, targetColumn, targetRow); 420 } 421 Panel datePanel = new Panel(CmsVaadinUtils.getMessageText(Messages.GUI_LISTMANAGER_FACET_DATE_0)); 422 datePanel.setContent(dateLayout); 423 return datePanel; 424 } else { 425 return null; 426 } 427 } 428 429 /** 430 * Prepares the folder facets for the given search result.<p> 431 * 432 * @param solrResultList the search result list 433 * @param resultWrapper the result wrapper 434 * 435 * @return the folder facets component 436 */ 437 private Component prepareFolderFacets(CmsSolrResultList solrResultList, CmsSearchResultWrapper resultWrapper) { 438 439 FacetField folderFacets = solrResultList.getFacetField(CmsListManager.FIELD_PARENT_FOLDERS); 440 I_CmsSearchControllerFacetField facetController = resultWrapper.getController().getFieldFacets().getFieldFacetController().get( 441 CmsListManager.FIELD_PARENT_FOLDERS); 442 if ((folderFacets != null) && (folderFacets.getValueCount() > 0)) { 443 VerticalLayout folderLayout = new VerticalLayout(); 444 for (final Count value : filterFolderFacets(folderFacets.getValues())) { 445 Button folder = new Button(getFolderLabel(value.getName()) + " (" + value.getCount() + ")"); 446 folder.addStyleName(ValoTheme.BUTTON_TINY); 447 folder.addStyleName(ValoTheme.BUTTON_BORDERLESS); 448 Boolean selected = facetController.getState().getIsChecked().get(value.getName()); 449 if ((selected != null) && selected.booleanValue()) { 450 folder.addStyleName(SELECTED_STYLE); 451 } 452 folder.addClickListener(new ClickListener() { 453 454 private static final long serialVersionUID = 1L; 455 456 public void buttonClick(ClickEvent event) { 457 458 if (isSelected(event.getComponent())) { 459 resetFacetsAndSearch(); 460 } else { 461 selectFieldFacet(CmsListManager.FIELD_PARENT_FOLDERS, value.getName()); 462 } 463 } 464 }); 465 folderLayout.addComponent(folder); 466 } 467 Panel folderPanel = new Panel(CmsVaadinUtils.getMessageText(Messages.GUI_LISTMANAGER_FACET_FOLDERS_0)); 468 folderPanel.setContent(folderLayout); 469 return folderPanel; 470 } else { 471 return null; 472 } 473 } 474 475}