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