001/*
002 *  Copyright 2020 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.web.parameters;
017
018import java.util.ArrayList;
019import java.util.Collection;
020import java.util.HashMap;
021import java.util.List;
022import java.util.Map;
023
024import org.apache.avalon.framework.component.Component;
025import org.apache.commons.lang.StringUtils;
026
027import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
028import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder;
029import org.ametys.plugins.repository.data.holder.group.impl.ModelAwareRepeater;
030import org.ametys.plugins.repository.data.holder.group.impl.ModelAwareRepeaterEntry;
031import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareRepeater;
032import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareRepeaterEntry;
033import org.ametys.plugins.repository.model.RepeaterDefinition;
034import org.ametys.runtime.i18n.I18nizableText;
035import org.ametys.runtime.model.DefinitionAndValue;
036import org.ametys.runtime.model.ElementDefinition;
037import org.ametys.runtime.model.ModelHelper;
038import org.ametys.runtime.model.ModelItem;
039import org.ametys.runtime.model.ModelItemContainer;
040import org.ametys.runtime.model.type.ElementType;
041import org.ametys.runtime.plugin.component.AbstractLogEnabled;
042
043/**
044 * Manager to handle parameters
045 */
046public class ParametersManager extends AbstractLogEnabled implements Component               
047{
048    /** Avalon Role */
049    public static final String ROLE = ParametersManager.class.getName();
050    
051    /** Constant for untouched binary metadata. */
052    protected static final String _PARAM_UNTOUCHED_BINARY = "untouched";
053    
054    /**
055     * Set parameters values to the data holder for each model items
056     * @param dataHolder the data holder
057     * @param modelItems the list of model items
058     * @param values the values
059     * @return the map of possible errors
060     */
061    public Map<String, List<I18nizableText>> setParameterValues(ModifiableModelAwareDataHolder dataHolder, Collection<? extends ModelItem> modelItems, Map<String, Object> values)
062    {
063        Map<String, List<I18nizableText>> allErrors = new HashMap<>();
064        
065        // FIXME CMS-10275
066        for (ModelItem definition : modelItems)
067        {
068            _setParameterValues(definition, values, dataHolder, "", allErrors);
069        }
070        
071        return allErrors;
072    }
073    
074    /**
075     * Set parameters values of the model item
076     * @param def the model item definition
077     * @param values the value
078     * @param dataHolder the data holder
079     * @param prefix the prefix to get the parameter values
080     * @param allErrors the map of possible errors
081     */
082    protected void _setParameterValues(ModelItem def, Map<String, Object> values, ModifiableModelAwareDataHolder dataHolder, String prefix, Map<String, List<I18nizableText>> allErrors)
083    {
084        if (def instanceof ElementDefinition)
085        {
086            ElementDefinition definition = (ElementDefinition) def;
087            Map<String, DefinitionAndValue> definitionAndValues = _getBrothersDefinitionAndValues(prefix, values, definition);
088            boolean isGroupSwitchOn = ModelHelper.isGroupSwitchOn(definition, values);
089            boolean isDisabled = ModelHelper.evaluateDisableConditions(definition.getDisableConditions(), definitionAndValues, getLogger());
090            
091            if (isGroupSwitchOn && !isDisabled)
092            {
093            
094                Object submittedValue = values.get(prefix + definition.getName());
095                ElementType parameterType = definition.getType();
096                Object value = parameterType.fromJSONForClient(submittedValue);
097                
098                // TODO RUNTIME-2897: call the validateValue without boolean when multiple values are managed in enumerators
099                List<I18nizableText> errors = ModelHelper.validateValue(definition, value, false);
100                if (!errors.isEmpty())
101                {
102                    allErrors.put(definition.getName(), errors);
103                    return;
104                }
105        
106                String typeId = def.getType().getId();
107                if (_PARAM_UNTOUCHED_BINARY.equals(value)
108                        /* keeps the value of an empty password field */
109                        || value == null && org.ametys.runtime.model.type.ModelItemTypeConstants.PASSWORD_ELEMENT_TYPE_ID.equals(typeId))
110                {
111                    // do not set value
112                    return;
113                }
114                dataHolder.setValue(definition.getName(), value);
115            }
116        }
117        else if (def instanceof RepeaterDefinition)
118        {
119            RepeaterDefinition definition = (RepeaterDefinition) def;
120            ModifiableModelAwareRepeater repeater = dataHolder.getRepeater(definition.getName(), true);
121            
122            // First move the entries according to the given previous positions
123            repeater.moveEntries(_getRepeaterPositionsMapping(values, prefix + definition.getName()));
124            
125            // Then save the entries' parameter values
126            for (ModifiableModelAwareRepeaterEntry entry : repeater.getEntries())
127            {
128                List<ModelItem> childrenDefinitions = definition.getChildren();
129                String newPrefix = prefix + definition.getName() + "[" + entry.getPosition() + "]" + ModelItem.ITEM_PATH_SEPARATOR;
130                for (ModelItem childDefinition : childrenDefinitions)
131                {
132                    _setParameterValues(childDefinition, values, entry, newPrefix, allErrors);
133                }
134            }
135        }
136    }
137    
138    /**
139     * Get the definition and value pairs of the given definition brothers. The definition and value pairs are indexed by the parameter name
140     * @param prefix prefix to get the parameter values
141     * @param values all the parameter values
142     * @param definition the definition
143     * @return the definition and value pairs of the given definition brother
144     */
145    protected Map<String, DefinitionAndValue> _getBrothersDefinitionAndValues(String prefix, Map<String, Object> values, ElementDefinition definition)
146    {
147        Map<String, DefinitionAndValue> definitionAndValues = new HashMap<>();
148        
149        ModelItemContainer definitionContainer = definition.getParent() != null ? definition.getParent() : definition.getModel();
150        Collection< ? extends ModelItem> definitionBrothers;
151        definitionBrothers = definitionContainer.getModelItems();
152        for (ModelItem brother : definitionBrothers)
153        {
154            Object value = values.get(prefix + brother.getName());
155            if (brother instanceof ElementDefinition)
156            {
157                DefinitionAndValue definitionAndValue = new DefinitionAndValue<>(null, brother, value);
158                definitionAndValues.put(brother.getName(), definitionAndValue);
159            }
160        }
161        
162        return definitionAndValues;
163    }
164    
165    /**
166     * Get the repeater positions mapping with its old positions
167     * @param values the values
168     * @param repeaterPath the repeater path
169     * @return the repeater positions mapping
170     */
171    protected Map<Integer, Integer> _getRepeaterPositionsMapping(Map<String, Object> values, String repeaterPath)
172    {
173        Map<Integer, Integer> positionsMapping = new HashMap<>();
174
175        String sizeEntryName = "_" + repeaterPath + ModelItem.ITEM_PATH_SEPARATOR + "size";
176        if (values.containsKey(sizeEntryName))
177        {
178            int size = (int) values.get(sizeEntryName);
179            for (int position = 1; position <= size; position++)
180            {
181                String previousPositionEntryName = "_" + repeaterPath + "[" + position + "]" + ModelItem.ITEM_PATH_SEPARATOR + "previous-position";
182                if (values.containsKey(previousPositionEntryName))
183                {
184                    int previousPosition = (int) values.get("_" + repeaterPath + "[" + position + "]" + ModelItem.ITEM_PATH_SEPARATOR + "previous-position");
185                    positionsMapping.put(position, previousPosition);
186                }
187                else
188                {
189                    throw new IllegalArgumentException("The given values don't contain the previous position of the repeater entry '" + repeaterPath + "[" + position + "]" + "'.");
190                }
191            }
192            
193            return positionsMapping;
194        }
195        else
196        {
197            throw new IllegalArgumentException("The given values don't contain the size of the repeater at path '" + repeaterPath + "'.");
198        }
199    }
200    
201    /**
202     * Get the parameters values
203     * @param items the list of model item
204     * @param dataHolder the data holder
205     * @param prefix prefix to get the parameter values
206     * @return the parameters values
207     */
208    public Map<String, Object> getParametersValues(Collection<? extends ModelItem> items, ModelAwareDataHolder dataHolder, String prefix)
209    {
210        Map<String, Object> values = new HashMap<>();
211        
212        for (ModelItem item : items)
213        {
214            _addParameterValues(item, dataHolder, prefix, values);
215        }
216        
217        return values;
218    }
219
220    /**
221     * Add the parameter values to all values
222     * @param item the model item
223     * @param dataHolder the data holder
224     * @param prefix prefix to get the parameter values
225     * @param values all the values
226     */
227    protected void _addParameterValues(ModelItem item, ModelAwareDataHolder dataHolder, String prefix, Map<String, Object> values)
228    {
229        try
230        {
231            if (item instanceof ElementDefinition)
232            {
233                if (dataHolder.hasValue(item.getName()))
234                {
235                    ElementType type = ((ElementDefinition) item).getType();
236                    Object value = dataHolder.getValue(item.getName());
237                    
238                    values.put(prefix + item.getName(), type.valueToJSONForClient(value));
239                }
240            }
241            else if (item instanceof RepeaterDefinition)
242            {
243                if (dataHolder.hasValue(item.getName()))
244                {
245                    ModelAwareRepeater repeater = dataHolder.getRepeater(item.getName());
246                    for (ModelAwareRepeaterEntry entry: repeater.getEntries())
247                    {
248                        String newPrefix = prefix + item.getName() + "[" + entry.getPosition() + "]/";
249                        values.putAll(getParametersValues(((RepeaterDefinition) item).getChildren(), entry, newPrefix));
250                    }
251                    values.put(prefix + item.getName(), new ArrayList<>());
252                }
253            }
254        }
255        catch (Exception e) 
256        {
257            getLogger().error("Can't get values from parameter with name '{}'", item.getName(), e);
258        }
259    }
260    
261    /**
262     * Get the repeaters values
263     * @param items the list of model item
264     * @param dataHolder the data holder
265     * @param prefix prefix to get the parameter values
266     * @return the repeaters values
267     */
268    public List<Map<String, Object>> getRepeatersValues(Collection<ModelItem> items, ModelAwareDataHolder dataHolder, String prefix)
269    {
270        List<Map<String, Object>> results = new ArrayList<>();
271        
272        for (ModelItem item : items)
273        {
274            if (item instanceof RepeaterDefinition)
275            {
276                Map<String, Object> result = new HashMap<>();
277                
278                result.put("name", item.getName());
279                result.put("prefix", prefix);
280                
281                if (dataHolder.hasValue(item.getName()))
282                {
283                    ModelAwareRepeater repeater = dataHolder.getRepeater(item.getName());
284                    result.put("count", repeater.getSize());
285                    for (ModelAwareRepeaterEntry entry: repeater.getEntries())
286                    {
287                        StringBuilder newPrefix = new StringBuilder();
288                        newPrefix.append(prefix);
289                        newPrefix.append(item.getName()).append("[").append(entry.getPosition()).append("]/");
290                        results.addAll(getRepeatersValues(((RepeaterDefinition) item).getChildren(), entry, newPrefix.toString()));
291                    }
292                }
293                else
294                {
295                    result.put("count", 0);
296                }
297                
298                results.add(result);
299            }
300        }
301        
302        return results;
303    }
304    
305    /**
306     * True if the parameter has a value or a default value
307     * @param parameterPath the parameter path
308     * @param dataHolder the data holder
309     * @param defaultValue the default value
310     * @return true if the parameter has a value or a default value
311     */
312    protected boolean _hasParameterValueOrDefaultValue(String parameterPath, ModelAwareDataHolder dataHolder, Object defaultValue)
313    {
314        if (dataHolder.hasValue(parameterPath))
315        {
316            // If there is a value but it is empty, consider there is no value
317            Object value = dataHolder.getValue(parameterPath);
318            return value != null && (!(value instanceof Object[]) || (value instanceof Object[] && ((Object[]) value).length != 0));
319        }
320        else
321        {
322            if (defaultValue != null)
323            {
324                return defaultValue instanceof String ? StringUtils.isNotEmpty((String) defaultValue) : true;
325            }
326            else
327            {
328                return false;
329            }
330        }
331    }
332    
333    /**
334     * Get all parameters which start with prefix and change the name of the attribute removing this prefix
335     * @param parameterValues the parameter values
336     * @param prefix the prefix
337     * @return the map of filtered parameters
338     */
339    public Map<String, Object> getParametersStartWithPrefix(Map<String, Object> parameterValues, String prefix)
340    {
341        Map<String, Object> newParameterValues = new HashMap<>();
342        for (String name : parameterValues.keySet())
343        {
344            if (name.startsWith(prefix))
345            {
346                newParameterValues.put(StringUtils.substringAfter(name, prefix), parameterValues.get(name));
347            }
348        }
349        
350        return newParameterValues;
351    }
352    
353    /**
354     * Add prefix to all parameters
355     * @param parameterValues the parameters values
356     * @param prefix the prefix
357     * @return the map of parameters with its prefix
358     */
359    public Map<String, Object> addPrefixToParameters(Map<String, Object> parameterValues, String prefix)
360    {
361        Map<String, Object> newParameterValues = new HashMap<>();
362        for (String name : parameterValues.keySet())
363        {
364            newParameterValues.put(prefix + name, parameterValues.get(name));
365        }
366        
367        return newParameterValues;
368    }
369}