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.cms.content.referencetable.search;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.LinkedHashMap;
022import java.util.List;
023import java.util.Map;
024import java.util.Optional;
025import java.util.Set;
026
027import org.apache.avalon.framework.configuration.Configuration;
028import org.apache.avalon.framework.configuration.DefaultConfiguration;
029import org.apache.avalon.framework.context.Context;
030import org.apache.avalon.framework.context.ContextException;
031import org.apache.avalon.framework.context.Contextualizable;
032import org.apache.avalon.framework.service.ServiceException;
033import org.apache.avalon.framework.service.ServiceManager;
034import org.apache.avalon.framework.service.Serviceable;
035
036import org.ametys.cms.content.ContentHelper;
037import org.ametys.cms.contenttype.ContentType;
038import org.ametys.cms.repository.Content;
039import org.ametys.cms.search.model.SearchCriterionHelper;
040import org.ametys.cms.search.model.SystemPropertyExtensionPoint;
041import org.ametys.cms.search.query.Query.Operator;
042import org.ametys.cms.search.ui.model.AbstractSearchUIModel;
043import org.ametys.cms.search.ui.model.SearchUIColumn;
044import org.ametys.cms.search.ui.model.SearchUIColumnHelper;
045import org.ametys.cms.search.ui.model.SearchUICriterion;
046import org.ametys.cms.search.ui.model.SearchUIModel;
047import org.ametys.cms.search.ui.model.impl.IndexingFieldSearchUICriterion;
048import org.ametys.cms.search.ui.model.impl.SystemSearchUICriterion;
049import org.ametys.plugins.repository.AmetysObjectResolver;
050import org.ametys.runtime.model.ModelHelper;
051import org.ametys.runtime.model.ModelItem;
052import org.ametys.runtime.model.View;
053import org.ametys.runtime.model.ViewHelper;
054import org.ametys.runtime.model.ViewItemAccessor;
055import org.ametys.runtime.model.ViewItemContainer;
056import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
057
058/**
059 * Implementation of {@link SearchUIModel} for contents referencing the same content's values for a given attribute.
060 */
061public class ReferencingContentsWithSameValuesSearchUIModel extends AbstractSearchUIModel implements Serviceable, Contextualizable
062{
063    private static final String __CONTENT_ID_CONTEXTUAL_PARAM_NAME = "contentId";
064    private static final String __REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME = "referenceField";
065    
066    /** ComponentManager for {@link SearchUICriterion}s. */
067    protected ThreadSafeComponentManager<SearchUICriterion> _searchCriterionManager;
068    
069    private Context _context;
070    private ServiceManager _manager;
071    private AmetysObjectResolver _resolver;
072    private ContentHelper _contentHelper;
073    private SystemPropertyExtensionPoint _systemPropertyExtensionPoint;
074    private SearchCriterionHelper _searchCriterionHelper;
075    
076    public void contextualize(Context context) throws ContextException
077    {
078        _context = context;
079    }
080
081    public void service(ServiceManager manager) throws ServiceException
082    {
083        _manager = manager;
084        
085        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
086        _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE);
087        _systemPropertyExtensionPoint = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE);
088        _searchCriterionHelper = (SearchCriterionHelper) manager.lookup(SearchCriterionHelper.ROLE);
089    }
090    
091    @Override
092    public Set<String> getContentTypes(Map<String, Object> contextualParameters)
093    {
094        if (contextualParameters != null && contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME)  && contextualParameters.containsKey(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME))
095        {
096            // Get the content type which has declared the attribute
097            String contentId = (String) contextualParameters.get(__CONTENT_ID_CONTEXTUAL_PARAM_NAME);
098            Content content = _resolver.resolveById(contentId);
099            
100            String referenceAttributePath = (String) contextualParameters.get(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME);
101            if (content.hasDefinition(referenceAttributePath))
102            {
103                ModelItem modelItem = content.getDefinition(referenceAttributePath);
104                return Set.of(modelItem.getModel().getId());
105            }
106        }
107        
108        return Set.of();
109    }
110    
111    @Override
112    public Set<String> getExcludedContentTypes(Map<String, Object> contextualParameters)
113    {
114        return Collections.emptySet();
115    }
116    
117    @Override
118    public Map<String, SearchUICriterion> getCriteria(Map<String, Object> contextualParameters)
119    {
120        List<SearchUICriterion> criteria = new ArrayList<>();
121        
122        if (contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME))
123        {
124            try
125            {
126                _searchCriterionManager = new ThreadSafeComponentManager<>();
127                _searchCriterionManager.setLogger(getLogger());
128                _searchCriterionManager.contextualize(_context);
129                _searchCriterionManager.service(_manager);
130                
131                List<String> roles = new ArrayList<>();
132                
133                String contentId = (String) contextualParameters.get(__CONTENT_ID_CONTEXTUAL_PARAM_NAME);
134                
135                Content content = _resolver.resolveById(contentId);
136                
137                Set<String> setWithOnlyFirstCTypeId = Set.of(content.getTypes()[0]);
138                
139                Configuration conf = _searchCriterionHelper.getIndexingFieldCriteriaConfiguration(this, Optional.empty(), setWithOnlyFirstCTypeId, Content.ATTRIBUTE_TITLE, Optional.of(Operator.SEARCH), Optional.empty());
140                _searchCriterionManager.addComponent("cms", null, Content.ATTRIBUTE_TITLE, IndexingFieldSearchUICriterion.class, conf);
141                roles.add("title");
142                
143                conf = _searchCriterionHelper.getSystemCriteriaConfiguration(this, Optional.empty(), setWithOnlyFirstCTypeId, "full", Optional.empty());
144                _searchCriterionManager.addComponent("cms", null, "full", SystemSearchUICriterion.class, conf);
145                roles.add("full");
146                
147                conf = _searchCriterionHelper.getSystemCriteriaConfiguration(this, Optional.empty(), setWithOnlyFirstCTypeId, "workflowStep", Optional.empty());
148                _searchCriterionManager.addComponent("cms", null, "workflowStep", SystemSearchUICriterion.class, conf);
149                roles.add("workflowStep");
150                
151                if (contextualParameters.containsKey(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME) && contextualParameters.containsKey("referenceValues"))
152                {
153                    String referenceMetadataPath = ((String) contextualParameters.get(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME)).replaceAll("\\.", "/");
154                    
155                    if (content.hasDefinition(referenceMetadataPath))
156                    {
157                        ModelItem modelItem = content.getDefinition(referenceMetadataPath);
158                        @SuppressWarnings("unchecked")
159                        List<String> referenceAttributeValues = (List<String>) contextualParameters.get("referenceValues");
160                        
161                        DefaultConfiguration referenceFieldConf = new DefaultConfiguration("criteria");
162                        // Default value(s)
163                        for (String referenceAttributeValue : referenceAttributeValues)
164                        {
165                            DefaultConfiguration defaultValueConf = new DefaultConfiguration("default-value");
166                            defaultValueConf.setValue(referenceAttributeValue);
167                            referenceFieldConf.addChild(defaultValueConf);
168                        }
169                        // AND query on multiple entries
170                        DefaultConfiguration multipleOperandConf = new DefaultConfiguration("multiple-operand");
171                        multipleOperandConf.setValue("and");
172                        referenceFieldConf.addChild(multipleOperandConf);
173                        // Hidden
174                        referenceFieldConf.setAttribute("hidden", "true");
175                        
176                        conf = _searchCriterionHelper.getIndexingFieldCriteriaConfiguration(this, Optional.of(referenceFieldConf), Set.of(modelItem.getModel().getId()), referenceMetadataPath, Optional.empty(), Optional.empty());
177                        _searchCriterionManager.addComponent("cms", null, "referenceField", IndexingFieldSearchUICriterion.class, conf);
178                        roles.add("referenceField");
179                    }
180                }
181                
182                _searchCriterionManager.initialize();
183                
184                // Lookup.
185                for (String role : roles)
186                {
187                    criteria.add(_searchCriterionManager.lookup(role));
188                }
189                
190                setCriteria(criteria);
191                
192                // Dispose
193                _searchCriterionManager.dispose();
194                _searchCriterionManager = null;
195            }
196            catch (Exception e)
197            {
198                getLogger().error("Error getting the search criteria.", e);
199                throw new IllegalStateException("Error getting the search criteria.", e);
200            }
201        }
202        else
203        {
204            setCriteria(criteria);
205        }
206        
207        return super.getCriteria(contextualParameters);
208    }
209
210    @Override
211    public Map<String, SearchUICriterion> getAdvancedCriteria(Map<String, Object> contextualParameters)
212    {
213        return Collections.emptyMap();
214    }
215    
216    @Override
217    public Map<String, SearchUICriterion> getFacetedCriteria(Map<String, Object> contextualParameters)
218    {
219        Map<String, SearchUICriterion> criteria = new LinkedHashMap<>();
220        
221        if (contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME))
222        {
223            try
224            {
225                _searchCriterionManager = new ThreadSafeComponentManager<>();
226                _searchCriterionManager.setLogger(getLogger());
227                _searchCriterionManager.contextualize(_context);
228                _searchCriterionManager.service(_manager);
229                
230                List<String> roles = new ArrayList<>();
231                
232                String contentId = (String) contextualParameters.get(__CONTENT_ID_CONTEXTUAL_PARAM_NAME);
233                
234                Content content = _resolver.resolveById(contentId);
235                
236                Set<String> setWithOnlyFirstCTypeId = Set.of(content.getTypes()[0]);
237                
238                for (String propertyId : new String[] {"contentTypes", "workflowStep", "contributor", "creator"})
239                {
240                    Configuration conf = _searchCriterionHelper.getSystemCriteriaConfiguration(this, Optional.empty(), setWithOnlyFirstCTypeId, propertyId, Optional.empty());
241                    _searchCriterionManager.addComponent("cms", null, propertyId, SystemSearchUICriterion.class, conf);
242                    roles.add(propertyId);
243                }
244                
245                _searchCriterionManager.initialize();
246                
247                // Lookup.
248                for (String role : roles)
249                {
250                    criteria.put(role, _searchCriterionManager.lookup(role));
251                }
252                
253                // Dispose
254                _searchCriterionManager.dispose();
255                _searchCriterionManager = null;
256            }
257            catch (Exception e)
258            {
259                getLogger().error("Error getting the facet criteria.", e);
260                throw new IllegalStateException("Error getting the facet criteria.", e);
261            }
262        }
263        
264        return Collections.unmodifiableMap(criteria);
265    }
266    
267    @Override
268    public ViewItemContainer getResultItems(Map<String, Object> contextualParameters)
269    {
270        ViewItemContainer resultItems = new View();
271        
272        if (contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME))
273        {
274            String contentId = (String) contextualParameters.get(__CONTENT_ID_CONTEXTUAL_PARAM_NAME);
275            Content content = _resolver.resolveById(contentId);
276            
277            // Add a column for the title
278            ModelItem titleDefinition = content.getDefinition(Content.ATTRIBUTE_TITLE);
279            SearchUIColumn titleColumn = SearchUIColumnHelper.createModelItemColumn(titleDefinition);
280            titleColumn.setAllowSortOnMultipleJoin(allowSortOnMultipleJoin());
281            resultItems.addViewItem(titleColumn);
282            
283            // Add a column for the referenced field
284            if (contextualParameters.containsKey(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME))
285            {
286                Collection<ContentType> contentTypes = _contentHelper.getContentTypes(content);
287
288                String referenceModelItemPath = ((String) contextualParameters.get(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME)).replaceAll("\\.", "/");
289                int lastIndexOfItemPathSeparator = referenceModelItemPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR);
290                String referencedModelItemName = lastIndexOfItemPathSeparator > -1 ? referenceModelItemPath.substring(lastIndexOfItemPathSeparator + ModelItem.ITEM_PATH_SEPARATOR.length()) : referenceModelItemPath;
291
292                // Get or create parent of the referenced field
293                ViewItemAccessor parent = resultItems;
294                if (lastIndexOfItemPathSeparator > -1)
295                {
296                    String parentPath = referenceModelItemPath.substring(0, lastIndexOfItemPathSeparator);
297                    parent = (ViewItemAccessor) ViewHelper.addViewItem(parentPath, resultItems, contentTypes.toArray(new ContentType[contentTypes.size()]));
298                }
299                
300                // Create the column for the referenced model item
301                ModelItem referencedModelItem = _systemPropertyExtensionPoint.hasExtension(referencedModelItemName)
302                        ? _systemPropertyExtensionPoint.getExtension(referencedModelItemName)
303                        : ModelHelper.getModelItem(referencedModelItemName, contentTypes);
304                SearchUIColumn referencedItemColumn = SearchUIColumnHelper.createModelItemColumn(referencedModelItem);
305                referencedItemColumn.setAllowSortOnMultipleJoin(allowSortOnMultipleJoin());
306
307                // Add the column to its parent
308                parent.addViewItem(referencedItemColumn);
309            }
310            
311            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("contributor")));
312            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("lastModified")));
313            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("contentTypes")));
314            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("contentLanguage")));
315            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("workflowStep")));
316        }
317
318        setResultItems(resultItems);
319        return super.getResultItems(contextualParameters);
320    }
321}