001/*
002 *  Copyright 2013 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.Iterator;
020import java.util.List;
021
022import org.apache.avalon.framework.configuration.Configurable;
023import org.apache.avalon.framework.configuration.Configuration;
024import org.apache.avalon.framework.configuration.ConfigurationException;
025import org.apache.avalon.framework.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.avalon.framework.service.Serviceable;
028
029import org.ametys.cms.contenttype.ContentType;
030import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
031import org.ametys.cms.contenttype.ContentTypesHelper;
032import org.ametys.cms.contenttype.MetadataDefinition;
033import org.ametys.cms.contenttype.MetadataType;
034import org.ametys.cms.contenttype.RepeaterDefinition;
035import org.ametys.cms.repository.Content;
036import org.ametys.cms.search.SearchField;
037import org.ametys.cms.search.content.ContentSearchHelper;
038import org.ametys.cms.search.model.MetadataResultField;
039
040/**
041 * This class is a search column on a metadata of a content
042 *
043 */
044public class MetadataSearchUIColumn extends AbstractSearchUIColumn implements MetadataResultField, Serviceable, Configurable
045{
046    /** The content type extension point. */
047    protected ContentTypeExtensionPoint _cTypeEP;
048    
049    /** The content type helper. */
050    protected ContentTypesHelper _cTypeHelper;
051    
052    /** The search helper. */
053    protected ContentSearchHelper _searchHelper;
054    
055    /** The full metadata path. */
056    protected String _fullMetadataPath;
057    
058    /** True if the metadata is joined, false otherwise. */
059    protected boolean _joinedMetadata;
060    
061    /** The ID of the content type which contains the metadata. */
062    protected String _contentTypeId;
063    
064    @Override
065    public void service(ServiceManager manager) throws ServiceException
066    {
067        _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
068        _cTypeHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
069        _searchHelper = (ContentSearchHelper) manager.lookup(ContentSearchHelper.ROLE);
070    }
071    
072    @Override
073    public void configure(Configuration configuration) throws ConfigurationException
074    {
075        // The full path can contain "join" paths.
076        _fullMetadataPath = configuration.getChild("metadata").getAttribute("path");
077        _contentTypeId = configuration.getChild("contentTypes").getAttribute("baseId", null);
078        
079        List<MetadataDefinition> metadataDefinitions = _configureMetadataDefinitions();
080        
081        if (metadataDefinitions.isEmpty())
082        {
083            throw new ConfigurationException("Unknown metadata '" + _fullMetadataPath + "' in content type '" + _contentTypeId + "'");
084        }
085        
086        // Compute "join" and "multiple" status.
087        _joinedMetadata = false;
088        boolean multiple = false;
089        boolean multiLevelMultiple = false;
090        Iterator<MetadataDefinition> defIt = metadataDefinitions.iterator();
091        while (defIt.hasNext())
092        {
093            MetadataDefinition metaDef = defIt.next();
094            MetadataType type = metaDef.getType();
095            // The column has multiple values if the full path contains a multiple metadata or a repeater.
096            if (metaDef.isMultiple() || metaDef instanceof RepeaterDefinition)
097            {
098                multiple = true;
099                if (defIt.hasNext())
100                {
101                    multiLevelMultiple = true;
102                }
103            }
104            // The column represents a "joined" value if it has a content metadata (except if it's the last one).
105            if ((type == MetadataType.CONTENT || type == MetadataType.SUB_CONTENT) && defIt.hasNext())
106            {
107                _joinedMetadata = true;
108            }
109        }
110        
111        MetadataDefinition metadataDefinition = metadataDefinitions.get(metadataDefinitions.size() - 1);
112        MetadataType type = metadataDefinition.getType();
113        setId(_fullMetadataPath);
114        setLabel(_configureI18nizableText(configuration.getChild("label", false), metadataDefinition.getLabel()));
115        setDescription(_configureI18nizableText(configuration.getChild("description", false), metadataDefinition.getDescription()));
116        setType(type);
117        setWidth(configuration.getChild("width").getValueAsInteger(200));
118        setHidden(configuration.getChild("hidden").getValueAsBoolean(false));
119        // The metadata is not editable if it is on another content.
120        setEditable(configuration.getChild("editable").getValueAsBoolean(true) && !_joinedMetadata && !multiLevelMultiple);
121        // The metadata is not sortable if it is on another content.
122        boolean isSortable = configuration.getChild("sortable").getValueAsBoolean(true)
123                && _isSortableMetadata(metadataDefinition)
124                && !_joinedMetadata;
125        setSortable(isSortable);
126        if (isSortable)
127        {
128            setDefaultSorter(configuration.getChild("default-sorter").getValue(null));
129        }
130        setValidator(metadataDefinition.getValidator());
131        setEnumerator(metadataDefinition.getEnumerator());
132        setWidget(metadataDefinition.getWidget());
133        setWidgetParameters(metadataDefinition.getWidgetParameters());
134        setMultiple(multiple);
135        setContentTypeId(metadataDefinition.getContentType());
136        
137        configureRenderer(configuration, type);
138        configureConverter(configuration, type);
139    }
140
141    private List<MetadataDefinition> _configureMetadataDefinitions() throws ConfigurationException
142    {
143        List<MetadataDefinition> metadataDefinitions;
144        
145        if (_contentTypeId != null)
146        {
147            ContentType cType = _cTypeEP.getExtension(_contentTypeId);
148            metadataDefinitions = _cTypeHelper.getMetadataDefinitionPath(_fullMetadataPath, cType);
149        }
150        else if ("title".equals(_fullMetadataPath))
151        {
152            // Only the title metadata is allowed if the base content type is null.
153            metadataDefinitions = Collections.singletonList(ContentTypesHelper.getTitleMetadataDefinition());
154        }
155        else
156        {
157            throw new ConfigurationException("The metadata '" + _fullMetadataPath + "' is forbidden when no content type is specified: only title can be used.");
158        }
159        
160        return metadataDefinitions;
161    }
162    
163    /**
164     * Configure the column renderer.
165     * @param configuration The column configuration.
166     * @param type The metadata type.
167     */
168    protected void configureRenderer(Configuration configuration, MetadataType type)
169    {
170        String renderer = configuration.getChild("renderer").getValue(null);
171        if (renderer != null)
172        {
173            setRenderer(renderer);
174        }
175        else if ("title".equals(_fullMetadataPath))
176        {
177            setRenderer("Ametys.cms.content.EditContentsGrid.renderTitle");
178        }
179    }
180    
181    /**
182     * Configure the column converter.
183     * @param configuration The column configuration.
184     * @param type The metadata type.
185     */
186    protected void configureConverter(Configuration configuration, MetadataType type)
187    {
188        String converter = configuration.getChild("converter").getValue(null);
189        if (converter != null)
190        {
191            setConverter(converter);
192        }
193        else if (type == MetadataType.CONTENT || type == MetadataType.SUB_CONTENT)
194        {
195            setConverter("Ametys.cms.content.EditContentsGrid.convertContent");
196        }
197        else if (type == MetadataType.USER)
198        {
199            setConverter("Ametys.cms.content.EditContentsGrid.convertUser");
200        }
201    }
202    
203    private boolean _isSortableMetadata(MetadataDefinition metadataDefinition)
204    {
205        switch (metadataDefinition.getType())
206        { 
207            case STRING:
208            case LONG:
209            case DATE:
210            case DATETIME:
211            case BOOLEAN:
212            case CONTENT:
213            case SUB_CONTENT:
214            case DOUBLE:
215            case USER:
216                return true;
217            case COMPOSITE:
218            case BINARY:
219            case FILE:
220            case RICH_TEXT:
221            case REFERENCE:
222                return false;
223            default:
224                return false;
225        }
226    }
227    
228    /**
229     * Set the metadata path
230     * @param metadataPath the path to metadata
231     */
232    public void setFieldPath(String metadataPath)
233    {
234        _fullMetadataPath = metadataPath;
235    }
236    
237    /**
238     * Get the path of metadata (separated by '/')
239     * @return the path of metadata
240     */
241    public String getFieldPath()
242    {
243        return _fullMetadataPath;
244    }
245    
246    @Override
247    public Object getValue(Content content)
248    {
249        return _searchHelper.getMetadataValues(content, _fullMetadataPath, getType(), isMultiple(), getEnumerator(), false);
250    }
251    
252    @Override
253    public Object getFullValue(Content content)
254    {
255        return _searchHelper.getMetadataValues(content, _fullMetadataPath, getType(), isMultiple(), getEnumerator(), true);
256    }
257    
258    @Override
259    public SearchField getSearchField()
260    {
261        if (!_joinedMetadata)
262        {
263            return _searchHelper.getMetadataSearchField(getFieldPath(), getType());
264        }
265        
266        return null;
267    }
268    
269}