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()
239                    || definition instanceof RepeaterDefinition)
240                {
241                    return true;
242                }
243            }
244        }
245        
246        return false;
247    }
248    
249    @Override
250    public String getFieldPath()
251    {
252        return (_joinPaths.isEmpty() ? "" : _joinPaths + ModelItem.ITEM_PATH_SEPARATOR) + getSystemPropertyId();
253    }
254    
255    /**
256     * Get the system property name
257     * @return The property name
258     */
259    public String getSystemPropertyId()
260    {
261        return _systemProperty.getId();
262    }
263    
264    @Override
265    public Object getValue(Content content, Locale defaultLocale)
266    {
267        return _getValue(content, false);
268    }
269    
270    @Override
271    public Object getFullValue(Content content, Locale defaultLocale)
272    {
273        return _getValue(content, true);
274    }
275    
276    private Object _getValue(Content content, boolean full)
277    {
278        if (isMultiple())
279        {
280            Set<Object> values = new LinkedHashSet<>();
281            
282            for (Content targetContent : _contentHelper.getTargetContents(content, _joinPaths))
283            {
284                Object value = full ? _systemProperty.getJsonValue(targetContent, true) : _systemProperty.getValue(targetContent);
285                if (value != null)
286                {
287                    if (value instanceof Collection<?>)
288                    {
289                        // Flatten values
290                        values.addAll((Collection<?>) value);
291                    }
292                    else if (value instanceof Object[])
293                    {
294                        // Flatten values
295                        values.addAll(Arrays.asList((Object[]) value));
296                    }
297                    else
298                    {
299                        values.add(value);
300                    }
301                }
302            }
303            
304            return values;
305        }
306        else
307        {
308            Content targetContent = _contentHelper.getTargetContent(content, _joinPaths);
309            if (targetContent != null)
310            {
311                return full ? _systemProperty.getJsonValue(targetContent, true) : _systemProperty.getValue(targetContent);
312            }
313        }
314        
315        return null;
316    }
317    
318    @Override
319    public SearchField getSearchField()
320    {
321        return _systemProperty.getSearchField();
322    }
323    
324    private String _initializeEnumerator(EnumeratorDefinition enumDef)
325    {
326        String role = null;
327        
328        if (enumDef.isStatic())
329        {
330            StaticEnumerator enumerator = new StaticEnumerator();
331            enumDef.getStaticEntries().entrySet().forEach(entry -> enumerator.add(entry.getValue(), entry.getKey()));
332            setEnumerator(enumerator);
333        }
334        else
335        {
336            role = "enumerator";
337            _enumeratorManager.addComponent("cms", null, role, enumDef.getEnumeratorClass(), enumDef.getConfiguration());
338        }
339        
340        return role;
341    }
342
343    public Map<String, SearchUIColumn> getSubColumns()
344    {
345        return null;
346    }
347    
348}