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                    criteria.addViewItem(referenceCriterionViewItem);
133                    SearchModelCriterionDefinition referenceCriterion = (SearchModelCriterionDefinition) referenceCriterionViewItem.getDefinition();
134                    referenceCriterion.setMultiple(true);
135                    
136                    // Default value(s)
137                    List<String> referenceAttributeValues = (List<String>) contextualParameters.get("referenceValues");
138                    List<Pair<String, Object>> defaultValues = new ArrayList<>();
139                    for (String referenceAttributeValue : referenceAttributeValues)
140                    {
141                        defaultValues.add(new ImmutablePair<>(null, referenceAttributeValue));
142                    }
143                    referenceCriterion.setParsedDefaultValues(defaultValues);
144                    
145                    // AND query on multiple entries
146                    referenceCriterion.setMultipleOperandOperator(LogicalOperator.AND);
147    
148                    // Hidden
149                    referenceCriterionViewItem.setHidden(true);
150                }
151            }
152        }
153        
154        setCriteria(criteria);
155        return super.getCriteria(contextualParameters);
156    }
157
158    @Override
159    public ViewItemContainer getAdvancedCriteria(Map<String, Object> contextualParameters)
160    {
161        return null;
162    }
163    
164    @Override
165    public ViewItemContainer getFacetedCriteria(Map<String, Object> contextualParameters)
166    {
167        ViewItemContainer criteria = new View();
168        
169        if (contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME))
170        {
171            for (String propertyId : new String[] {"contentTypes", "workflowStep", "contributor", "creator"})
172            {
173                SearchModelCriterionViewItem criterionViewItem = _searchModelCriterionViewItemHelper.createReferencingCriterionViewItem(this, propertyId);
174                criteria.addViewItem(criterionViewItem);
175            }
176        }
177        
178        setFacetedCriteria(criteria);
179        return super.getFacetedCriteria(contextualParameters);
180    }
181    
182    @Override
183    public ViewItemContainer getResultItems(Map<String, Object> contextualParameters)
184    {
185        ViewItemContainer resultItems = new View();
186        
187        if (contextualParameters.containsKey(__CONTENT_ID_CONTEXTUAL_PARAM_NAME))
188        {
189            String contentId = (String) contextualParameters.get(__CONTENT_ID_CONTEXTUAL_PARAM_NAME);
190            Content content = _resolver.resolveById(contentId);
191            
192            // Add a column for the title
193            ModelItem titleDefinition = content.getDefinition(Content.ATTRIBUTE_TITLE);
194            SearchUIColumn titleColumn = SearchUIColumnHelper.createModelItemColumn(titleDefinition);
195            titleColumn.setAllowSortOnMultipleJoin(allowSortOnMultipleJoin());
196            resultItems.addViewItem(titleColumn);
197            
198            // Add a column for the referenced field
199            if (contextualParameters.containsKey(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME))
200            {
201                Collection<ContentType> contentTypes = _contentHelper.getContentTypes(content);
202
203                String referenceModelItemPath = ((String) contextualParameters.get(__REFERENCE_FIELD_CONTEXTUAL_PARAM_NAME)).replaceAll("\\.", "/");
204                int lastIndexOfItemPathSeparator = referenceModelItemPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR);
205                String referencedModelItemName = lastIndexOfItemPathSeparator > -1 ? referenceModelItemPath.substring(lastIndexOfItemPathSeparator + ModelItem.ITEM_PATH_SEPARATOR.length()) : referenceModelItemPath;
206
207                // Get or create parent of the referenced field
208                ViewItemAccessor parent = resultItems;
209                if (lastIndexOfItemPathSeparator > -1)
210                {
211                    String parentPath = referenceModelItemPath.substring(0, lastIndexOfItemPathSeparator);
212                    parent = (ViewItemAccessor) ViewHelper.addViewItem(parentPath, resultItems, contentTypes.toArray(new ContentType[contentTypes.size()]));
213                }
214                
215                // Create the column for the referenced model item
216                ModelItem referencedModelItem = _systemPropertyExtensionPoint.hasExtension(referencedModelItemName)
217                        ? _systemPropertyExtensionPoint.getExtension(referencedModelItemName)
218                        : ModelHelper.getModelItem(referencedModelItemName, contentTypes);
219                SearchUIColumn referencedItemColumn = SearchUIColumnHelper.createModelItemColumn(referencedModelItem);
220                referencedItemColumn.setAllowSortOnMultipleJoin(allowSortOnMultipleJoin());
221
222                // Add the column to its parent
223                parent.addViewItem(referencedItemColumn);
224            }
225            
226            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("contributor")));
227            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("lastModified")));
228            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("contentTypes")));
229            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("contentLanguage")));
230            resultItems.addViewItem(SearchUIColumnHelper.createModelItemColumn(_systemPropertyExtensionPoint.getExtension("workflowStep")));
231        }
232
233        setResultItems(resultItems);
234        return super.getResultItems(contextualParameters);
235    }
236}