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