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 String dataPath = prefix + definition.getName(); 095 Object submittedValue = values.get(dataPath); 096 ElementType parameterType = definition.getType(); 097 Object value = parameterType.fromJSONForClient(submittedValue, DataContext.newInstance().withDataPath(dataPath)); 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 String dataPath = prefix + item.getName(); 240 // Put empty values in the values map in order make the difference between empty and not present values 241 if (dataHolder.hasValueOrEmpty(item.getName())) 242 { 243 ElementType type = ((ElementDefinition) item).getType(); 244 Object value = dataHolder.getValue(item.getName()); 245 246 values.put(dataPath, type.valueToJSONForClient(value, DataContext.newInstance())); 247 } 248 } 249 else if (item instanceof RepeaterDefinition) 250 { 251 if (dataHolder.hasValue(item.getName())) 252 { 253 ModelAwareRepeater repeater = dataHolder.getRepeater(item.getName()); 254 for (ModelAwareRepeaterEntry entry: repeater.getEntries()) 255 { 256 String newPrefix = prefix + item.getName() + "[" + entry.getPosition() + "]" + ModelItem.ITEM_PATH_SEPARATOR; 257 values.putAll(getParametersValues(((RepeaterDefinition) item).getChildren(), entry, newPrefix)); 258 } 259 values.put(prefix + item.getName(), new ArrayList<>()); 260 } 261 } 262 } 263 catch (Exception e) 264 { 265 getLogger().error("Can't get values from parameter with name '{}'", item.getName(), e); 266 } 267 } 268 269 /** 270 * Get the repeaters values 271 * @param items the list of model item 272 * @param dataHolder the data holder 273 * @param prefix prefix to get the parameter values 274 * @return the repeaters values 275 */ 276 public List<Map<String, Object>> getRepeatersValues(Collection<ModelItem> items, ModelAwareDataHolder dataHolder, String prefix) 277 { 278 List<Map<String, Object>> results = new ArrayList<>(); 279 280 for (ModelItem item : items) 281 { 282 if (item instanceof RepeaterDefinition) 283 { 284 Map<String, Object> result = new HashMap<>(); 285 286 result.put("name", item.getName()); 287 result.put("prefix", prefix); 288 289 if (dataHolder.hasValue(item.getName())) 290 { 291 ModelAwareRepeater repeater = dataHolder.getRepeater(item.getName()); 292 result.put("count", repeater.getSize()); 293 for (ModelAwareRepeaterEntry entry: repeater.getEntries()) 294 { 295 StringBuilder newPrefix = new StringBuilder(); 296 newPrefix.append(prefix); 297 newPrefix.append(item.getName()).append("[").append(entry.getPosition()).append("]/"); 298 results.addAll(getRepeatersValues(((RepeaterDefinition) item).getChildren(), entry, newPrefix.toString())); 299 } 300 } 301 else 302 { 303 result.put("count", 0); 304 } 305 306 results.add(result); 307 } 308 } 309 310 return results; 311 } 312 313 /** 314 * True if the parameter has a value or a default value 315 * @param parameterPath the parameter path 316 * @param dataHolder the data holder 317 * @param defaultValue the default value 318 * @return true if the parameter has a value or a default value 319 */ 320 protected boolean _hasParameterValueOrDefaultValue(String parameterPath, ModelAwareDataHolder dataHolder, Object defaultValue) 321 { 322 if (dataHolder.hasValue(parameterPath)) 323 { 324 return true; 325 } 326 else 327 { 328 if (defaultValue != null) 329 { 330 return defaultValue instanceof String ? StringUtils.isNotEmpty((String) defaultValue) : true; 331 } 332 else 333 { 334 return false; 335 } 336 } 337 } 338 339 /** 340 * Get all parameters which start with prefix and change the name of the attribute removing this prefix 341 * @param parameterValues the parameter values 342 * @param prefix the prefix 343 * @return the map of filtered parameters 344 */ 345 public Map<String, Object> getParametersStartWithPrefix(Map<String, Object> parameterValues, String prefix) 346 { 347 Map<String, Object> newParameterValues = new HashMap<>(); 348 for (String name : parameterValues.keySet()) 349 { 350 if (name.startsWith(prefix)) 351 { 352 newParameterValues.put(StringUtils.substringAfter(name, prefix), parameterValues.get(name)); 353 } 354 } 355 356 return newParameterValues; 357 } 358 359 /** 360 * Add prefix to all parameters 361 * @param parameterValues the parameters values 362 * @param prefix the prefix 363 * @return the map of parameters with its prefix 364 */ 365 public Map<String, Object> addPrefixToParameters(Map<String, Object> parameterValues, String prefix) 366 { 367 Map<String, Object> newParameterValues = new HashMap<>(); 368 for (String name : parameterValues.keySet()) 369 { 370 newParameterValues.put(prefix + name, parameterValues.get(name)); 371 } 372 373 return newParameterValues; 374 } 375}