001/*
002 *  Copyright 2023 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.search.ui.model.impl;
017
018import java.util.Collections;
019import java.util.LinkedHashMap;
020import java.util.List;
021import java.util.Map;
022import java.util.Set;
023
024import org.apache.commons.collections.MapUtils;
025
026import org.ametys.cms.search.model.DefaultSearchModel;
027import org.ametys.cms.search.model.LanguageAwareCriterionDefinition;
028import org.ametys.cms.search.model.SearchModelCriterionDefinition;
029import org.ametys.cms.search.model.SearchModelCriterionDefinitionHelper;
030import org.ametys.cms.search.ui.model.SearchModelCriterionViewItem;
031import org.ametys.cms.search.ui.model.SearchUIModel;
032import org.ametys.runtime.i18n.I18nizableText;
033import org.ametys.runtime.model.DefinitionContext;
034import org.ametys.runtime.model.ElementDefinition;
035import org.ametys.runtime.model.ModelViewItem;
036import org.ametys.runtime.model.View;
037import org.ametys.runtime.model.ViewHelper;
038import org.ametys.runtime.model.ViewItem;
039import org.ametys.runtime.model.ViewItemAccessor;
040import org.ametys.runtime.model.ViewItemContainer;
041
042/**
043 * Default implementation of a {@link SearchUIModel}.
044 */
045public class DefaultSearchUIModel extends DefaultSearchModel implements SearchUIModel
046{
047    /** The default plugin name for URLs */
048    protected static final String __DEFAULT_URL_PLUGIN = "cms";
049    /** The default URL for search */
050    protected static final String __DEFAULT_SEARCH_URL = "search/list.json";
051    /** The default URL for CSV export */
052    protected static final String __DEFAULT_EXPORT_CSV_URL = "search/export.csv";
053    /** The URL for print results */
054    protected static final String __DEFAULT_PRINT_URL = "search/print.html";
055    
056    /** The advanced criteria */
057    private ViewItemContainer _advancedCriteria = new View();
058    
059    /** The page size **/
060    private int _pageSize = -1;
061    
062    /** The URL for search */
063    private String _searchUrl = __DEFAULT_SEARCH_URL;
064    /** The plugin name for search */
065    private String _searchUrlPlugin = __DEFAULT_URL_PLUGIN;
066    
067    /** The URL for CSV export of results */
068    private String _exportCSVUrl = __DEFAULT_EXPORT_CSV_URL;
069    /** The plugin name for CSV export of results */
070    private String _exportCSVUrlPlugin = __DEFAULT_URL_PLUGIN;
071    
072    /** The URL for print results */
073    private String _printUrl = __DEFAULT_PRINT_URL;
074    /** The plugin name for print results */
075    private String _printUrlPlugin = __DEFAULT_URL_PLUGIN;
076    
077    /** The name of the view to use for summary of the content */
078    private String _summaryView;
079    
080    /** Determine is sort is allowed on multiple join */
081    private boolean _sortOnMultipleJoin;
082    private I18nizableText _label;
083    private I18nizableText _description;
084    private Set<String> _tags = Set.of();
085    
086    /**
087     * Default constructor
088     */
089    public DefaultSearchUIModel()
090    {
091        super();
092    }
093    
094    /**
095     * Constructor by copying an existing {@link SearchUIModel}.
096     * @param searchUIModelToCopy The {@link SearchUIModel} to copy
097     * @param contextualParameters The contextual parameters
098     */
099    public DefaultSearchUIModel(SearchUIModel searchUIModelToCopy, Map<String, Object> contextualParameters)
100    {
101        super(searchUIModelToCopy, contextualParameters);
102        
103        setLabel(searchUIModelToCopy.getLabel());
104        setDescription(searchUIModelToCopy.getDescription());
105        
106        setTags(searchUIModelToCopy.getTags());
107        
108        ViewItemContainer advancedCriteriaToCopy = searchUIModelToCopy.getAdvancedCriteria(contextualParameters);
109        if (advancedCriteriaToCopy != null)
110        {
111            View advancedCriteria = new View();
112            List<ViewItem> advancedCriteriaViewItems = ViewHelper.copyViewItems(advancedCriteriaToCopy.getViewItems());
113            advancedCriteria.addViewItems(advancedCriteriaViewItems);
114            setAdvancedCriteria(advancedCriteria);
115        }
116        
117        setPageSize(searchUIModelToCopy.getPageSize(contextualParameters));
118        
119        setSearchUrl(searchUIModelToCopy.getSearchUrl(contextualParameters));
120        setSearchUrlPlugin(searchUIModelToCopy.getSearchUrlPlugin(contextualParameters));
121        
122        setExportCSVUrl(searchUIModelToCopy.getExportCSVUrl(contextualParameters));
123        setExportCSVUrlPlugin(searchUIModelToCopy.getExportCSVUrlPlugin(contextualParameters));
124        
125        setPrintUrl(searchUIModelToCopy.getPrintUrl(contextualParameters));
126        setPrintUrlPlugin(searchUIModelToCopy.getPrintUrlPlugin(contextualParameters));
127    }
128    
129    public String getName()
130    {
131        return getId();
132    }
133    
134    public I18nizableText getLabel()
135    {
136        return _label;
137    }
138    
139    /**
140     * Set the label of the model
141     * @param label the label
142     */
143    public void setLabel(I18nizableText label)
144    {
145        _label =  label;
146    }
147    
148    public I18nizableText getDescription()
149    {
150        return _description;
151    }
152    
153    /**
154     * Set the description of the model
155     * @param description the description
156     */
157    public void setDescription(I18nizableText description)
158    {
159        _description = description;
160    }
161
162    public ViewItemContainer getAdvancedCriteria(Map<String, Object> contextualParameters)
163    {
164        return _advancedCriteria;
165    }
166    
167    /**
168     * Add criteria to the advanced criteria. The created criteria reference the given paths
169     * @param criterionDefinitionHelper the criterion definition helper
170     * @param contextualParameters the contextual parameters
171     * @param references the paths to the items the criteria will reference
172     */
173    public void addAdvancedCriteria(SearchModelCriterionDefinitionHelper criterionDefinitionHelper, Map<String, Object> contextualParameters, String... references)
174    {
175        for (String reference : references)
176        {
177            SearchModelCriterionDefinition criterion = criterionDefinitionHelper.createReferencingCriterionDefinition(this, reference, getContentTypes(contextualParameters));
178            addAdvancedCriterion(criterion, contextualParameters);
179        }
180    }
181    
182    /**
183     * Set the advanced criteria
184     * @param criteria The advanced criteria to set
185     */
186    public void setAdvancedCriteria(ViewItemContainer criteria)
187    {
188        _advancedCriteria = criteria;
189    }
190    
191    public int getPageSize(Map<String, Object> contextualParameters)
192    {
193        return _pageSize;
194    }
195    
196    /**
197     * Set the page size
198     * @param pageSize The page size to set. Set to negative or 0 for unlimited or default
199     */
200    public void setPageSize(int pageSize)
201    {
202        _pageSize = pageSize;
203    }
204
205    public String getSearchUrl(Map<String, Object> contextualParameters)
206    {
207        return _searchUrl;
208    }
209    
210    /**
211     * Set the URL for search
212     * @param searchUrl The URL to set
213     */
214    public void setSearchUrl(String searchUrl)
215    {
216        _searchUrl = searchUrl;
217    }
218
219    public String getSearchUrlPlugin(Map<String, Object> contextualParameters)
220    {
221        return _searchUrlPlugin;
222    }
223    
224    /**
225     * Set the plugin name for search
226     * @param searchUrlPlugin the plugin name to set
227     */
228    public void setSearchUrlPlugin(String searchUrlPlugin)
229    {
230        _searchUrlPlugin = searchUrlPlugin;
231    }
232
233    public String getExportCSVUrl(Map<String, Object> contextualParameters)
234    {
235        return _exportCSVUrl;
236    }
237    
238    /**
239     * Set the URL for CSV export of results
240     * @param exportCSVUrl The URL to set
241     */
242    public void setExportCSVUrl(String exportCSVUrl)
243    {
244        _exportCSVUrl = exportCSVUrl;
245    }
246
247    public String getExportCSVUrlPlugin(Map<String, Object> contextualParameters)
248    {
249        return _exportCSVUrlPlugin;
250    }
251    
252    /**
253     * Set the plugin name for CSV export of results
254     * @param exportCSVUrlPlugin the plugin name to set
255     */
256    public void setExportCSVUrlPlugin(String exportCSVUrlPlugin)
257    {
258        _exportCSVUrlPlugin = exportCSVUrlPlugin;
259    }
260
261    public String getPrintUrl(Map<String, Object> contextualParameters)
262    {
263        return _printUrl;
264    }
265    
266    /**
267     * Set the URL for print results
268     * @param printUrl The URL to set
269     */
270    public void setPrintUrl(String printUrl)
271    {
272        _printUrl = printUrl;
273    }
274
275    public String getPrintUrlPlugin(Map<String, Object> contextualParameters)
276    {
277        return _printUrlPlugin;
278    }
279    
280    /**
281     * Set the plugin name for print results
282     * @param printUrlPlugin the plugin name to set
283     */
284    public void setPrintUrlPlugin(String printUrlPlugin)
285    {
286        _printUrlPlugin = printUrlPlugin;
287    }
288
289    public String getSummaryView()
290    {
291        return _summaryView;
292    }
293
294    /**
295     * Set The name of the view to use for summary of the content
296     * @param summaryView the name to set
297     */
298    public void setSummaryView(String summaryView)
299    {
300        _summaryView = summaryView;
301    }
302    
303    /**
304     * Indicates if sorting on join columns which contain at least one {@link ElementDefinition#isMultiple() multiple} {@link ElementDefinition definition} in its path (intermediate ones only) must be allowed.
305     * <br>If the final definition of the path is multiple, the column will not be sortable though.
306     * @return <code>true</code> if sorting on join columns which contains at least one multiple item in its path (intermediate ones only) must be allowed.
307     */
308    protected boolean allowSortOnMultipleJoin()
309    {
310        return _sortOnMultipleJoin;
311    }
312    
313    /**
314     * Set to <code>true</code> to allow sort on multiple join (default to <code>false</code>)
315     * @param sortOnMultipleJoin <code>true</code> to allow sort on multiple join, <code>false</code> otherwise
316     */
317    public void setAllowSortOnMultipleJoin(boolean sortOnMultipleJoin)
318    {
319        _sortOnMultipleJoin = sortOnMultipleJoin;
320    }
321    
322    @Override
323    public Map<String, Object> toJSON(Map<String, Object> contextualParameters)
324    {
325        Map<String, Object> jsonObject = super.toJSON(contextualParameters);
326        
327        jsonObject.put("pageSize", getPageSize(contextualParameters));
328        jsonObject.put("workspace", getWorkspace(contextualParameters));
329        jsonObject.put("searchUrl", getSearchUrl(contextualParameters));
330        jsonObject.put("searchUrlPlugin", getSearchUrlPlugin(contextualParameters));
331        jsonObject.put("exportCSVUrl", getExportCSVUrl(contextualParameters));
332        jsonObject.put("exportCSVUrlPlugin", getExportCSVUrlPlugin(contextualParameters));
333        jsonObject.put("printUrl", getPrintUrl(contextualParameters));
334        jsonObject.put("printUrlPlugin", getPrintUrlPlugin(contextualParameters));
335        jsonObject.put("summaryView", getSummaryView());
336        
337        jsonObject.put("advanced-criteria", _getAdvancedCriteriaListInfo(getAdvancedCriteria(contextualParameters)));
338        
339        return jsonObject;
340    }
341    
342    /**
343     * Return information on a list of advanced {@link SearchModelCriterionViewItem}, serialized as a Map.
344     * @param criteria A map of advanced criteria.
345     * @return the detailed information serialized in a Map.
346     */
347    protected Map<String, Object> _getAdvancedCriteriaListInfo(ViewItemAccessor criteria)
348    {
349        Map<String, Object> jsonObject = new LinkedHashMap<>();
350
351        if (criteria != null)
352        {
353            // Make a copy of the criteria view
354            ViewItemAccessor criteriaCopy = new View();
355            criteriaCopy.addViewItems(ViewHelper.copyViewItems(criteria.getViewItems()));
356    
357            // Extract language criterion from the others
358            ModelViewItem languageCriterion = _getContentLanguageCriterionViewItem(criteriaCopy);
359            if (languageCriterion != null)
360            {
361                jsonObject.put("language", languageCriterion.toJSON(DefinitionContext.newInstance()));
362                
363                // Remove language criterion that is already processed
364                ViewItemAccessor languageParent = languageCriterion.getParent();
365                languageParent.removeViewItem(languageCriterion);
366            }
367            
368            Map<String, Object> criteriaObject = ViewHelper.viewItemsToJSON(criteriaCopy.getViewItems(), DefinitionContext.newInstance());
369            if (MapUtils.isNotEmpty(criteriaObject))
370            {
371                jsonObject.put("criteria", criteriaObject);
372            }
373        }
374        
375        return jsonObject;
376    }
377    
378    private ModelViewItem _getContentLanguageCriterionViewItem(ViewItemAccessor criteria)
379    {
380        for (ViewItem viewItem : criteria.getViewItems())
381        {
382            if (viewItem instanceof SearchModelCriterionViewItem criterionViewItem
383                    && criterionViewItem.getDefinition() instanceof LanguageAwareCriterionDefinition)
384            {
385                return criterionViewItem;
386            }
387            else if (viewItem instanceof ViewItemContainer itemContainer)
388            {
389                ModelViewItem contentLanguageCriterion = _getContentLanguageCriterionViewItem(itemContainer);
390                if (contentLanguageCriterion != null)
391                {
392                    return contentLanguageCriterion;
393                }
394            }
395        }
396        
397        // No criterion on contentLanguage has been found
398        return null;
399    }
400    
401    /**
402     * Set the tags of this search model
403     * @param tags the set of tags to set
404     */
405    public void setTags(Set<String> tags)
406    {
407        _tags = tags;
408    }
409    
410    @Override
411    public boolean hasTag(String tagName)
412    {
413        return _tags.contains(tagName);
414    }
415    
416    @Override
417    public Set<String> getTags()
418    {
419        return Collections.unmodifiableSet(_tags);
420    }
421}