001/*
002 *  Copyright 2017 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.Collection;
019import java.util.Locale;
020
021import org.apache.avalon.framework.service.ServiceException;
022import org.apache.avalon.framework.service.ServiceManager;
023import org.apache.avalon.framework.service.Serviceable;
024
025import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
026import org.ametys.cms.contenttype.ContentTypesHelper;
027import org.ametys.cms.contenttype.MetadataDefinition;
028import org.ametys.cms.contenttype.MetadataType;
029import org.ametys.cms.contenttype.RepeaterDefinition;
030import org.ametys.cms.repository.Content;
031import org.ametys.cms.search.SearchField;
032import org.ametys.cms.search.content.ContentSearchHelper;
033import org.ametys.cms.search.model.MetadataResultField;
034import org.ametys.runtime.i18n.I18nizableText;
035
036/**
037 * Default implementation of a search ui column for the metadata of a content
038 */
039public class DefaultMetadataSearchUIColumn extends AbstractSearchUIColumn implements MetadataResultField, Serviceable
040{
041    /** The content type extension point. */
042    protected ContentTypeExtensionPoint _cTypeEP;
043    
044    /** The content type helper. */
045    protected ContentTypesHelper _cTypeHelper;
046    
047    /** The search helper. */
048    protected ContentSearchHelper _searchHelper;
049    
050    /** The full metadata path. */
051    protected String _fullMetadataPath;
052    
053    /** True if the metadata is joined, false otherwise. */
054    protected boolean _joinedMetadata;
055
056    /** The content types of the contents on which this metadata column applies */
057    protected Collection<String> _contentTypes;
058
059    private boolean _isTypeContentWithMultilingualTitle;
060    
061    /**
062     * Default empty constructor
063     */
064    public DefaultMetadataSearchUIColumn()
065    {
066        //
067    }
068    
069    /**
070     * Default constructor. Fill the search ui column from the metadata definition
071     * @param metadataDefinition The metadata definition
072     * @param fullMetadataPath The full metadata path
073     * @param contentTypes The content types of the contents on which this metadata column applies
074     */
075    public DefaultMetadataSearchUIColumn(MetadataDefinition metadataDefinition, String fullMetadataPath, Collection<String> contentTypes)
076    {
077        _fullMetadataPath = fullMetadataPath;
078        
079        _configure(metadataDefinition, metadataDefinition.getLabel(), metadataDefinition.getDescription(), 200, false, true, false, metadataDefinition.isMultiple(), false, null, null, null, contentTypes, false);
080    }
081    
082    @Override
083    public void service(ServiceManager manager) throws ServiceException
084    {
085        _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
086        _cTypeHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
087        _searchHelper = (ContentSearchHelper) manager.lookup(ContentSearchHelper.ROLE);
088    }
089    
090    /**
091     * Configure the metadata search ui column
092     * @param metadataDefinition The definition
093     * @param label The column label
094     * @param description The column description
095     * @param width The column width
096     * @param hidden True if the column should be hidden
097     * @param editable True if the column is editable
098     * @param sortable True to allow sorting the column
099     * @param multiple True if the value is multiple
100     * @param multiLevelMultiple If the metadata path contains a multiple metadata or a repeater
101     * @param defaultSorter The default column sorter
102     * @param renderer A specific renderer to use. If null, it will be deduced from the metadata definition
103     * @param converter A specific converter to use. If null, it will be deduced from the metadata definition
104     * @param contentTypes The content types of the contents on which this metadata column applies
105     * @param allowSortOnMultipleJoin True to allow sort when the join path contains at least one mutliple metadata (intermediates only)
106     */
107    protected void _configure(MetadataDefinition metadataDefinition, I18nizableText label, I18nizableText description, int width, boolean hidden, boolean editable, boolean sortable, boolean multiple, boolean multiLevelMultiple, String defaultSorter, String renderer, String converter, Collection<String> contentTypes, boolean allowSortOnMultipleJoin)
108    {
109        MetadataType type = metadataDefinition.getType();
110        
111        setId(_fullMetadataPath);
112        setLabel(label);
113        setDescription(description);
114        setType(type);
115        setWidth(width);
116        setHidden(hidden);
117        // The metadata is not editable if it is on another content.
118        setEditable(editable && !multiLevelMultiple && isEditionAllowed(metadataDefinition));
119        boolean finalMetaDefMultiple = metadataDefinition.isMultiple() || metadataDefinition instanceof RepeaterDefinition;
120        boolean isSortable = sortable
121                && (allowSortOnMultipleJoin && !finalMetaDefMultiple // if path is not multiple, but an intermediate in the path is, it is OK => consider as sortable
122                        || !allowSortOnMultipleJoin && !multiple)    // if path is multiple => do not consider as sortable
123                && _isSortableMetadata(metadataDefinition);
124        setSortable(isSortable);
125        if (isSortable)
126        {
127            setDefaultSorter(defaultSorter);
128        }
129        setValidator(metadataDefinition.getValidator());
130        setEnumerator(metadataDefinition.getEnumerator());
131        setWidget(metadataDefinition.getWidget());
132        setWidgetParameters(metadataDefinition.getWidgetParameters());
133        setMultiple(multiple);
134        setContentTypeId(metadataDefinition.getContentType());
135        
136        configureRenderer(renderer, metadataDefinition);
137        configureConverter(converter, metadataDefinition);
138        
139        _contentTypes = contentTypes;
140        
141        _configureTypeContentWithMultilingualTitle(metadataDefinition);
142    }
143    
144    /**
145     * Determines if the inline edition is allowed
146     * @param definition The metadata definition
147     * @return true if the metadata is editable
148     */
149    protected boolean isEditionAllowed(MetadataDefinition definition)
150    {
151        if (_joinedMetadata)
152        {
153            // metadata is not editable if it is on another content
154            return false;
155        }
156        
157        switch (definition.getType())
158        {
159            case COMPOSITE:
160            case RICH_TEXT:
161                // richtext and composite are never editable inline
162                return false;
163            default:
164                break;
165        }
166        
167        return true;
168    }
169
170    /**
171     * Configure the column renderer.
172     * @param renderer A specific renderer. If null, it will be deduced from the metadata definition.
173     * @param metadataDefinition The metadata definition.
174     */
175    protected void configureRenderer(String renderer, MetadataDefinition metadataDefinition)
176    {
177        MetadataType type = metadataDefinition.getType();
178        if (renderer != null)
179        {
180            setRenderer(renderer);
181        }
182        else if ("title".equals(_fullMetadataPath) && type == MetadataType.STRING)
183        {
184            setRenderer("Ametys.plugins.cms.search.SearchGridHelper.renderTitle");
185        }
186        else if ("title".equals(_fullMetadataPath) && type == MetadataType.MULTILINGUAL_STRING)
187        {
188            setRenderer("Ametys.plugins.cms.search.SearchGridHelper.renderMultilingualTitle");
189        }
190        else if (metadataDefinition instanceof RepeaterDefinition)
191        {
192            setRenderer("Ametys.plugins.cms.search.SearchGridHelper.renderRepeater");
193        }
194    }
195    
196    /**
197     * Configure the column converter.
198     * @param converter A specific converter. If null, it will be deduced from the metadata definition.
199     * @param metadataDefinition The metadata definition.
200     */
201    protected void configureConverter(String converter, MetadataDefinition metadataDefinition)
202    {
203        MetadataType type = metadataDefinition.getType();
204        if (converter != null)
205        {
206            setConverter(converter);
207        }
208        else if (type == MetadataType.CONTENT || type == MetadataType.SUB_CONTENT)
209        {
210            setConverter("Ametys.plugins.cms.search.SearchGridHelper.convertContent");
211        }
212        else if (metadataDefinition instanceof RepeaterDefinition)
213        {
214            setConverter("Ametys.plugins.cms.search.SearchGridHelper.convertRepeater");
215        }
216    }
217    
218    private boolean _isSortableMetadata(MetadataDefinition metadataDefinition)
219    {
220        switch (metadataDefinition.getType())
221        { 
222            case STRING:
223            case MULTILINGUAL_STRING:
224            case LONG:
225            case DATE:
226            case DATETIME:
227            case BOOLEAN:
228            case CONTENT:
229            case SUB_CONTENT:
230            case DOUBLE:
231            case USER:
232                return true;
233            case COMPOSITE:
234            case BINARY:
235            case FILE:
236            case RICH_TEXT:
237            case REFERENCE:
238                return false;
239            default:
240                return false;
241        }
242    }
243    
244    /**
245     * Set the metadata path
246     * @param metadataPath the path to metadata
247     */
248    public void setFieldPath(String metadataPath)
249    {
250        _fullMetadataPath = metadataPath;
251    }
252    
253    /**
254     * Get the path of metadata (separated by '/')
255     * @return the path of metadata
256     */
257    public String getFieldPath()
258    {
259        return _fullMetadataPath;
260    }
261    
262    @Override
263    public Object getValue(Content content, Locale defaultLocale)
264    {
265        return _searchHelper.getMetadataValue(content, _fullMetadataPath, getType(), isMultiple(), getEnumerator(), defaultLocale, false);
266    }
267    
268    @Override
269    public Object getFullValue(Content content, Locale defaultLocale)
270    {
271        return _searchHelper.getMetadataValue(content, _fullMetadataPath, getType(), isMultiple(), getEnumerator(), defaultLocale, true);
272    }
273    
274    @Override
275    public SearchField getSearchField()
276    {
277        if (_joinedMetadata)
278        {
279            return _searchHelper.getSearchField(_contentTypes, _fullMetadataPath).orElse(null);
280        }
281        else
282        {
283            return _searchHelper.getMetadataSearchField(getFieldPath(), getType(), _isTypeContentWithMultilingualTitle);
284        }
285    }
286    
287    private void _configureTypeContentWithMultilingualTitle(MetadataDefinition finalDefinition)
288    {
289        _isTypeContentWithMultilingualTitle = _searchHelper.isTitleMultilingual(finalDefinition);
290    }
291}