001/*
002 *  Copyright 2014 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.cms.search.ui.model.impl;
017
018import java.util.ArrayList;
019import java.util.LinkedHashMap;
020import java.util.LinkedList;
021import java.util.List;
022import java.util.Map;
023import java.util.Optional;
024import java.util.Set;
025import java.util.stream.Collectors;
026
027import org.apache.avalon.framework.component.ComponentException;
028import org.apache.avalon.framework.configuration.Configuration;
029import org.apache.avalon.framework.configuration.ConfigurationException;
030import org.apache.avalon.framework.configuration.DefaultConfiguration;
031import org.apache.avalon.framework.configuration.MutableConfiguration;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.commons.lang3.tuple.ImmutablePair;
035import org.apache.commons.lang3.tuple.Pair;
036
037import org.ametys.cms.contenttype.ContentTypesHelper;
038import org.ametys.cms.contenttype.MetadataType;
039import org.ametys.cms.search.query.AndQuery;
040import org.ametys.cms.search.query.OrQuery;
041import org.ametys.cms.search.query.Query;
042import org.ametys.cms.search.query.Query.Operator;
043import org.ametys.cms.search.ui.model.SearchUICriterion;
044import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
045
046/**
047 * Aggregate multiple indexing field as a unique search criteria.
048 * The resulting query is an OR query on all the terms.
049 */
050public class IndexingFieldAggregatorSearchUICriterion extends AbstractSearchUICriterion
051{
052    /** Prefix for id of indexing field aggregator search criteria */
053    public static final String SEARCH_CRITERIA_METADATA_AGGREGATOR_PREFIX = "metadata-aggregator-";
054    
055    /** ComponentManager for aggregated {@link SearchUICriterion}s. */
056    protected ThreadSafeComponentManager<SearchUICriterion> _fieldCriteriaManager;
057    
058    /** The map of aggregated criteria */
059    protected Map<String, SearchUICriterion> _fieldCriteriaMap;
060    
061    /** The helper for convenient methods on content types */
062    protected ContentTypesHelper _contentTypesHelper;
063    
064    /** The type of the aggregated fields */
065    protected MetadataType _type;
066
067    @Override
068    public void service(ServiceManager manager) throws ServiceException
069    {
070        super.service(manager);
071        _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
072    }
073    
074    @Override
075    public void configure(Configuration configuration) throws ConfigurationException
076    {
077        try
078        {
079            super.configure(configuration);
080            
081            _fieldCriteriaManager = new ThreadSafeComponentManager<>();
082            _fieldCriteriaManager.setLogger(_logger);
083            _fieldCriteriaManager.contextualize(_context);
084            _fieldCriteriaManager.service(_manager);
085            
086            MutableConfiguration baseConf = new DefaultConfiguration(configuration);
087            baseConf.removeChild(baseConf.getChild("field"));
088            
089            Configuration fieldsConf = configuration.getChild("fields");
090            Configuration[] fieldConfs = fieldsConf.getChildren("field");
091            
092            List<Pair<String, Configuration>> fieldSearchUICriterionToLookup = _fieldSearchUICriterionToLookup(fieldConfs, baseConf);
093            
094            _initializeFieldCriteria(fieldSearchUICriterionToLookup, fieldsConf);
095            
096            _configureWidgetAndWidgetParameters(configuration);
097        }
098        catch (ConfigurationException e)
099        {
100            throw e;
101        }
102        catch (Exception e)
103        {
104            throw new ConfigurationException("Unable to create local component manager.", configuration, e);
105        }
106    }
107    
108    private List<Pair<String, Configuration>>  _fieldSearchUICriterionToLookup(Configuration[] fieldConfs, MutableConfiguration baseConf) throws ConfigurationException
109    {
110        List<Pair<String, Configuration>> fieldSearchUICriterionToLookup = new ArrayList<>();
111        
112        for (Configuration fieldConf : fieldConfs)
113        {
114            String path = fieldConf.getAttribute("path");
115            
116            MutableConfiguration indexingFieldConf = new DefaultConfiguration(baseConf);
117            DefaultConfiguration newFieldConf = new DefaultConfiguration(fieldConf);
118            indexingFieldConf.addChild(newFieldConf);
119            newFieldConf.setAttribute("path", path);
120            
121            String role = path;
122            _fieldCriteriaManager.addComponent("cms", null, role, IndexingFieldSearchUICriterion.class, indexingFieldConf);
123            
124            fieldSearchUICriterionToLookup.add(ImmutablePair.of(role, fieldConf));
125        }
126        
127        return fieldSearchUICriterionToLookup;
128    }
129    
130    private void _initializeFieldCriteria(List<Pair<String, Configuration>> fieldSearchUICriterionToLookup, Configuration fieldsConf) throws Exception
131    {
132        String typeAttr = fieldsConf.getAttribute("type", null);
133        
134        _fieldCriteriaManager.initialize();
135        
136        _fieldCriteriaMap = new LinkedHashMap<>();
137        _type = Optional.ofNullable(typeAttr)
138                        .map(String::toUpperCase)
139                        .map(MetadataType::valueOf)
140                        .orElse(null);
141        for (Pair<String, Configuration> pair : fieldSearchUICriterionToLookup)
142        {
143            String role = pair.getLeft();
144            SearchUICriterion criterion;
145            try
146            {
147                criterion = _fieldCriteriaManager.lookup(role);
148            }
149            catch (ComponentException e)
150            {
151                throw new ConfigurationException("Impossible to lookup the search criterion of role: " + role, e);
152            }
153            
154            MetadataType criterionType = criterion.getType();
155            if (_type == null)
156            {
157                _type = criterionType;
158            }
159            else if (_type != criterionType)
160            {
161                String location = pair.getRight().getLocation();
162                throw new ConfigurationException("Search criterion of role '" + role + "' is of type '" + criterionType + "' but should be of type '" + _type + "' at " + location + ".");
163            }
164            _fieldCriteriaMap.put(criterion.getId(), criterion);
165        }
166    }
167    
168    private void _configureWidgetAndWidgetParameters(Configuration configuration) throws ConfigurationException
169    {
170        setWidget(configureWidget(configuration, null, _type));
171        
172        String commonContentTypeId = null; 
173        if (_type == MetadataType.CONTENT || _type == MetadataType.SUB_CONTENT)
174        {
175            Set<String> contentTypeIds = _fieldCriteriaMap.values().stream()
176                                                          .map(SearchUICriterion::getContentTypeId)
177                                                          .collect(Collectors.toSet());
178            Set<String> commonContentTypeIds = _contentTypesHelper.getCommonAncestors(contentTypeIds);
179            
180            if (commonContentTypeIds.size() == 1)
181            {
182                commonContentTypeId = commonContentTypeIds.iterator().next();
183            }
184        }
185        setWidgetParameters(configureWidgetParameters(configuration, null, _type, commonContentTypeId));
186    }
187    
188    @Override
189    protected void configureId(Configuration configuration) throws ConfigurationException
190    {
191        String customRef = configuration.getAttribute("custom-ref");
192        setId(SEARCH_CRITERIA_METADATA_AGGREGATOR_PREFIX + customRef);
193    }
194    
195    @Override
196    public MetadataType getType()
197    {
198        return _type;
199    }
200    
201    @Override
202    public Query getQuery(Object value, Operator customOperator, Map<String, Object> allValues, String language, Map<String, Object> contextualParameters)
203    {
204        List<Query> fieldQueries = new LinkedList<>();
205        
206        for (String role : _fieldCriteriaMap.keySet())
207        {
208            SearchUICriterion searchCriteria = _fieldCriteriaMap.get(role);
209            Query query = searchCriteria.getQuery(value, customOperator, allValues, language, contextualParameters);
210            
211            if (query != null)
212            {
213                fieldQueries.add(query);
214            }
215        }
216        
217        if (fieldQueries.isEmpty())
218        {
219            return null;
220        }
221        else
222        {
223            return (customOperator != null && Operator.NE.equals(customOperator)) ? new AndQuery(fieldQueries) : new OrQuery(fieldQueries);
224        }
225    }
226    
227    @Override
228    public void dispose()
229    {
230        super.dispose();
231        _fieldCriteriaManager.dispose();
232        _fieldCriteriaManager = null;
233    }
234    
235    @Override
236    public String getFieldId()
237    {
238        return getId();
239    }
240    
241    @Override
242    public boolean isSortable()
243    {
244        // Not destined to be displayed.
245        return false;
246    }
247    
248    @Override
249    public Operator getOperator()
250    {
251        return null;
252    }
253    
254}