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.plugins.thesaurus.search;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024
025import org.apache.avalon.framework.configuration.Configuration;
026import org.apache.avalon.framework.configuration.DefaultConfiguration;
027import org.apache.avalon.framework.service.ServiceException;
028import org.apache.avalon.framework.service.ServiceManager;
029import org.apache.commons.lang3.ArrayUtils;
030
031import org.ametys.cms.contenttype.ContentType;
032import org.ametys.cms.contenttype.ContentTypesHelper;
033import org.ametys.cms.contenttype.MetadataDefinition;
034import org.ametys.cms.repository.Content;
035import org.ametys.cms.search.model.SystemPropertyExtensionPoint;
036import org.ametys.cms.search.query.Query.Operator;
037import org.ametys.cms.search.ui.model.AbstractSearchUIModel;
038import org.ametys.cms.search.ui.model.SearchUIColumn;
039import org.ametys.cms.search.ui.model.SearchUICriterion;
040import org.ametys.cms.search.ui.model.impl.IndexingFieldSearchUICriterion;
041import org.ametys.cms.search.ui.model.impl.MetadataSearchUIColumn;
042import org.ametys.cms.search.ui.model.impl.SystemSearchUIColumn;
043import org.ametys.cms.search.ui.model.impl.SystemSearchUICriterion;
044import org.ametys.plugins.repository.AmetysObjectResolver;
045import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
046
047/**
048 * Search model for indexed content tools
049 *
050 */
051public class IndexedContentsSearchUIModel extends AbstractSearchUIModel
052{
053    /** The content type helper. */
054    protected ContentTypesHelper _cTypeHelper;
055    
056    /** The system property extension point. */
057    protected SystemPropertyExtensionPoint _sysPropEP;
058    
059    /** ComponentManager for {@link SearchUICriterion}s. */
060    protected ThreadSafeComponentManager<SearchUICriterion> _searchCriterionManager;
061    
062    /** ComponentManager for {@link SearchUIColumn}s. */
063    protected ThreadSafeComponentManager<SearchUIColumn> _searchColumnManager;
064    
065    private AmetysObjectResolver _resolver;
066
067    @Override
068    public void service(ServiceManager manager) throws ServiceException
069    {
070        super.service(manager);
071        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
072        _cTypeHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
073        _sysPropEP = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE);
074    }
075    
076    @Override
077    public Set<String> getContentTypes(Map<String, Object> contextualParameters)
078    {
079        Set<String> searchContentTypes = new HashSet<>();
080        
081        if (contextualParameters != null && contextualParameters.containsKey("contentId") && contextualParameters.containsKey("indexField"))
082        {
083            // Find the content type where the index field is defined
084            String contentId = (String) contextualParameters.get("contentId");
085            Content content = _resolver.resolveById(contentId);
086            
087            Set<String> cTypes = new HashSet<>();
088            
089            String[] allContentTypes = ArrayUtils.addAll(content.getTypes(), content.getMixinTypes());
090            for (String id : allContentTypes)
091            {
092                cTypes.addAll(_cTypeHelper.getAncestors(id));
093                cTypes.add(id);
094            }
095            
096            String indexMetadataPath = (String) contextualParameters.get("indexField");
097            for (String cTypeId : cTypes)
098            {
099                ContentType contentType = _cTypeEP.getExtension(cTypeId);
100                MetadataDefinition metadataDef = contentType.getMetadataDefinitionByPath(indexMetadataPath);
101                if (metadataDef != null)
102                {
103                    searchContentTypes.add(cTypeId);
104                    return searchContentTypes;
105                }
106            }
107        }
108        
109        return searchContentTypes;
110    }
111    
112    @Override
113    public Set<String> getExcludedContentTypes(Map<String, Object> contextualParameters)
114    {
115        return Collections.emptySet();
116    }
117    
118    @Override
119    public Map<String, SearchUICriterion> getCriteria(Map<String, Object> contextualParameters)
120    {
121        List<SearchUICriterion> criteria = new ArrayList<>();
122        
123        if (contextualParameters.containsKey("contentId"))
124        {
125            try
126            {
127                _searchCriterionManager = new ThreadSafeComponentManager<>();
128                _searchCriterionManager.setLogger(getLogger());
129                _searchCriterionManager.contextualize(_context);
130                _searchCriterionManager.service(_manager);
131                
132                List<String> roles = new ArrayList<>();
133                
134                String contentId = (String) contextualParameters.get("contentId");
135                
136                Content content = _resolver.resolveById(contentId);
137                
138                String firstCTypeId = content.getTypes()[0];
139                
140                Configuration conf = getIndexingFieldCriteriaConfiguration(firstCTypeId, "title", Operator.SEARCH);
141                _searchCriterionManager.addComponent("cms", null, "title", IndexingFieldSearchUICriterion.class, conf);
142                roles.add("title");
143                
144                conf = getSystemCriteriaConfiguration(firstCTypeId, "fulltext");
145                _searchCriterionManager.addComponent("cms", null, "fulltext", SystemSearchUICriterion.class, conf);
146                roles.add("fulltext");
147                
148                conf = getSystemCriteriaConfiguration(firstCTypeId, "workflowStep");
149                _searchCriterionManager.addComponent("cms", null, "workflowStep", SystemSearchUICriterion.class, conf);
150                roles.add("workflowStep");
151                
152                if (contextualParameters.containsKey("indexField") && contextualParameters.containsKey("indexValues"))
153                {
154                    Set<String> cTypes = getAllTypes(content);
155                    
156                    String indexMetadataPath = ((String) contextualParameters.get("indexField")).replaceAll("\\.", "/");
157                    @SuppressWarnings("unchecked")
158                    List<String> indexMetadataValues = (List<String>) contextualParameters.get("indexValues");
159                    
160                    for (String cTypeId : cTypes)
161                    {
162                        ContentType targetContentType = _cTypeEP.getExtension(cTypeId);
163                        MetadataDefinition metadataDef = targetContentType.getMetadataDefinitionByPath(indexMetadataPath);
164                        if (metadataDef != null)
165                        {
166                            DefaultConfiguration indexFieldConf = new DefaultConfiguration("criteria");
167                            // Default value(s)
168                            for (String indexMetadataValue : indexMetadataValues)
169                            {
170                                DefaultConfiguration defaultValueConf = new DefaultConfiguration("default-value");
171                                defaultValueConf.setValue(indexMetadataValue);
172                                indexFieldConf.addChild(defaultValueConf);
173                            }
174                            // AND query on multiple terms
175                            DefaultConfiguration multipleOperandConf = new DefaultConfiguration("multiple-operand");
176                            multipleOperandConf.setValue("and");
177                            indexFieldConf.addChild(multipleOperandConf);
178                            // Hidden
179                            indexFieldConf.setAttribute("hidden", "true");
180                            
181                            conf = getIndexingFieldCriteriaConfiguration(indexFieldConf, cTypeId, indexMetadataPath, null, null);
182                            _searchCriterionManager.addComponent("cms", null, "indexField", IndexingFieldSearchUICriterion.class, conf);
183                            roles.add("indexField");
184                            break;
185                        }
186                    }
187                }
188                
189                _searchCriterionManager.initialize();
190                
191                // Lookup.
192                for (String role : roles)
193                {
194                    criteria.add(_searchCriterionManager.lookup(role));
195                }
196                
197                setCriteria(criteria);
198                
199                // Dispose
200                _searchCriterionManager.dispose();
201                _searchCriterionManager = null;
202            }
203            catch (Exception e)
204            {
205                getLogger().error("Error getting the search criteria.", e);
206                throw new IllegalStateException("Error getting the search criteria.", e);
207            }
208        }
209        else
210        {
211            setCriteria(criteria);
212        }
213        
214        return super.getCriteria(contextualParameters);
215    }
216
217    @Override
218    public Map<String, SearchUICriterion> getAdvancedCriteria(Map<String, Object> contextualParameters)
219    {
220        return Collections.emptyMap();
221    }
222    
223    @Override
224    public Map<String, SearchUICriterion> getFacetedCriteria(Map<String, Object> contextualParameters)
225    {
226        return Collections.emptyMap();
227    }
228    
229    @Override
230    public Map<String, SearchUIColumn> getResultFields(Map<String, Object> contextualParameters)
231    {
232        List<SearchUIColumn> columns = new ArrayList<>();
233        
234        try
235        {
236            _searchColumnManager = new ThreadSafeComponentManager<>();
237            _searchColumnManager.setLogger(getLogger());
238            _searchColumnManager.contextualize(_context);
239            _searchColumnManager.service(_manager);
240            
241            List<String> roles = new ArrayList<>();
242            
243            if (contextualParameters.containsKey("contentId"))
244            {
245                String contentId = (String) contextualParameters.get("contentId");
246                
247                Content content = _resolver.resolveById(contentId);
248                
249                String firstCTypeId = content.getTypes()[0];
250                
251                // Column title
252                Configuration conf = getMetadataColumnConfiguration(firstCTypeId, "title");
253                _searchColumnManager.addComponent("cms", null, "title", MetadataSearchUIColumn.class, conf);
254                roles.add("title");
255                
256                if (contextualParameters.containsKey("indexField"))
257                {
258                    Set<String> cTypes = getAllTypes(content);
259                    
260                    String indexMetadataPath = ((String) contextualParameters.get("indexField")).replaceAll("\\.", "/");
261                    
262                    for (String cTypeId : cTypes)
263                    {
264                        ContentType targetContentType = _cTypeEP.getExtension(cTypeId);
265                        MetadataDefinition metadataDef = targetContentType.getMetadataDefinitionByPath(indexMetadataPath);
266                        if (metadataDef != null)
267                        {
268                            conf = getMetadataColumnConfiguration(cTypeId, indexMetadataPath);
269                            _searchColumnManager.addComponent("cms", null, "indexField", MetadataSearchUIColumn.class, conf);
270                            roles.add("indexField");
271                            break;
272                        }
273                    }
274                }
275            }
276            
277            // System columns
278            for (String propertyName : _sysPropEP.getDisplayProperties())
279            {
280                Configuration conf = getSystemColumnConfiguration(null, propertyName);
281                _searchColumnManager.addComponent("cms", null, propertyName, SystemSearchUIColumn.class, conf);
282                roles.add(propertyName);
283            }
284            
285            _searchColumnManager.initialize();
286            
287            for (String role : roles)
288            {
289                columns.add(_searchColumnManager.lookup(role));
290            }
291            
292            _searchColumnManager.dispose();
293            _searchColumnManager = null;
294        }
295        catch (Exception e)
296        {
297            getLogger().error("Error getting the result fields.", e);
298            throw new IllegalStateException("Error getting the result fields.", e);
299        }
300        
301        setResultFields(columns);
302        
303        return super.getResultFields(contextualParameters);
304    }
305    
306    /**
307     * Get all the types and mixins and their ancestors of a Content.
308     * @param content The content.
309     * @return a Set containing all the content type and mixin IDs.
310     */
311    protected Set<String> getAllTypes(Content content)
312    {
313        Set<String> cTypes = new HashSet<>();
314        
315        for (String id : content.getTypes())
316        {
317            cTypes.addAll(_cTypeHelper.getAncestors(id));
318            cTypes.add(id);
319        }
320        for (String id : content.getMixinTypes())
321        {
322            cTypes.addAll(_cTypeHelper.getAncestors(id));
323            cTypes.add(id);
324        }
325        
326        return cTypes;
327    }
328
329}