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.List;
022import java.util.Map;
023import java.util.Set;
024
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.avalon.framework.service.Serviceable;
028import org.apache.commons.lang3.tuple.ImmutablePair;
029import org.apache.commons.lang3.tuple.Pair;
030
031import org.ametys.cms.content.ContentHelper;
032import org.ametys.cms.contenttype.ContentType;
033import org.ametys.cms.repository.Content;
034import org.ametys.cms.search.model.SearchModelCriterionDefinition;
035import org.ametys.cms.search.model.SystemPropertyExtensionPoint;
036import org.ametys.cms.search.query.Query.LogicalOperator;
037import org.ametys.cms.search.query.Query.Operator;
038import org.ametys.cms.search.ui.model.SearchUIColumn;
039import org.ametys.cms.search.ui.model.SearchUIColumnHelper;
040import org.ametys.cms.search.ui.model.SearchModelCriterionViewItem;
041import org.ametys.cms.search.ui.model.SearchUIModel;
042import org.ametys.cms.search.ui.model.SearchModelCriterionViewItemHelper;
043import org.ametys.cms.search.ui.model.impl.DefaultSearchUIModel;
044import org.ametys.plugins.repository.AmetysObjectResolver;
045import org.ametys.runtime.model.ModelHelper;
046import org.ametys.runtime.model.ModelItem;
047import org.ametys.runtime.model.View;
048import org.ametys.runtime.model.ViewHelper;
049import org.ametys.runtime.model.ViewItemAccessor;
050import org.ametys.runtime.model.ViewItemContainer;
051
052/**
053 * Implementation of {@link SearchUIModel} for contents referencing the same content's values for a given attribute.
054 */
055public class ReferencingContentsWithSameValuesSearchUIModel extends DefaultSearchUIModel implements Serviceable
056{
057    private static final String __CONTENT_ID_CONTEXTUAL_PARAM_NAME = "contentId";
058    private static final String __REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME = "referenceField";
059    
060    private AmetysObjectResolver _resolver;
061    private ContentHelper _contentHelper;
062    private SystemPropertyExtensionPoint _systemPropertyExtensionPoint;
063    private SearchModelCriterionViewItemHelper _searchModelCriterionViewItemHelper;
064    
065    public void service(ServiceManager manager) throws ServiceException
066    {
067        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
068        _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE);
069        _systemPropertyExtensionPoint = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE);
070        _searchModelCriterionViewItemHelper = (SearchModelCriterionViewItemHelper) manager.lookup(SearchModelCriterionViewItemHelper.ROLE);
071    }
072    
073    @Override
074    public Set<String> getContentTypes(Map<String, Object> contextualParameters)
075    {
076        if (contextualParameters != null && contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME)  && contextualParameters.containsKey(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME))
077        {
078            // Get the content type which has declared the attribute
079            String contentId = (String) contextualParameters.get(__CONTENT_ID_CONTEXTUAL_PARAM_NAME);
080            Content content = _resolver.resolveById(contentId);
081            
082            String referenceAttributePath = (String) contextualParameters.get(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME);
083            if (content.hasDefinition(referenceAttributePath))
084            {
085                ModelItem modelItem = content.getDefinition(referenceAttributePath);
086                return Set.of(modelItem.getModel().getId());
087            }
088        }
089        
090        return Set.of();
091    }
092    
093    @Override
094    public Set<String> getExcludedContentTypes(Map<String, Object> contextualParameters)
095    {
096        return Collections.emptySet();
097    }
098    
099    @SuppressWarnings("unchecked")
100    @Override
101    public ViewItemContainer getCriteria(Map<String, Object> contextualParameters)
102    {
103        ViewItemContainer criteria = new View();
104        
105        if (contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME))
106        {
107            String contentId = (String) contextualParameters.get(__CONTENT_ID_CONTEXTUAL_PARAM_NAME);
108            Content content = _resolver.resolveById(contentId);
109            Collection<ContentType> contentTypes = content.getModel()
110                                                          .stream()
111                                                          .map(ContentType.class::cast)
112                                                          .toList();
113            
114            SearchModelCriterionViewItem titleCriterionViewItem = _searchModelCriterionViewItemHelper.createReferencingCriterionViewItem(this, Content.ATTRIBUTE_TITLE, contentTypes);
115            SearchModelCriterionDefinition titleCriterion = (SearchModelCriterionDefinition) titleCriterionViewItem.getDefinition();
116            titleCriterion.setOperator(Operator.SEARCH);
117            criteria.addViewItem(titleCriterionViewItem);
118            
119            SearchModelCriterionViewItem fullCriterionViewItem = _searchModelCriterionViewItemHelper.createReferencingCriterionViewItem(this, "full");
120            criteria.addViewItem(fullCriterionViewItem);
121            
122            SearchModelCriterionViewItem workflowStepCriterionViewItem = _searchModelCriterionViewItemHelper.createReferencingCriterionViewItem(this, "workflowStep");
123            criteria.addViewItem(workflowStepCriterionViewItem);
124            
125            if (contextualParameters.containsKey(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME) && contextualParameters.containsKey("referenceValues"))
126            {
127                String referencePath = ((String) contextualParameters.get(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME)).replaceAll("\\.", "/");
128                
129                SearchModelCriterionViewItem referenceCriterionViewItem = _searchModelCriterionViewItemHelper.createReferencingCriterionViewItem(this, referencePath, contentTypes);
130                if (referenceCriterionViewItem != null)
131                {
132                    SearchModelCriterionDefinition referenceCriterion = (SearchModelCriterionDefinition) titleCriterionViewItem.getDefinition();
133                    
134                    // Default value(s)
135                    List<String> referenceAttributeValues = (List<String>) contextualParameters.get("referenceValues");
136                    List<Pair<String, Object>> defaultValues = new ArrayList<>();
137                    for (String referenceAttributeValue : referenceAttributeValues)
138                    {
139                        defaultValues.add(new ImmutablePair<>(null, referenceAttributeValue));
140                    }
141                    referenceCriterion.setParsedDefaultValues(defaultValues);
142                    
143                    // AND query on multiple entries
144                    referenceCriterion.setMultipleOperandOperator(LogicalOperator.AND);
145    
146                    // Hidden
147                    referenceCriterionViewItem.setHidden(true);
148                }
149            }
150        }
151        
152        setCriteria(criteria);
153        return super.getCriteria(contextualParameters);
154    }
155
156    @Override
157    public ViewItemContainer getAdvancedCriteria(Map<String, Object> contextualParameters)
158    {
159        return null;
160    }
161    
162    @Override
163    public ViewItemContainer getFacetedCriteria(Map<String, Object> contextualParameters)
164    {
165        ViewItemContainer criteria = new View();
166        
167        if (contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME))
168        {
169            for (String propertyId : new String[] {"contentTypes", "workflowStep", "contributor", "creator"})
170            {
171                SearchModelCriterionViewItem criterionViewItem = _searchModelCriterionViewItemHelper.createReferencingCriterionViewItem(this, propertyId);
172                criteria.addViewItem(criterionViewItem);
173            }
174        }
175        
176        setFacetedCriteria(criteria);
177        return super.getFacetedCriteria(contextualParameters);
178    }
179    
180    @Override
181    public ViewItemContainer getResultItems(Map<String, Object> contextualParameters)
182    {
183        ViewItemContainer resultItems = new View();
184        
185        if (contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME))
186        {
187            String contentId = (String) contextualParameters.get(__CONTENT_ID_CONTEXTUAL_PARAM_NAME);
188            Content content = _resolver.resolveById(contentId);
189            
190            // Add a column for the title
191            ModelItem titleDefinition = content.getDefinition(Content.ATTRIBUTE_TITLE);
192            SearchUIColumn titleColumn = SearchUIColumnHelper.createModelItemColumn(titleDefinition);
193            titleColumn.setAllowSortOnMultipleJoin(allowSortOnMultipleJoin());
194            resultItems.addViewItem(titleColumn);
195            
196            // Add a column for the referenced field
197            if (contextualParameters.containsKey(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME))
198            {
199                Collection<ContentType> contentTypes = _contentHelper.getContentTypes(content);
200
201                String referenceModelItemPath = ((String) contextualParameters.get(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME)).replaceAll("\\.", "/");
202                int lastIndexOfItemPathSeparator = referenceModelItemPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR);
203                String referencedModelItemName = lastIndexOfItemPathSeparator > -1 ? referenceModelItemPath.substring(lastIndexOfItemPathSeparator + ModelItem.ITEM_PATH_SEPARATOR.length()) : referenceModelItemPath;
204
205                // Get or create parent of the referenced field
206                ViewItemAccessor parent = resultItems;
207                if (lastIndexOfItemPathSeparator > -1)
208                {
209                    String parentPath = referenceModelItemPath.substring(0, lastIndexOfItemPathSeparator);
210                    parent = (ViewItemAccessor) ViewHelper.addViewItem(parentPath, resultItems, contentTypes.toArray(new ContentType[contentTypes.size()]));
211                }
212                
213                // Create the column for the referenced model item
214                ModelItem referencedModelItem = _systemPropertyExtensionPoint.hasExtension(referencedModelItemName)
215                        ? _systemPropertyExtensionPoint.getExtension(referencedModelItemName)
216                        : ModelHelper.getModelItem(referencedModelItemName, contentTypes);
217                SearchUIColumn referencedItemColumn = SearchUIColumnHelper.createModelItemColumn(referencedModelItem);
218                referencedItemColumn.setAllowSortOnMultipleJoin(allowSortOnMultipleJoin());
219
220                // Add the column to its parent
221                parent.addViewItem(referencedItemColumn);
222            }
223            
224            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("contributor")));
225            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("lastModified")));
226            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("contentTypes")));
227            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("contentLanguage")));
228            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("workflowStep")));
229        }
230
231        setResultItems(resultItems);
232        return super.getResultItems(contextualParameters);
233    }
234}