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}