001/*
002 *  Copyright 2015 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.systemprop;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.Collections;
021import java.util.Date;
022import java.util.List;
023import java.util.Map;
024
025import org.apache.avalon.framework.configuration.Configurable;
026import org.apache.avalon.framework.configuration.Configuration;
027import org.apache.avalon.framework.configuration.ConfigurationException;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.solr.common.SolrInputDocument;
032
033import org.ametys.cms.content.indexing.solr.SolrIndexer;
034import org.ametys.cms.repository.Content;
035import org.ametys.cms.search.SearchField;
036import org.ametys.cms.search.model.SystemProperty;
037import org.ametys.cms.search.solr.schema.CopyFieldDefinition;
038import org.ametys.cms.search.solr.schema.FieldDefinition;
039import org.ametys.cms.search.solr.schema.SchemaDefinition;
040import org.ametys.cms.search.solr.schema.SchemaHelper;
041import org.ametys.core.user.UserIdentity;
042import org.ametys.core.util.I18nUtils;
043import org.ametys.runtime.i18n.I18nizableText;
044import org.ametys.runtime.parameter.ParameterHelper;
045import org.ametys.runtime.parameter.ParameterHelper.ParameterType;
046import org.ametys.runtime.plugin.component.AbstractLogEnabled;
047import org.ametys.runtime.plugin.component.PluginAware;
048
049/**
050 * Abstract class providing standard behavior and helper methods for a
051 * {@link SystemProperty}.
052 */
053public abstract class AbstractSystemProperty extends AbstractLogEnabled implements SystemProperty, Serviceable, Configurable, PluginAware
054{
055
056    /** The i18n utils. */
057    protected I18nUtils _i18nUtils;
058
059    /** The property ID. */
060    protected String _id;
061
062    /** The property label. */
063    protected I18nizableText _label;
064
065    /** The property description. */
066    protected I18nizableText _description;
067
068    /** The property plugin name. */
069    protected String _pluginName;
070
071    @Override
072    public void setPluginInfo(String pluginName, String featureName, String id)
073    {
074        _pluginName = pluginName;
075        _id = id;
076    }
077
078    public void service(ServiceManager manager) throws ServiceException
079    {
080        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
081    }
082
083    @Override
084    public void configure(Configuration configuration) throws ConfigurationException
085    {
086        _label = _parseI18nizableText(configuration, _pluginName, "label");
087        _description = _parseI18nizableText(configuration, _pluginName, "description");
088    }
089
090    @Override
091    public String getId()
092    {
093        return _id;
094    }
095
096    @Override
097    public I18nizableText getLabel()
098    {
099        return _label;
100    }
101
102    @Override
103    public I18nizableText getDescription()
104    {
105        return _description;
106    }
107
108    @Override
109    public EnumeratorDefinition getEnumeratorDefinition(Collection<String> contentTypes, Configuration configuration)
110    {
111        // Default null, override to set an enumerator.
112        return null;
113    }
114
115    @Override
116    public String getContentTypeId()
117    {
118        return null;
119    }
120
121    @Override
122    public boolean isSearchable()
123    {
124        // Default to true: override when the property is not searchable.
125        return true;
126    }
127
128    @Override
129    public boolean isDisplayable()
130    {
131        // Default to true: override when the property is not displayable.
132        return true;
133    }
134
135    @Override
136    public String getWidget()
137    {
138        return null;
139    }
140
141    public Map<String, I18nizableText> getWidgetParameters()
142    {
143        return Collections.emptyMap();
144    }
145
146    @Override
147    public String getRenderer()
148    {
149        return null;
150    }
151
152    @Override
153    public String getConverter()
154    {
155        return null;
156    }
157
158    @Override
159    public Integer getColumnWidth()
160    {
161        return null;
162    }
163
164    @Override
165    public void index(Content content, SolrInputDocument document)
166    {
167        SearchField searchField = getSearchField();
168        String fieldName = searchField != null ? searchField.getName() : null;
169        String sortFieldName = searchField != null ? searchField.getSortField() : null;
170        Object value = getValue(content);
171
172        if (value != null && fieldName != null)
173        {
174            if (isMultiple())
175            {
176                if (value instanceof Collection< ? > || value.getClass().isArray())
177                {
178                    // FIXME can lead to indexation mistakes if collection or array of dates
179                    document.setField(fieldName, value);
180                }
181                else
182                {
183                    _indexSingleValue(document, fieldName, value);
184                }
185            }
186            else
187            {
188                _indexSingleValue(document, fieldName, value);
189            }
190
191            Object sortValue = getSortValue(content);
192            if (isSortable() && sortValue != null && sortFieldName != null && !fieldName.equals(sortFieldName))
193            {
194                document.setField(sortFieldName, sortValue);
195            }
196        }
197    }
198    
199    private void _indexSingleValue(SolrInputDocument document, String fieldName, Object value)
200    {
201        if (value instanceof Date)
202        {
203            document.setField(fieldName, SolrIndexer.dateFormat().format((Date) value));
204        }
205        else
206        {
207            // Index the single value.
208            document.setField(fieldName, value);
209        }
210    }
211
212    @Override
213    public Object getFullValue(Content content)
214    {
215        // Default to getValue(), override to provide a specific "full" value.
216        return getValue(content);
217    }
218
219    @Override
220    public Object getSortValue(Content content)
221    {
222        // Default to getValue(), override to provide a specific sort value.
223        return null;
224    }
225
226    @Override
227    public Collection<SchemaDefinition> getSchemaDefinitions()
228    {
229        List<SchemaDefinition> definitions = new ArrayList<>();
230
231        SearchField searchField = getSearchField();
232        if (searchField != null)
233        {
234            String name = searchField.getName();
235            String sortFieldName = searchField.getSortField();
236            String facetFieldName = searchField.getFacetField();
237            boolean multiple = isMultiple();
238            String type = SchemaHelper.getSchemaType(getType());
239
240            if (type != null)
241            {
242                definitions.add(new FieldDefinition(name, type, multiple, false));
243
244                if (sortFieldName != null && !sortFieldName.equals(name))
245                {
246                    definitions.add(new FieldDefinition(sortFieldName, type, false, false));
247                }
248
249                if (facetFieldName != null && !facetFieldName.equals(name))
250                {
251                    definitions.add(new FieldDefinition(facetFieldName, type, multiple, true));
252                    definitions.add(new CopyFieldDefinition(name, facetFieldName));
253                }
254            }
255        }
256
257        return definitions;
258    }
259
260    /**
261     * Parse an i18n text.
262     * 
263     * @param config the configuration to use.
264     * @param pluginName the current plugin name.
265     * @param name the child name.
266     * @return the i18n text.
267     * @throws ConfigurationException if the configuration is not valid.
268     */
269    protected I18nizableText _parseI18nizableText(Configuration config, String pluginName, String name) throws ConfigurationException
270    {
271        return I18nizableText.parseI18nizableText(config.getChild(name), "plugin." + pluginName);
272    }
273
274    /**
275     * Get the value as a long.
276     * 
277     * @param value The value as an object.
278     * @return The value as a long.
279     */
280    protected String parseString(Object value)
281    {
282        if (value instanceof String)
283        {
284            return (String) value;
285        }
286        else
287        {
288            throw new IllegalArgumentException("The value " + value + " for criterion " + getId() + " is not a valid String value.");
289        }
290    }
291
292    /**
293     * Get the value as a long.
294     * 
295     * @param value The value as an object.
296     * @return The value as a long.
297     */
298    protected long parseLong(Object value)
299    {
300        if (value instanceof Long)
301        {
302            return (Long) value;
303        }
304        else if (value instanceof String)
305        {
306            return Long.parseLong((String) value);
307        }
308        else
309        {
310            throw new IllegalArgumentException("The value " + value + " for criterion " + getId() + " is not a valid long value.");
311        }
312    }
313
314    /**
315     * Get the value as a double.
316     * 
317     * @param value The value as an object.
318     * @return The value as a double.
319     */
320    protected double parseDouble(Object value)
321    {
322        if (value instanceof Double)
323        {
324            return (Double) value;
325        }
326        else if (value instanceof String)
327        {
328            return Double.parseDouble((String) value);
329        }
330        else
331        {
332            throw new IllegalArgumentException("The value " + value + " for criterion " + getId() + " is not a valid double value.");
333        }
334    }
335
336    /**
337     * Get the value as a boolean.
338     * 
339     * @param value The value as an object, can be a Boolean or a String.
340     * @return The value as a boolean.
341     */
342    protected boolean parseBoolean(Object value)
343    {
344        if (value instanceof Boolean)
345        {
346            return (Boolean) value;
347        }
348        else if (value instanceof String)
349        {
350            return Boolean.parseBoolean((String) value);
351        }
352        else
353        {
354            throw new IllegalArgumentException("The value " + value + " for criterion " + getId() + " is not a valid boolean value.");
355        }
356    }
357
358    /**
359     * Get the value as a date.
360     * 
361     * @param value The value as an object, can be a Date or a String.
362     * @return The value as a Date object.
363     */
364    protected Date parseDate(Object value)
365    {
366        if (value instanceof Date)
367        {
368            return (Date) value;
369        }
370        else if (value instanceof String)
371        {
372            return (Date) ParameterHelper.castValue((String) value, ParameterType.DATE);
373        }
374        else
375        {
376            throw new IllegalArgumentException("The value " + value + " for criterion " + getId() + " is not a valid date value.");
377        }
378    }
379    
380    /**
381     * Get the value as array of {@link UserIdentity}
382     * @param value The value to parse
383     * @return A array of {@link UserIdentity}
384     */
385    @SuppressWarnings("unchecked")
386    protected UserIdentity[] parseUserArray (Object value)
387    {
388        if (value instanceof UserIdentity)
389        {
390            return new UserIdentity[] {(UserIdentity) value};
391        }
392        else if (value instanceof UserIdentity[])
393        {
394            return (UserIdentity[]) value;
395        }
396        else if (value instanceof List<?>)
397        {
398            return (UserIdentity[]) ((List<?>) value).stream().map(v -> new UserIdentity(((Map<String, String>) v).get("login"), ((Map<String, String>) v).get("populationId"))).toArray();
399        }
400        else if (value instanceof Map)
401        {
402            Map<String, Object> userValue = (Map<String, Object>) value;
403            
404            String login = (String) userValue.get("login");
405            String populationId = (String) userValue.get("populationId");
406            
407            if (login != null && populationId != null)
408            {
409                return new UserIdentity[] {new UserIdentity(login, populationId)};
410            }
411            return new UserIdentity[0];
412        }
413        else
414        {
415            throw new IllegalArgumentException("The value " + value + " for criterion " + getId() + " is not a valid UserIdentity values.");
416        }
417    }
418
419    /**
420     * Get the value as a array of String
421     * @param value The value as an object to parse
422     * @return The values as a String array
423     */
424    protected String[] parseStringArray(Object value)
425    {
426        if (value instanceof String)
427        {
428            return new String[] {(String) value};
429        }
430        else if (value instanceof String[])
431        {
432            return (String[]) value;
433        }
434        else if (value instanceof List<?>)
435        {
436            return ((List<?>) value).stream()
437                    .map(v -> String.valueOf(v))
438                    .toArray(String[]::new);
439        }
440        else
441        {
442            throw new IllegalArgumentException("The value " + value + " for criterion " + getId() + " is not a valid String[] values.");
443        }
444    }
445
446}