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