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}