001/*
002 *  Copyright 2018 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.search.requesttime.impl;
017
018import java.util.Collection;
019import java.util.List;
020import java.util.Locale;
021import java.util.Map;
022import java.util.Set;
023import java.util.function.Function;
024import java.util.stream.Collectors;
025
026import org.apache.cocoon.environment.Request;
027import org.apache.commons.collections4.CollectionUtils;
028import org.apache.commons.lang3.ArrayUtils;
029import org.apache.commons.lang3.tuple.Pair;
030
031import org.ametys.cms.search.Sort;
032import org.ametys.cms.search.Sort.Order;
033import org.ametys.cms.search.cocoon.SearchAction;
034import org.ametys.cms.search.solr.SearcherFactory.Searcher;
035import org.ametys.web.frontoffice.search.instance.SearchServiceInstance;
036import org.ametys.web.frontoffice.search.metamodel.SortDefinition;
037import org.ametys.web.frontoffice.search.requesttime.SearchComponent;
038import org.ametys.web.frontoffice.search.requesttime.SearchComponentArguments;
039
040/**
041 * {@link SearchComponent} for specifying the sort in the search.
042 */
043public class SortSearchComponent implements SearchComponent
044{
045    @Override
046    public int priority()
047    {
048        return SEARCH_PRIORITY - 6000;
049    }
050
051    @Override
052    public boolean supports(SearchComponentArguments args)
053    {
054        return args.launchSearch();
055    }
056
057    @Override
058    public void execute(SearchComponentArguments args) throws Exception
059    {
060        Searcher searcher = args.searcher();
061        SearchServiceInstance serviceInstance = args.serviceInstance();
062        List<Pair<SortDefinition, Order>> serviceInitialSorts = serviceInstance.getInitialSorts();
063        Collection<SortDefinition> serviceProposedSorts = serviceInstance.getProposedSorts();
064        List<Pair<String, Sort.Order>> userSorts = args.userInputs().sorts();
065        
066        Map<String, SortDefinition> serviceProposedSortsById = serviceProposedSorts.stream()
067                .collect(Collectors.toMap(SortDefinition::getId, Function.identity()));
068        
069        checkValidInputs(serviceProposedSortsById, userSorts);
070        
071        setSearchLanguageAttribute(args);
072        
073        // Apply user sorts (keeping user order)
074        for (Pair<String, Sort.Order> userSort : userSorts)
075        {
076            SortDefinition sortDefinition = serviceProposedSortsById.get(userSort.getLeft());
077            Sort.Order order = userSort.getRight();
078            searcher.addSort(new Sort(sortDefinition.getField(), order));
079        }
080        
081        // Apply initial sorts if no user sort
082        if (userSorts.isEmpty())
083        {
084            for (Pair<SortDefinition, Sort.Order> initialSort : serviceInitialSorts)
085            {
086                searcher.addSort(new Sort(initialSort.getLeft().getField(), initialSort.getRight()));
087            }
088        }
089    }
090    
091    /**
092     * Checks the user inputs are valid
093     * @param serviceProposedSortsById The proposed sorts of the service instance
094     * @param userSorts The user input sorts
095     * @throws InvalidUserInputException if at least user one input is invalid
096     */
097    protected void checkValidInputs(Map<String, SortDefinition> serviceProposedSortsById,  List<Pair<String, Sort.Order>> userSorts) throws InvalidUserInputException
098    {
099        Collection<String> proposedSortIds = serviceProposedSortsById.keySet();
100        Set<String> userSortIds = userSorts
101                .stream()
102                .map(Pair::getLeft)
103                .collect(Collectors.toSet());
104        
105        if (!CollectionUtils.containsAll(proposedSortIds, userSortIds))
106        {
107            throw new InvalidUserInputException("At least one of the user input sorts is invalid because it was not declared by the service instance.");
108        }
109        
110        for (Pair<String, Sort.Order> userSort : userSorts)
111        {
112            String sortDefId = userSort.getLeft();
113            SortDefinition sortDefinition = serviceProposedSortsById.get(sortDefId);
114            Sort.Order order = userSort.getRight();
115            if (!ArrayUtils.contains(sortDefinition.orders(), order))
116            {
117                throw new InvalidUserInputException("The user input sort order '" + order + "' for sort '" + sortDefId + "' is not a valid sort order.");
118            }
119        }
120    }
121    
122    /**
123     * Sets the SEARCH_LOCALE attribute in request
124     * @param args the arguments
125     */
126    protected void setSearchLanguageAttribute(SearchComponentArguments args)
127    {
128        String currentLang = args.currentLang();
129        Request request = args.request();
130        // Need to pass this attribute for the computing of sort field to use for MultilingualStringSearchField
131        request.setAttribute(SearchAction.SEARCH_LOCALE, new Locale(currentLang));
132    }
133}