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.List;
021import java.util.Map;
022
023import org.apache.avalon.framework.service.ServiceException;
024import org.apache.avalon.framework.service.ServiceManager;
025import org.apache.avalon.framework.service.Serviceable;
026import org.apache.solr.common.SolrInputDocument;
027
028import org.ametys.cms.data.ametysobject.ModelAwareDataAwareAmetysObject;
029import org.ametys.cms.data.type.indexing.IndexableDataContext;
030import org.ametys.cms.data.type.indexing.IndexableElementType;
031import org.ametys.cms.model.properties.AbstractProperty;
032import org.ametys.cms.search.SearchField;
033import org.ametys.cms.search.model.SystemProperty;
034import org.ametys.cms.search.solr.schema.CopyFieldDefinition;
035import org.ametys.cms.search.solr.schema.FieldDefinition;
036import org.ametys.cms.search.solr.schema.SchemaDefinition;
037import org.ametys.core.user.UserIdentity;
038import org.ametys.core.user.UserManager;
039import org.ametys.core.util.I18nUtils;
040import org.ametys.core.util.date.AdaptableDate;
041import org.ametys.core.util.date.AdaptableDateParser;
042import org.ametys.plugins.core.user.UserHelper;
043import org.ametys.plugins.repository.AmetysObjectResolver;
044
045/**
046 * Abstract class providing standard behavior and helper methods for a {@link SystemProperty}.
047 * @param <T> type of the property values
048 * @param <X> type of ametys object supported by this property
049 */
050public abstract class AbstractSystemProperty<T, X extends ModelAwareDataAwareAmetysObject> extends AbstractProperty<T, X> implements SystemProperty<T, X>, Serviceable
051{
052    /** The i18n utils. */
053    protected I18nUtils _i18nUtils;
054    /** The user helper */
055    protected UserHelper _userHelper;
056    /** The ametys object resolver */
057    protected AmetysObjectResolver _resolver;
058    /** The user manager */
059    protected UserManager _userManager;
060    /** The service manager */
061    protected ServiceManager _manager;
062
063    public void service(ServiceManager manager) throws ServiceException
064    {
065        _manager = manager;
066        _i18nUtils = (I18nUtils) manager.lookup(I18nUtils.ROLE);
067        _userHelper = (UserHelper) manager.lookup(UserHelper.ROLE);
068        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
069        _userManager = (UserManager) manager.lookup(UserManager.ROLE);
070    }
071    
072    @Override
073    protected String _getNameConfigurationAttribute()
074    {
075        return "id";
076    }
077
078    @Override
079    public void indexValue(SolrInputDocument document, X ametysObject, IndexableDataContext context)
080    {
081        Object value = getValue(ametysObject);
082        
083        SearchField searchField = getSearchField();
084        
085        if (value == null || searchField == null || searchField.getName() == null)
086        {
087            // Nothing to index
088            return;
089        }
090        
091        IndexableElementType<T> type = getType();
092        if (type.getManagedClass().isInstance(value))
093        {
094            Object valueToIndex = type.getSingleValueToIndex(type.getManagedClass().cast(value));
095            document.addField(searchField.getName(), valueToIndex);
096        }
097        else if (type.getManagedClassArray().isInstance(value))
098        {
099            T[] values = type.getManagedClassArray().cast(value);
100            for (T singleValue : values)
101            {
102                Object valueToIndex = type.getSingleValueToIndex(singleValue);
103                document.addField(searchField.getName(), valueToIndex);
104            }
105        }
106        
107        // Index sort field
108        Object sortValue = getSortValue(ametysObject);
109        if (isSortable() && sortValue != null && searchField.getSortField() != null && !searchField.getName().equals(searchField.getSortField()))
110        {
111            document.setField(searchField.getSortField(), sortValue);
112        }
113    }
114
115    @Override
116    public Object getSortValue(X ametysObject)
117    {
118        // Default to getValue(), override to provide a specific sort value.
119        Object value = getValue(ametysObject);
120        
121        if (value == null)
122        {
123            return null;
124        }
125
126        if (isMultiple() && value instanceof Object[] && ((Object[]) value).length > 0)
127        {
128            return ((Object[]) value)[0]; // return the first value
129        }
130        else
131        {
132            return value;
133        }
134    }
135    
136    @Override
137    public Collection<SchemaDefinition> getSchemaDefinitions()
138    {
139        List<SchemaDefinition> definitions = new ArrayList<>();
140
141        SearchField searchField = getSearchField();
142        if (searchField != null)
143        {
144            String name = searchField.getName();
145            String sortFieldName = searchField.getSortField();
146            String facetFieldName = searchField.getFacetField();
147            boolean multiple = isMultiple();
148            String type = getType().getSchemaType();
149
150            if (type != null)
151            {
152                definitions.add(new FieldDefinition(name, type, multiple, false));
153
154                if (sortFieldName != null && !sortFieldName.equals(name))
155                {
156                    definitions.add(new FieldDefinition(sortFieldName, type, false, false));
157                }
158
159                if (facetFieldName != null && !facetFieldName.equals(name))
160                {
161                    // By default the index value in field name will be automatically copy in facet field
162                    // So we do not need the index facet field manually
163                    definitions.add(new FieldDefinition(facetFieldName, type, multiple, true));
164                    definitions.add(new CopyFieldDefinition(name, facetFieldName));
165                }
166            }
167        }
168
169        return definitions;
170    }
171    
172    /**
173     * Get the value as a long.
174     * 
175     * @param value The value as an object.
176     * @return The value as a long.
177     */
178    protected String parseString(Object value)
179    {
180        if (value instanceof String)
181        {
182            return (String) value;
183        }
184        else
185        {
186            throw new IllegalArgumentException("The value " + value + " for criterion " + getName() + " is not a valid String value.");
187        }
188    }
189
190    /**
191     * Get the value as a long.
192     * 
193     * @param value The value as an object.
194     * @return The value as a long.
195     */
196    protected long parseLong(Object value)
197    {
198        if (value instanceof Number)
199        {
200            return ((Number) value).longValue();
201        }
202        else if (value instanceof String)
203        {
204            return Long.parseLong((String) value);
205        }
206        else
207        {
208            throw new IllegalArgumentException("The value " + value + " for criterion " + getName() + " is not a valid long value.");
209        }
210    }
211
212    /**
213     * Get the value as a double.
214     * 
215     * @param value The value as an object.
216     * @return The value as a double.
217     */
218    protected double parseDouble(Object value)
219    {
220        if (value instanceof Number)
221        {
222            return ((Number) value).doubleValue();
223        }
224        else if (value instanceof String)
225        {
226            return Double.parseDouble((String) value);
227        }
228        else
229        {
230            throw new IllegalArgumentException("The value " + value + " for criterion " + getName() + " is not a valid double value.");
231        }
232    }
233
234    /**
235     * Get the value as a boolean.
236     * 
237     * @param value The value as an object, can be a Boolean or a String.
238     * @return The value as a boolean.
239     */
240    protected boolean parseBoolean(Object value)
241    {
242        if (value instanceof Boolean)
243        {
244            return (Boolean) value;
245        }
246        else if (value instanceof String)
247        {
248            return Boolean.parseBoolean((String) value);
249        }
250        else
251        {
252            throw new IllegalArgumentException("The value " + value + " for criterion " + getName() + " is not a valid boolean value.");
253        }
254    }
255
256    /**
257     * Get the value as a date.
258     * 
259     * @param value The value as an object, can be a Date or a String.
260     * @return The value as a Date object.
261     */
262    protected AdaptableDate parseDate(Object value)
263    {
264        if (value instanceof AdaptableDate)
265        {
266            return (AdaptableDate) value;
267        }
268        else if (value instanceof String)
269        {
270            return AdaptableDateParser.parse((String) value);
271        }
272        else
273        {
274            throw new IllegalArgumentException("The value " + value + " for criterion " + getName() + " is not a valid date value.");
275        }
276    }
277    
278    /**
279     * Get the value as array of {@link UserIdentity}
280     * @param value The value to parse
281     * @return A array of {@link UserIdentity}
282     */
283    @SuppressWarnings("unchecked")
284    protected UserIdentity[] parseUserArray (Object value)
285    {
286        if (value instanceof UserIdentity)
287        {
288            return new UserIdentity[] {(UserIdentity) value};
289        }
290        else if (value instanceof UserIdentity[])
291        {
292            return (UserIdentity[]) value;
293        }
294        else if (value instanceof List<?>)
295        {
296            return (UserIdentity[]) ((List<?>) value).stream().map(v -> _userHelper.json2userIdentity((Map<String, String>) v)).toArray();
297        }
298        else if (value instanceof Map)
299        {
300            UserIdentity userIdentity = _userHelper.json2userIdentity((Map<String, String>) value);
301            if (userIdentity != null)
302            {
303                return new UserIdentity[] {userIdentity};
304            }
305            return new UserIdentity[0];
306        }
307        else
308        {
309            throw new IllegalArgumentException("The value " + value + " for criterion " + getName() + " is not a valid UserIdentity values.");
310        }
311    }
312
313    /**
314     * Get the value as a array of String
315     * @param value The value as an object to parse
316     * @return The values as a String array
317     */
318    protected String[] parseStringArray(Object value)
319    {
320        if (value instanceof String)
321        {
322            return new String[] {(String) value};
323        }
324        else if (value instanceof String[])
325        {
326            return (String[]) value;
327        }
328        else if (value instanceof List<?>)
329        {
330            return ((List<?>) value).stream()
331                    .map(v -> String.valueOf(v))
332                    .toArray(String[]::new);
333        }
334        else
335        {
336            throw new IllegalArgumentException("The value " + value + " for criterion " + getName() + " is not a valid String[] values.");
337        }
338    }
339}