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 simple contents.
045 * The search tool model automatically declares simple first level metadatas as criteria and columns.
046 */
047public class SimpleContentSearchUIModel 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        MetadataSet metadataSet = cType.getMetadataSetForEdition("main");
166        for (AbstractMetadataSetElement subMetadataSetElement : metadataSet.getElements())
167        {
168            // Get only simple metadata (ignore composites and repeaters)
169            if (subMetadataSetElement instanceof MetadataDefinitionReference)
170            {
171                String metadataName = ((MetadataDefinitionReference) subMetadataSetElement).getMetadataName();
172                MetadataDefinition metadataDefinition = cType.getMetadataDefinition(metadataName);
173                
174                Operator operator = metadataDefinition.getType().equals(MetadataType.STRING) ? Operator.SEARCH : Operator.EQ;
175                
176                addMetadataCriteriaComponent(cType, metadataName, operator);
177            }
178        }
179        
180        addSystemCriteriaComponent(cType, "contributor");
181        addSystemCriteriaComponent(cType, "contentLanguage");
182    }
183    
184    /**
185     * Add a metadata criteria component to the manager. 
186     * @param contentType the simple content type.
187     * @param metadataName the metadata name.
188     * @param operator the criterion operator.
189     * @throws ConfigurationException if a configuration error occurs.
190     * @throws ComponentException if a component cannot be initialized.
191     */
192    protected void addMetadataCriteriaComponent(ContentType contentType, String metadataName, Operator operator) throws ConfigurationException, ComponentException
193    {
194        Configuration conf = getIndexingFieldCriteriaConfiguration(contentType.getId(), metadataName, operator, null);
195        _searchCriteriaManager.addComponent("cms", null, metadataName, IndexingFieldSearchUICriterion.class, conf);
196        _searchCriteriaRoles.add(metadataName);
197    }
198    
199    /**
200     * Add a system criteria component to the manager.
201     * @param contentType the simple content type.
202     * @param property the system property.
203     * @throws ConfigurationException if a configuration error occurs.
204     * @throws ComponentException if a component cannot be initialized.
205     */
206    protected void addSystemCriteriaComponent(ContentType contentType, String property) throws ConfigurationException, ComponentException
207    {
208        DefaultConfiguration conf = (DefaultConfiguration) getSystemCriteriaConfiguration(contentType.getId(), property, null);
209        
210        if (property.equals("contentLanguage"))
211        {
212            // FIXME Is this configuration should be provided by Language system property itself ?
213            // FIXME For now the simple contents are only created for language 'fr'
214            DefaultConfiguration widgetConf = new DefaultConfiguration("widget");
215            widgetConf.setValue("edition.select-language");
216            conf.addChild(widgetConf);
217            
218            DefaultConfiguration defaultConf = new DefaultConfiguration("default-value");
219            defaultConf.setValue("CURRENT");
220            conf.addChild(defaultConf);
221            
222            DefaultConfiguration validConf = new DefaultConfiguration("validation");
223            DefaultConfiguration mandatoryConf = new DefaultConfiguration("mandatory");
224            mandatoryConf.setValue(true);
225            validConf.addChild(mandatoryConf);
226            conf.addChild(validConf);
227        }
228        
229        _searchCriteriaManager.addComponent("cms", null, property, SystemSearchUICriterion.class, conf);
230        _searchCriteriaRoles.add(property);
231    }
232    
233    /**
234     * Lookup all the criteria.
235     * @param cType the simple content type.
236     * @return the search criteria list.
237     * @throws ComponentException if a component cannot be looked up.
238     */
239    protected List<SearchUICriterion> getSearchUICriteria(ContentType cType) throws ComponentException
240    {
241        List<SearchUICriterion> criteria = new ArrayList<>();
242        
243        for (String role : _searchCriteriaRoles)
244        {
245            SearchUICriterion criterion = _searchCriteriaManager.lookup(role);
246            criteria.add(criterion);
247        }
248        
249        return criteria;
250    }
251    
252    /**
253     * Add column components to the manager.
254     * @param cType the simple content type.
255     * @throws ConfigurationException if a configuration error occurs.
256     * @throws ComponentException if a component cannot be initialized.
257     */
258    protected void addColumnComponents(ContentType cType) throws ConfigurationException, ComponentException
259    {
260        MetadataSet metadataSet = cType.getMetadataSetForEdition("main");
261        for (AbstractMetadataSetElement subMetadataSetElement : metadataSet.getElements())
262        {
263            // Get only simple metadata (ignore composites and repeaters)
264            if (subMetadataSetElement instanceof MetadataDefinitionReference)
265            {
266                String metadataName = ((MetadataDefinitionReference) subMetadataSetElement).getMetadataName();
267                
268                addMetadataColumnComponent(cType, metadataName);
269            }
270        }
271        
272        addSystemColumnComponent(cType, "contributor");
273        addSystemColumnComponent(cType, "lastModified");
274        addSystemColumnComponent(cType, "contentLanguage");
275    }
276    
277    /**
278     * Add a metadata column component to the manager. 
279     * @param contentType the simple content type.
280     * @param metadataName the metadata name.
281     * @throws ConfigurationException if a configuration error occurs.
282     * @throws ComponentException if a component cannot be initialized.
283     */
284    protected void addMetadataColumnComponent(ContentType contentType, String metadataName) throws ConfigurationException, ComponentException
285    {
286        Configuration columnConf = getMetadataColumnConfiguration(contentType.getId(), metadataName);
287        _searchColumnManager.addComponent("cms", null, metadataName, MetadataSearchUIColumn.class, columnConf);
288        _searchColumnRoles.add(metadataName);
289    }
290    
291    /**
292     * Add a system column component to the manager.
293     * @param contentType the simple content type.
294     * @param property the system property.
295     * @throws ConfigurationException if a configuration error occurs.
296     * @throws ComponentException if a component cannot be initialized.
297     */
298    protected void addSystemColumnComponent(ContentType contentType, String property) throws ConfigurationException, ComponentException
299    {
300        Configuration conf = getSystemColumnConfiguration(contentType.getId(), property);
301        _searchColumnManager.addComponent("cms", null, property, SystemSearchUIColumn.class, conf);
302        _searchColumnRoles.add(property);
303    }
304    
305    /**
306     * Lookup all the columns.
307     * @param cType the simple content type.
308     * @return the search column list.
309     * @throws ComponentException if a component cannot be looked up.
310     */
311    protected List<SearchUIColumn> getColumns(ContentType cType) throws ComponentException
312    {
313        List<SearchUIColumn> columns = new ArrayList<>();
314        
315        for (String columnRole : _searchColumnRoles)
316        {
317            SearchUIColumn column = _searchColumnManager.lookup(columnRole);
318            columns.add(column);
319        }
320        
321        return columns;
322    }
323
324}