001/*
002 *  Copyright 2013 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;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023
024import org.apache.avalon.framework.component.ComponentException;
025import org.apache.avalon.framework.configuration.Configurable;
026import org.apache.avalon.framework.configuration.Configuration;
027import org.apache.avalon.framework.configuration.ConfigurationException;
028import org.apache.avalon.framework.configuration.DefaultConfiguration;
029
030import org.ametys.cms.contenttype.AbstractMetadataSetElement;
031import org.ametys.cms.contenttype.ContentType;
032import org.ametys.cms.contenttype.MetadataDefinition;
033import org.ametys.cms.contenttype.MetadataDefinitionReference;
034import org.ametys.cms.contenttype.MetadataSet;
035import org.ametys.cms.contenttype.MetadataType;
036import org.ametys.cms.search.query.Query.Operator;
037import org.ametys.cms.search.ui.model.impl.IndexingFieldSearchUICriterion;
038import org.ametys.cms.search.ui.model.impl.MetadataSearchUIColumn;
039import org.ametys.cms.search.ui.model.impl.SystemSearchUIColumn;
040import org.ametys.cms.search.ui.model.impl.SystemSearchUICriterion;
041import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
042
043/**
044 * Generic implementation of {@link SearchUIModel} for reference tables
045 * The search tool model automatically declares simple first level metadatas as criteria and columns.
046 */
047public class ReferenceTableSearchUIModel extends AbstractSearchUIModel implements Configurable
048{
049    
050    /** ComponentManager for {@link SearchUICriterion}s. */
051    protected ThreadSafeComponentManager<SearchUICriterion> _searchCriteriaManager;
052    
053    /** ComponentManager for {@link SearchUIColumn}s. */
054    protected ThreadSafeComponentManager<SearchUIColumn> _searchColumnManager;
055    
056    /** The search criteria roles. */
057    protected List<String> _searchCriteriaRoles;
058    
059    /** The search column roles. */
060    protected List<String> _searchColumnRoles;
061    
062    @Override
063    public void configure(Configuration configuration) throws ConfigurationException
064    {
065        try
066        {
067            String cTypeId = configuration.getChild("contentType").getValue(null);
068            if (cTypeId != null)
069            {
070                setContentTypes(Collections.singleton(cTypeId));
071            }
072            
073            _searchCriteriaManager = new ThreadSafeComponentManager<>();
074            _searchCriteriaManager.setLogger(getLogger());
075            _searchCriteriaManager.contextualize(_context);
076            _searchCriteriaManager.service(_manager);
077            
078            _searchColumnManager = new ThreadSafeComponentManager<>();
079            _searchColumnManager.setLogger(getLogger());
080            _searchColumnManager.contextualize(_context);
081            _searchColumnManager.service(_manager);
082        }
083        catch (Exception e)
084        {
085            throw new ConfigurationException("Unable to create local component managers.", configuration, e);
086        }
087    }
088    
089    @Override
090    public Set<String> getExcludedContentTypes(Map<String, Object> contextualParameters)
091    {
092        return Collections.emptySet();
093    }
094    
095    @Override
096    public Map<String, SearchUICriterion> getCriteria(Map<String, Object> contextualParameters)
097    {
098        try
099        {
100            if (_searchCriteria == null)
101            {
102                String cTypeId = getContentTypes(contextualParameters).iterator().next();
103                ContentType cType = _cTypeEP.getExtension(cTypeId);
104                
105                _searchCriteriaRoles = new ArrayList<>();
106                
107                addCriteriaComponents(cType);
108                _searchCriteriaManager.initialize();
109                setCriteria(getSearchUICriteria(cType));
110            }
111        }
112        catch (Exception e)
113        {
114            throw new RuntimeException("Impossible to initialize criteria components.", e);
115        }
116        
117        return _searchCriteria;
118    }
119    
120    @Override
121    public Map<String, SearchUICriterion> getFacetedCriteria(Map<String, Object> contextualParameters)
122    {
123        return Collections.emptyMap();
124    }
125    
126    @Override
127    public Map<String, SearchUICriterion> getAdvancedCriteria(Map<String, Object> contextualParameters)
128    {
129        return Collections.emptyMap();
130    }
131    
132    @Override
133    public Map<String, SearchUIColumn> getResultFields(Map<String, Object> contextualParameters)
134    {
135        try
136        {
137            if (_columns == null)
138            {
139                _searchColumnRoles = new ArrayList<>();
140                
141                String cTypeId = getContentTypes(contextualParameters).iterator().next();
142                ContentType cType = _cTypeEP.getExtension(cTypeId);
143                
144                addColumnComponents(cType);
145                _searchColumnManager.initialize();
146                setResultFields(getColumns(cType));
147            }
148        }
149        catch (Exception e)
150        {
151            throw new RuntimeException("Impossible to initialize column components.", e);
152        }
153        
154        return _columns;
155    }
156    
157    /**
158     * Add criteria components to the manager.
159     * @param cType the simple content type.
160     * @throws ConfigurationException if a configuration error occurs.
161     * @throws ComponentException if a component cannot be initialized.
162     */
163    protected void addCriteriaComponents(ContentType cType) throws ConfigurationException, ComponentException
164    {
165        addParentMetadataSystemCriterionComponent(cType);
166        
167        MetadataSet metadataSet = cType.getMetadataSetForEdition("main");
168        for (AbstractMetadataSetElement subMetadataSetElement : metadataSet.getElements())
169        {
170            // Get only simple metadata (ignore composites and repeaters)
171            if (subMetadataSetElement instanceof MetadataDefinitionReference)
172            {
173                String metadataName = ((MetadataDefinitionReference) subMetadataSetElement).getMetadataName();
174                MetadataDefinition metadataDefinition = cType.getMetadataDefinition(metadataName);
175                
176                if (metadataDefinition == null)
177                {
178                    getLogger().warn("The metadata '{}' defined in the metadata set '{}' does not seem to exist.", metadataName, metadataSet);
179                }
180                else if (_filterMetadata(metadataDefinition))
181                {
182                    Operator operator = metadataDefinition.getType().equals(MetadataType.STRING) || metadataDefinition.getType().equals(MetadataType.MULTILINGUAL_STRING) ? Operator.SEARCH : Operator.EQ;
183                    addMetadataCriterionComponent(cType, metadataName, operator);
184                    if ("title".equals(metadataName))
185                    {
186                        addLikeTitleCriterionComponent(cType);
187                    }
188                }
189            }
190        }
191        
192        addSystemCriterionComponent(cType, "contributor");
193        
194        if (!cType.isMultilingual())
195        {
196            addSystemCriterionComponent(cType, "contentLanguage");
197        }
198    }
199    
200    /**
201     * Returns true if metadata can be used as criteria and column search UI
202     * @param metadataDefinition the metadata definition
203     * @return <code>true</code>
204     */
205    protected boolean _filterMetadata(MetadataDefinition metadataDefinition)
206    {
207        MetadataType type = metadataDefinition.getType();
208        switch (type)
209        {
210            case STRING:
211            case MULTILINGUAL_STRING:
212            case DATE:
213            case DATETIME:
214            case LONG:
215            case DOUBLE:
216            case BOOLEAN:
217            case CONTENT:
218                return true;
219            case BINARY:
220            case RICH_TEXT:
221            case REFERENCE:
222            case FILE:
223            case GEOCODE:
224            case COMPOSITE:
225            case SUB_CONTENT:
226            default:
227                return false;
228        }
229    }
230    /**
231     * Add a system criterion component for the "parent" metadata
232     * @param cType the simple content type
233     * @throws ConfigurationException if a configuration error occurs.
234     * @throws ComponentException if a component cannot be initialized.
235     */
236    protected void addParentMetadataSystemCriterionComponent(ContentType cType) throws ConfigurationException, ComponentException
237    {
238        MetadataDefinition parentMetadata = cType.getParentMetadata();
239        if (parentMetadata != null)
240        {
241            addSystemCriterionComponent(cType, "parents");
242        }
243    }
244    
245    /**
246     * Add the title metadata criterion component to the manager, with a 'LIKE' operator and an hidden widget
247     * @param contentType the simple content type.
248     * @throws ConfigurationException if a configuration error occurs.
249     * @throws ComponentException if a component cannot be initialized.
250     */
251    protected void addLikeTitleCriterionComponent(ContentType contentType) throws ConfigurationException, ComponentException
252    {
253        String metadataName = "title";
254        DefaultConfiguration originalConf = new DefaultConfiguration("criteria");
255        DefaultConfiguration widgetConf = new DefaultConfiguration("widget");
256        widgetConf.setValue("edition.hidden");
257        originalConf.addChild(widgetConf);
258        Configuration conf = getIndexingFieldCriteriaConfiguration(originalConf, contentType.getId(), metadataName, Operator.LIKE, null);
259        
260        String role = metadataName + "1";
261        _searchCriteriaManager.addComponent("cms", null, role, IndexingFieldSearchUICriterion.class, conf);
262        _searchCriteriaRoles.add(role);
263    }
264    
265    /**
266     * Add a metadata criterion component to the manager. 
267     * @param contentType the simple content type.
268     * @param metadataName the metadata name.
269     * @param operator the criterion operator.
270     * @throws ConfigurationException if a configuration error occurs.
271     * @throws ComponentException if a component cannot be initialized.
272     */
273    protected void addMetadataCriterionComponent(ContentType contentType, String metadataName, Operator operator) throws ConfigurationException, ComponentException
274    {
275        Configuration conf = getIndexingFieldCriteriaConfiguration(contentType.getId(), metadataName, operator, null);
276        _searchCriteriaManager.addComponent("cms", null, metadataName, IndexingFieldSearchUICriterion.class, conf);
277        _searchCriteriaRoles.add(metadataName);
278    }
279    
280    /**
281     * Add a system criterion component to the manager.
282     * @param contentType the simple content type.
283     * @param property the system property.
284     * @throws ConfigurationException if a configuration error occurs.
285     * @throws ComponentException if a component cannot be initialized.
286     */
287    protected void addSystemCriterionComponent(ContentType contentType, String property) throws ConfigurationException, ComponentException
288    {
289        DefaultConfiguration conf = (DefaultConfiguration) getSystemCriteriaConfiguration(contentType.getId(), property, null);
290        
291        if (property.equals("contentLanguage"))
292        {
293            // FIXME Is this configuration should be provided by Language system property itself ?
294            // FIXME For now the simple contents are only created for language 'fr'
295            DefaultConfiguration widgetConf = new DefaultConfiguration("widget");
296            widgetConf.setValue("edition.select-language");
297            conf.addChild(widgetConf);
298            
299            DefaultConfiguration defaultConf = new DefaultConfiguration("default-value");
300            defaultConf.setValue("CURRENT");
301            conf.addChild(defaultConf);
302            
303            DefaultConfiguration validConf = new DefaultConfiguration("validation");
304            DefaultConfiguration mandatoryConf = new DefaultConfiguration("mandatory");
305            mandatoryConf.setValue(true);
306            validConf.addChild(mandatoryConf);
307            conf.addChild(validConf);
308        }
309        
310        _searchCriteriaManager.addComponent("cms", null, property, SystemSearchUICriterion.class, conf);
311        _searchCriteriaRoles.add(property);
312    }
313    
314    /**
315     * Lookup all the criteria.
316     * @param cType the simple content type.
317     * @return the search criteria list.
318     * @throws ComponentException if a component cannot be looked up.
319     */
320    protected List<SearchUICriterion> getSearchUICriteria(ContentType cType) throws ComponentException
321    {
322        List<SearchUICriterion> criteria = new ArrayList<>();
323        
324        for (String role : _searchCriteriaRoles)
325        {
326            SearchUICriterion criterion = _searchCriteriaManager.lookup(role);
327            criteria.add(criterion);
328        }
329        
330        return criteria;
331    }
332    
333    /**
334     * Add column components to the manager.
335     * @param cType the simple content type.
336     * @throws ConfigurationException if a configuration error occurs.
337     * @throws ComponentException if a component cannot be initialized.
338     */
339    protected void addColumnComponents(ContentType cType) throws ConfigurationException, ComponentException
340    {
341        MetadataSet metadataSet = cType.getMetadataSetForEdition("main");
342        for (AbstractMetadataSetElement subMetadataSetElement : metadataSet.getElements())
343        {
344            // Get only simple metadata (ignore composites and repeaters)
345            if (subMetadataSetElement instanceof MetadataDefinitionReference)
346            {
347                String metadataName = ((MetadataDefinitionReference) subMetadataSetElement).getMetadataName();
348                MetadataDefinition metadataDefinition = cType.getMetadataDefinition(metadataName);
349                
350                if (metadataDefinition == null)
351                {
352                    getLogger().warn("The metadata '{}' defined in the metadata set '{}' does not seem to exist.", metadataName, metadataSet);
353                }
354                else if (_filterMetadata(metadataDefinition))
355                {
356                    addMetadataColumnComponent(cType, metadataName);
357                }
358            }
359        }
360        
361        addSystemColumnComponent(cType, "contributor");
362        addSystemColumnComponent(cType, "lastModified");
363        
364        if (!cType.isMultilingual())
365        {
366            addSystemColumnComponent(cType, "contentLanguage");
367        }
368    }
369    
370    /**
371     * Add a metadata column component to the manager. 
372     * @param contentType the simple content type.
373     * @param metadataName the metadata name.
374     * @throws ConfigurationException if a configuration error occurs.
375     * @throws ComponentException if a component cannot be initialized.
376     */
377    protected void addMetadataColumnComponent(ContentType contentType, String metadataName) throws ConfigurationException, ComponentException
378    {
379        Configuration columnConf = getMetadataColumnConfiguration(contentType.getId(), metadataName);
380        _searchColumnManager.addComponent("cms", null, metadataName, MetadataSearchUIColumn.class, columnConf);
381        _searchColumnRoles.add(metadataName);
382    }
383    
384    /**
385     * Add a system column component to the manager.
386     * @param contentType the simple content type.
387     * @param property the system property.
388     * @throws ConfigurationException if a configuration error occurs.
389     * @throws ComponentException if a component cannot be initialized.
390     */
391    protected void addSystemColumnComponent(ContentType contentType, String property) throws ConfigurationException, ComponentException
392    {
393        Configuration conf = getSystemColumnConfiguration(contentType.getId(), property);
394        _searchColumnManager.addComponent("cms", null, property, SystemSearchUIColumn.class, conf);
395        _searchColumnRoles.add(property);
396    }
397    
398    /**
399     * Lookup all the columns.
400     * @param cType the simple content type.
401     * @return the search column list.
402     * @throws ComponentException if a component cannot be looked up.
403     */
404    protected List<SearchUIColumn> getColumns(ContentType cType) throws ComponentException
405    {
406        List<SearchUIColumn> columns = new ArrayList<>();
407        
408        for (String columnRole : _searchColumnRoles)
409        {
410            SearchUIColumn column = _searchColumnManager.lookup(columnRole);
411            columns.add(column);
412        }
413        
414        return columns;
415    }
416
417}