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.Arrays;
019import java.util.Collection;
020import java.util.HashSet;
021import java.util.LinkedHashSet;
022import java.util.List;
023import java.util.Locale;
024import java.util.Map;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import org.apache.avalon.framework.activity.Disposable;
029import org.apache.avalon.framework.configuration.Configurable;
030import org.apache.avalon.framework.configuration.Configuration;
031import org.apache.avalon.framework.configuration.ConfigurationException;
032import org.apache.avalon.framework.context.Context;
033import org.apache.avalon.framework.context.ContextException;
034import org.apache.avalon.framework.context.Contextualizable;
035import org.apache.avalon.framework.service.ServiceException;
036import org.apache.avalon.framework.service.ServiceManager;
037import org.apache.avalon.framework.service.Serviceable;
038import org.apache.commons.lang3.StringUtils;
039import org.slf4j.Logger;
040
041import org.ametys.cms.content.ContentHelper;
042import org.ametys.cms.contenttype.ContentType;
043import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
044import org.ametys.cms.contenttype.ContentTypesHelper;
045import org.ametys.cms.repository.Content;
046import org.ametys.cms.search.SearchField;
047import org.ametys.cms.search.model.SystemProperty;
048import org.ametys.cms.search.model.SystemProperty.EnumeratorDefinition;
049import org.ametys.cms.search.model.SystemPropertyExtensionPoint;
050import org.ametys.cms.search.model.SystemResultField;
051import org.ametys.cms.search.ui.model.SearchUIColumn;
052import org.ametys.plugins.repository.AmetysObjectResolver;
053import org.ametys.plugins.repository.model.RepeaterDefinition;
054import org.ametys.runtime.i18n.I18nizableText;
055import org.ametys.runtime.model.ElementDefinition;
056import org.ametys.runtime.model.ModelHelper;
057import org.ametys.runtime.model.ModelItem;
058import org.ametys.runtime.parameter.Enumerator;
059import org.ametys.runtime.parameter.StaticEnumerator;
060import org.ametys.runtime.plugin.component.LogEnabled;
061import org.ametys.runtime.plugin.component.ThreadSafeComponentManager;
062
063/**
064 * This class is a search column on a system property (author, lastModified, with-comments, ...)
065 */
066public class SystemSearchUIColumn extends AbstractSearchUIColumn implements SystemResultField, Contextualizable, LogEnabled, Configurable, Serviceable, Disposable
067{
068    
069    /** ComponentManager for {@link Enumerator}s. */
070    protected ThreadSafeComponentManager<Enumerator> _enumeratorManager;
071    
072    /** The system property extension point. */
073    protected SystemPropertyExtensionPoint _systemPropEP;
074    
075    /** The content type extension point. */
076    protected ContentTypeExtensionPoint _cTypeEP;
077    
078    /** The content helper. */
079    protected ContentHelper _contentHelper;
080    
081    /** The content type helper. */
082    protected ContentTypesHelper _contentTypeHelper;
083    
084    /** The Ametys object resolver. */
085    protected AmetysObjectResolver _resolver;
086    
087    /** The system property. */
088    protected SystemProperty _systemProperty;
089    
090    /** The join paths. */
091    protected String _joinPaths;
092    
093    /** The content types */
094    private Set<String> _contentTypes;
095    
096    private ServiceManager _manager;
097    private Logger _logger;
098    private Context _context;
099    
100    @Override
101    public void contextualize(Context context) throws ContextException
102    {
103        _context = context;
104    }
105    
106    @Override
107    public void setLogger(Logger logger)
108    {
109        _logger = logger;
110    }
111    
112    @Override
113    public void service(ServiceManager manager) throws ServiceException
114    {
115        _manager = manager;
116        _systemPropEP = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE);
117        _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
118        _contentHelper = (ContentHelper) manager.lookup(ContentHelper.ROLE);
119        _contentTypeHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
120        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
121    }
122    
123    @Override
124    public void dispose()
125    {
126        _enumeratorManager.dispose();
127        _enumeratorManager = null;
128    }
129    
130    @Override
131    public void configure(Configuration configuration) throws ConfigurationException
132    {
133        try
134        {
135            _enumeratorManager = new ThreadSafeComponentManager<>();
136            _enumeratorManager.setLogger(_logger);
137            _enumeratorManager.contextualize(_context);
138            _enumeratorManager.service(_manager);
139        
140            String fullPath = configuration.getChild("systemProperty").getAttribute("name");
141            int pos = fullPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR);
142            
143            String systemPropertyId = pos > -1 ? fullPath.substring(pos + ModelItem.ITEM_PATH_SEPARATOR.length()) : fullPath;
144            
145            if (!_systemPropEP.isDisplayable(systemPropertyId))
146            {
147                throw new ConfigurationException("The property '" + systemPropertyId + "' doesn't exist or is not displayable.");
148            }
149            
150            _joinPaths = pos > -1 ? fullPath.substring(0, pos) : "";
151            _systemProperty = _systemPropEP.getExtension(systemPropertyId);
152            
153            _contentTypes = new HashSet<>();
154            for (Configuration cTypeConf : configuration.getChild("contentTypes").getChildren("type"))
155            {
156                _contentTypes.add(cTypeConf.getAttribute("id"));
157            }
158            
159            setId(fullPath);
160            
161            setLabel(_systemProperty.getLabel());
162            setDescription(_systemProperty.getDescription());
163            setType(_systemProperty.getType());
164            
165            Set<String> baseContentTypeIds = new HashSet<>();
166            for (Configuration cTypeConf : configuration.getChild("contentTypes").getChildren("baseType"))
167            {
168                baseContentTypeIds.add(cTypeConf.getAttribute("id"));
169            }
170            boolean multiple = testMultiple(baseContentTypeIds);
171            setMultiple(multiple);
172            
173            String enumeratorRole = null;
174            EnumeratorDefinition enumDef = _systemProperty.getEnumeratorDefinition(configuration);
175            if (enumDef != null)
176            {
177                enumeratorRole = _initializeEnumerator(enumDef);
178                if (enumeratorRole != null)
179                {
180                    _enumeratorManager.initialize();
181                    setEnumerator(_enumeratorManager.lookup(enumeratorRole));
182                }
183            }
184            
185            setEditable(false);
186            setWidth(_systemProperty.getColumnWidth() != null ? _systemProperty.getColumnWidth() : 150);
187            setHidden(configuration.getChild("hidden").getValueAsBoolean(false));
188            setSortable(configuration.getChild("sortable").getValueAsBoolean(_systemProperty.isSortable()));
189            if (isSortable())
190            {
191                setDefaultSorter(configuration.getChild("default-sorter").getValue(null));
192            }
193            // Potentially replace the standard label and description by the custom ones.
194            I18nizableText userLabel = _configureI18nizableText(configuration.getChild("label", false), null);
195            if (userLabel != null)
196            {
197                setLabel(userLabel);
198            }
199            I18nizableText userDescription = _configureI18nizableText(configuration.getChild("description", false), null);
200            if (userDescription != null)
201            {
202                setDescription(userDescription);
203            }
204            
205            if (_systemProperty.getRenderer() != null)
206            {
207                setRenderer(_systemProperty.getRenderer());
208            }
209            if (_systemProperty.getConverter() != null)
210            {
211                setConverter(_systemProperty.getConverter());
212            }
213        }
214        catch (Exception e)
215        {
216            throw new ConfigurationException("Error configuring the system search column.", configuration, e);
217        }
218    }
219
220    private boolean testMultiple(Set<String> contentTypeIds)
221    {
222        // if system property is multiple, then the column is multiple
223        if (_systemProperty.isMultiple())
224        {
225            return true;
226        }
227        
228        // Otherwise, the column is multiple if at least one of the join path definitions is multiple
229        if (!contentTypeIds.isEmpty() && StringUtils.isNotEmpty(_joinPaths))
230        {
231            Collection<ContentType> contentTypes = contentTypeIds.stream()
232                    .map(_cTypeEP::getExtension)
233                    .collect(Collectors.toList());
234            List<ModelItem> modelItems = ModelHelper.getAllModelItemsInPath(_joinPaths, contentTypes);
235            
236            for (ModelItem definition : modelItems)
237            {
238                if ((definition instanceof ElementDefinition && ((ElementDefinition) definition).isMultiple()) || definition instanceof RepeaterDefinition)
239                {
240                    return true;
241                }
242            }
243        }
244        
245        return false;
246    }
247    
248    @Override
249    public String getFieldPath()
250    {
251        return (_joinPaths.isEmpty() ? "" : _joinPaths + ModelItem.ITEM_PATH_SEPARATOR) + getSystemPropertyId();
252    }
253    
254    /**
255     * Get the system property name
256     * @return The property name
257     */
258    public String getSystemPropertyId()
259    {
260        return _systemProperty.getId();
261    }
262    
263    @Override
264    public Object getValue(Content content, Locale defaultLocale)
265    {
266        return _getValue(content, false);
267    }
268    
269    @Override
270    public Object getFullValue(Content content, Locale defaultLocale)
271    {
272        return _getValue(content, true);
273    }
274    
275    private Object _getValue(Content content, boolean full)
276    {
277        if (isMultiple())
278        {
279            Set<Object> values = new LinkedHashSet<>();
280            
281            for (Content targetContent : _contentHelper.getTargetContents(content, _joinPaths))
282            {
283                Object value = full ? _systemProperty.getJsonValue(targetContent, true) : _systemProperty.getValue(targetContent);
284                if (value != null)
285                {
286                    if (value instanceof Collection<?>)
287                    {
288                        // Flatten values
289                        values.addAll((Collection<?>) value);
290                    }
291                    else if (value instanceof Object[])
292                    {
293                        // Flatten values
294                        values.addAll(Arrays.asList((Object[]) value));
295                    }
296                    else
297                    {
298                        values.add(value);
299                    }
300                }
301            }
302            
303            return values;
304        }
305        else
306        {
307            Content targetContent = _contentHelper.getTargetContent(content, _joinPaths);
308            if (targetContent != null)
309            {
310                return full ? _systemProperty.getJsonValue(_contentHelper.getTargetContent(content, _joinPaths), true) : _systemProperty.getValue(targetContent);
311            }
312        }
313        
314        return null;
315    }
316    
317    @Override
318    public SearchField getSearchField()
319    {
320        return _systemProperty.getSearchField();
321    }
322    
323    private String _initializeEnumerator(EnumeratorDefinition enumDef)
324    {
325        String role = null;
326        
327        if (enumDef.isStatic())
328        {
329            StaticEnumerator enumerator = new StaticEnumerator();
330            enumDef.getStaticEntries().entrySet().forEach(entry -> enumerator.add(entry.getValue(), entry.getKey()));
331            setEnumerator(enumerator);
332        }
333        else
334        {
335            role = "enumerator";
336            _enumeratorManager.addComponent("cms", null, role, enumDef.getEnumeratorClass(), enumDef.getConfiguration());
337        }
338        
339        return role;
340    }
341
342    public Map<String, SearchUIColumn> getSubColumns()
343    {
344        return null;
345    }
346    
347}