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