001/* 002 * Copyright 2025 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.data.holder; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.Collection; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Optional; 025import java.util.Set; 026 027import org.apache.avalon.framework.component.Component; 028import org.apache.avalon.framework.configuration.ConfigurationException; 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.avalon.framework.service.Serviceable; 032import org.apache.commons.lang3.StringUtils; 033import org.apache.commons.lang3.tuple.Pair; 034 035import org.ametys.cms.data.ContentValue; 036import org.ametys.cms.data.ametysobject.ModelAwareDataAwareAmetysObject; 037import org.ametys.cms.data.type.ModelItemTypeConstants; 038import org.ametys.cms.model.ContentElementDefinition; 039import org.ametys.cms.search.model.SystemProperty; 040import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 041import org.ametys.plugins.repository.data.holder.DataHolder; 042import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 043import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeater; 044import org.ametys.plugins.repository.data.holder.impl.DataHolderHelper; 045import org.ametys.plugins.repository.data.holder.values.SynchronizableRepeater; 046import org.ametys.plugins.repository.data.holder.values.SynchronizableValue; 047import org.ametys.plugins.repository.data.holder.values.SynchronizableValue.Mode; 048import org.ametys.plugins.repository.data.holder.values.SynchronizationContext; 049import org.ametys.plugins.repository.data.holder.values.UntouchedValue; 050import org.ametys.plugins.repository.model.CompositeDefinition; 051import org.ametys.plugins.repository.model.RepeaterDefinition; 052import org.ametys.runtime.model.DefinitionAndValue; 053import org.ametys.runtime.model.DefinitionContext; 054import org.ametys.runtime.model.Model; 055import org.ametys.runtime.model.ModelHelper; 056import org.ametys.runtime.model.ModelItem; 057import org.ametys.runtime.model.ModelItemAccessor; 058import org.ametys.runtime.model.ModelViewItem; 059import org.ametys.runtime.model.View; 060import org.ametys.runtime.model.ViewItemContainer; 061import org.ametys.runtime.model.disableconditions.DisableCondition; 062import org.ametys.runtime.model.exception.BadItemTypeException; 063import org.ametys.runtime.model.exception.UndefinedItemPathException; 064import org.ametys.runtime.model.type.ElementType; 065 066/** 067 * {@link DisableCondition} for model aware {@link DataHolder} 068 */ 069public class DataHolderRelativeDisableConditionsHelper implements Component, Serviceable 070{ 071 /** The component role. */ 072 public static final String ROLE = DataHolderRelativeDisableConditionsHelper.class.getName(); 073 074 /** The contextual parameter key for synchronization context */ 075 public static final String SYNCHRONIZATION_CONTEXT_PARAMETER_KEY = "synchronizationContext"; 076 /** The JSON info containing exploded conditions when a condition points to an element that is in a distant content */ 077 public static final String EXPLODED_DISABLE_CONDITIONS = "explodedConditions"; 078 079 /** The contextual parameter key for data holder */ 080 protected static final String __DATA_HOLDER_PARAMETER_KEY = "dataHolder"; 081 /** The contextual parameter key for old condition path */ 082 protected static final String __OLD_CONDITION_PATH_PARAMETER_KEY = "oldCondtitionPath"; 083 084 private SystemPropertyExtensionPoint _systemPropertyExtensionPoint; 085 086 public void service(ServiceManager manager) throws ServiceException 087 { 088 _systemPropertyExtensionPoint = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE); 089 } 090 091 /** 092 * Retrieves the {@link SystemProperty} if the given data path represents one 093 * @param dataPath the data path of the system property to retrieve 094 * @return the system property or <code>null</code> if the given data path does not represent a system property 095 */ 096 public SystemProperty getSystemProperty(String dataPath) 097 { 098 String dataPathLastSegment = dataPath.contains(ModelItem.ITEM_PATH_SEPARATOR) 099 ? StringUtils.substring(dataPath, dataPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR)) 100 : dataPath; 101 102 return _systemPropertyExtensionPoint.hasExtension(dataPathLastSegment) 103 ? _systemPropertyExtensionPoint.getExtension(dataPathLastSegment) 104 : null; 105 } 106 107 /** 108 * Retrieves all model items of the given relative path to the system property 109 * @param condition the disable condition 110 * @param conditionPath the absolute path of the disable condition 111 * @param model the model containing the definition 112 * @param definition the model item defining this disable condition 113 * @param systemProperty the system property targeted by the condition 114 * @return all model items of the given relative path 115 * @throws UndefinedItemPathException if there is no item defined at the given path 116 * @throws IllegalArgumentException if the given path is null or empty 117 */ 118 public List<ModelItem> getAllModelItemsInPathToSystemProperty(DisableCondition condition, String conditionPath, Model model, ModelItem definition, SystemProperty systemProperty) throws UndefinedItemPathException, IllegalArgumentException 119 { 120 List<ModelItem> allModelItemsInPath = new ArrayList<>(); 121 122 if (conditionPath.contains(ModelItem.ITEM_PATH_SEPARATOR)) 123 { 124 String conditionParentPath = StringUtils.substring(conditionPath, 0, conditionPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR)); 125 allModelItemsInPath.addAll(ModelHelper.getAllModelItemsInPath(conditionParentPath, List.of(model))); 126 127 ModelItem parentModelItem = allModelItemsInPath.get(allModelItemsInPath.size() - 1); 128 if (!(ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID.equals(parentModelItem.getType().getId()))) 129 { 130 String message = String.format("Disable conditions: the disable condition '%s' on model item '%s' in model '%s' references a system property on a non content element '%s'", condition.getName(), definition.getPath(), model.getId(), conditionParentPath); 131 throw new IllegalArgumentException(message); 132 } 133 } 134 135 allModelItemsInPath.add(systemProperty); 136 return allModelItemsInPath; 137 } 138 139 /** 140 * Check a segment of the condition path 141 * @param condition the disable condition 142 * @param model the model containing the definition 143 * @param definition the model item defining this disable condition 144 * @param segmentModelItem the model item concerned by the segment 145 * @param segmentIndex the index of the segment in the path 146 * @param conditionPathSize the number of segments in the condition path 147 * @throws ConfigurationException if the segment can not be used in the condition path 148 */ 149 public void checkModelItemPathSegment(DisableCondition condition, Model model, ModelItem definition, ModelItem segmentModelItem, int segmentIndex, int conditionPathSize) throws ConfigurationException 150 { 151 if (segmentModelItem instanceof ContentElementDefinition && segmentIndex < conditionPathSize - 2) 152 { 153 // A reference to a content attribute must specify an item at root of this content, groups inside the distant content and multiple joins are not allowed 154 String message = String.format("Disable conditions: the disable condition '%s' on model item '%s' in model '%s' references an item that is not at root of the linked content", condition.getName(), definition.getPath(), model.getId()); 155 throw new ConfigurationException(message); 156 } 157 158 if (segmentModelItem instanceof RepeaterDefinition repeaterDefinition && !(definition.getPath().startsWith(repeaterDefinition.getPath()))) 159 { 160 // the definition holding the condition must be in the same repeater 161 String message = String.format("Disable conditions: the disable condition '%s' on model item '%s' in model '%s' references an item that is in a repeater", condition.getName(), definition.getPath(), model.getId()); 162 throw new ConfigurationException(message); 163 } 164 } 165 166 /** 167 * Retrieves the additional contextual parameters to use to extract values 168 * @param condition the disable condition 169 * @param oldDataPath the old path of the evaluated data. Needed to get stored value if the data has been moved 170 * @param dataHolder the data holder 171 * @return the {@link DefinitionAndValue} corresponding to the condition identifier 172 */ 173 public Map<String, Object> getAdditionalContextualParameters(DisableCondition condition, Optional<String> oldDataPath, ModelAwareDataHolder dataHolder) 174 { 175 Map<String, Object> contextualParameters = new HashMap<>(); 176 177 contextualParameters.put(__DATA_HOLDER_PARAMETER_KEY, dataHolder); 178 179 Optional<String> oldConditionAbsoluteDataPath = oldDataPath.map(path -> ModelHelper.getDisableConditionAbsolutePath(condition, path)); 180 contextualParameters.put(__OLD_CONDITION_PATH_PARAMETER_KEY, oldConditionAbsoluteDataPath); 181 182 return contextualParameters; 183 } 184 185 /** 186 * Check if the condition concerns a item in a repeater but with no specified entry position 187 * This is the case of external conditions evaluated for new repeater entries 188 * @param dataHolder the data holder 189 * @param conditionDataPath the absolute data path of the condition 190 * @return <code>true</code> if the condition is evaluated for a new repeater entry, <code>false</code> otherwise 191 */ 192 public boolean doesEvaluateConditionForNewRepeaterEntry(ModelAwareDataHolder dataHolder, String conditionDataPath) 193 { 194 ModelItem definition = dataHolder.getDefinition(conditionDataPath); 195 if (definition.getParent() != null && definition.getParent() instanceof RepeaterDefinition) 196 { 197 String[] pathSegments = StringUtils.split(conditionDataPath, ModelItem.ITEM_PATH_SEPARATOR); 198 String repeaterPathSegment = pathSegments[pathSegments.length - 2]; 199 return !DataHolderHelper.isRepeaterEntryPath(repeaterPathSegment); 200 } 201 else 202 { 203 return false; 204 } 205 } 206 207 /** 208 * Retrieves the model item accessors relative to the disable condition 209 * @param <T> Type of object holding the data to evaluate 210 * @param object the object holding the data to evaluate and the condition value 211 * @return the model item accessors relative to the disable condition 212 */ 213 public <T> Optional<Collection<? extends ModelItemAccessor>> getModelItemAccessors(Optional<T> object) 214 { 215 return object.filter(ModelAwareDataHolder.class::isInstance) 216 .map(ModelAwareDataHolder.class::cast) 217 .map(ModelAwareDataHolder::getModel); 218 } 219 220 /** 221 * Check if the values {@link Map} contains a value for the condition 222 * @param modelItemAccessors the relative accessors to the given condition path 223 * @param conditionDataPath the absolute data path of the condition 224 * @param values the values {@link Map} 225 * @param contextualParameters the contextual parameters 226 * @return <code>true</code> if there is a value (even empty) for the condition in the values {@link Map}, <code>false</code> otherwise 227 * @throws BadItemTypeException if a value does not correspond to the model of given object 228 */ 229 public boolean containsValue(Collection<? extends ModelItemAccessor> modelItemAccessors, String conditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) throws BadItemTypeException 230 { 231 String[] conditionDataPathSegments = StringUtils.split(conditionDataPath, ModelItem.ITEM_PATH_SEPARATOR); 232 233 ModelItem modelItem = ModelHelper.getModelItem(conditionDataPathSegments[0], modelItemAccessors); 234 if (conditionDataPathSegments.length == 1) 235 { 236 return _containsElementValue(modelItem, conditionDataPathSegments[0], values, contextualParameters); 237 } 238 else 239 { 240 String subConditionDataPath = StringUtils.join(conditionDataPathSegments, ModelItem.ITEM_PATH_SEPARATOR, 1, conditionDataPathSegments.length); 241 if (modelItem instanceof CompositeDefinition compositeDefinition) 242 { 243 return _doesCompositeContainValue(compositeDefinition, conditionDataPathSegments[0], subConditionDataPath, values, contextualParameters); 244 } 245 else if (modelItem instanceof RepeaterDefinition repeaterDefinition) 246 { 247 return _doesRepeaterContainValue(repeaterDefinition, conditionDataPathSegments[0], subConditionDataPath, values, contextualParameters); 248 } 249 else if (modelItem instanceof ContentElementDefinition contentElementDefinition) 250 { 251 return _containsElementValue(contentElementDefinition, conditionDataPathSegments[0], values, contextualParameters); 252 } 253 else 254 { 255 throw new BadItemTypeException("Unable to retrieve the value at the given path. the the segment '" + conditionDataPathSegments[0] + "' is not a model item accessor"); 256 } 257 } 258 } 259 260 @SuppressWarnings("unchecked") 261 private boolean _doesCompositeContainValue(CompositeDefinition compositeDefinition, String compositeName, String subConditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) 262 { 263 if (!values.containsKey(compositeName)) 264 { 265 // There is no corresponding value in the map 266 return false; 267 } 268 269 Object value = values.get(compositeName); 270 if (value == null) 271 { 272 // An empty value has been found 273 return true; 274 } 275 276 if (value instanceof UntouchedValue) 277 { 278 // If value is untouched, we should check in stored values 279 return false; 280 } 281 282 if (value instanceof Map) 283 { 284 return containsValue(List.of(compositeDefinition), subConditionDataPath, (Map<String, Object>) value, contextualParameters); 285 } 286 else 287 { 288 throw new BadItemTypeException("Unable to retrieve the value at the given path. the value of the segment '" + compositeName + "' for composite is not a Map"); 289 } 290 } 291 292 @SuppressWarnings("unchecked") 293 private boolean _doesRepeaterContainValue(RepeaterDefinition repeaterDefinition, String repeaterNameAndPositionSegment, String subConditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) throws BadItemTypeException 294 { 295 if (!DataHolderHelper.isRepeaterEntryPath(repeaterNameAndPositionSegment)) 296 { 297 // The condition is evaluated for new repeater entry, so there is no value 298 return false; 299 } 300 301 Pair<String, Integer> repeaterNameAndEntryPosition = DataHolderHelper.getRepeaterNameAndEntryPosition(repeaterNameAndPositionSegment); 302 String repeaterName = repeaterNameAndEntryPosition.getLeft(); 303 304 if (!values.containsKey(repeaterName)) 305 { 306 // There is no corresponding value in the map 307 return false; 308 } 309 310 Object value = values.get(repeaterName); 311 if (value == null) 312 { 313 // An empty value has been found 314 return true; 315 } 316 317 if (value instanceof UntouchedValue) 318 { 319 // If value is untouched, we should check in stored values 320 return false; 321 } 322 323 if (value instanceof List || value instanceof SynchronizableRepeater) 324 { 325 Integer entryPosition = repeaterNameAndEntryPosition.getRight(); 326 List<Map<String, Object>> entries = value instanceof List ? (List<Map<String, Object>>) value : ((SynchronizableRepeater) value).getEntries(); 327 SynchronizableRepeater.Mode mode = value instanceof SynchronizableRepeater ? ((SynchronizableRepeater) value).getMode() : SynchronizableRepeater.Mode.REPLACE_ALL; 328 329 if (mode == SynchronizableRepeater.Mode.REPLACE_ALL) 330 { 331 Map<String, Object> entry = entries.get(entryPosition - 1); 332 return containsValue(List.of(repeaterDefinition), subConditionDataPath, entry, contextualParameters); 333 } 334 else if (mode == SynchronizableRepeater.Mode.REPLACE) 335 { 336 return _doesReplaceModeRepeaterContainValue(repeaterDefinition, entryPosition, subConditionDataPath, (SynchronizableRepeater) value, entries, contextualParameters); 337 } 338 else // mode == SynchronizableRepeater.Mode.APPEND 339 { 340 return _doesAppendModeRepeaterContainValue(repeaterDefinition, entryPosition, (SynchronizableRepeater) value, contextualParameters); 341 } 342 } 343 else 344 { 345 throw new BadItemTypeException("Unable to retrieve the value at the given path. the value of the segment '" + repeaterNameAndPositionSegment + "' for repeater is not a List<Map>"); 346 } 347 } 348 349 private boolean _doesReplaceModeRepeaterContainValue(RepeaterDefinition repeaterDefinition, Integer entryPosition, String subConditionDataPath, SynchronizableRepeater value, List<Map<String, Object>> entries, Map<String, Object> contextualParameters) 350 { 351 int indexOfEntryInReplaceEntries = value.getReplacePositions().indexOf(entryPosition); 352 if (indexOfEntryInReplaceEntries >= 0) 353 { 354 Map<String, Object> entry = entries.get(indexOfEntryInReplaceEntries); 355 return containsValue(List.of(repeaterDefinition), subConditionDataPath, entry, contextualParameters); 356 } 357 else 358 { 359 return false; 360 } 361 } 362 363 private boolean _doesAppendModeRepeaterContainValue(RepeaterDefinition repeaterDefinition, Integer entryPosition, SynchronizableRepeater value, Map<String, Object> contextualParameters) 364 { 365 Set<Integer> removedEntries = value.getRemovedEntries(); 366 if (removedEntries.contains(entryPosition)) 367 { 368 // Non sense, we should not evaluate disable conditions on a data in a repeater entry that will be removed 369 return true; 370 } 371 372 ModelAwareRepeater repeater = null; 373 if (contextualParameters.containsKey(__DATA_HOLDER_PARAMETER_KEY)) 374 { 375 ModelAwareDataAwareAmetysObject ametysObject = (ModelAwareDataAwareAmetysObject) contextualParameters.get(__DATA_HOLDER_PARAMETER_KEY); 376 repeater = ametysObject.getRepeater(repeaterDefinition.getPath()); 377 } 378 379 int actualRepeaterSize = repeater != null ? repeater.getSize() : 0; 380 if (removedEntries.size() > actualRepeaterSize) 381 { 382 throw new IllegalArgumentException("Try to remove more entries than exist in repeater at path '" + repeaterDefinition.getPath() + "'."); 383 } 384 385 int repeaterSizeAfterRemoving = actualRepeaterSize - removedEntries.size(); 386 return entryPosition > repeaterSizeAfterRemoving; 387 } 388 389 private boolean _containsElementValue(ModelItem definition, String dataName, Map<String, Object> values, Map<String, Object> contextualParameters) 390 { 391 if (!values.containsKey(dataName)) 392 { 393 // There is no corresponding value in the map 394 return false; 395 } 396 397 Object value = values.get(dataName); 398 if (value == null) 399 { 400 // An empty value has been found 401 return true; 402 } 403 404 if (value instanceof UntouchedValue) 405 { 406 // If value is untouched, we should check in stored values 407 return false; 408 } 409 410 SynchronizationContext synchronizationContext = (SynchronizationContext) contextualParameters.get(SYNCHRONIZATION_CONTEXT_PARAMETER_KEY); 411 return synchronizationContext == null 412 || !(_getValueFromSynchronizableValue(definition, value, synchronizationContext, contextualParameters) instanceof UntouchedValue); 413 } 414 415 /** 416 * Retrieves the condition value from the values {@link Map} 417 * @param modelItemAccessors the relative accessors to the given condition path 418 * @param conditionDataPath the absolute data path of the condition 419 * @param values the values {@link Map} 420 * @param contextualParameters the contextual parameters 421 * @return the condition value found in the values {@link Map} 422 * @throws BadItemTypeException if a value does not correspond to the model of given object 423 */ 424 public Object getValueFromMap(Collection<? extends ModelItemAccessor> modelItemAccessors, String conditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) throws BadItemTypeException 425 { 426 String[] conditionDataPathSegments = StringUtils.split(conditionDataPath, ModelItem.ITEM_PATH_SEPARATOR); 427 428 ModelItem modelItem = ModelHelper.getModelItem(conditionDataPathSegments[0], modelItemAccessors); 429 if (conditionDataPathSegments.length == 1) 430 { 431 return _getElementValueFromMap(modelItem, conditionDataPathSegments[0], values, contextualParameters); 432 } 433 else 434 { 435 String subConditionDataPath = StringUtils.join(conditionDataPathSegments, ModelItem.ITEM_PATH_SEPARATOR, 1, conditionDataPathSegments.length); 436 if (modelItem instanceof CompositeDefinition compositeDefinition) 437 { 438 return _getValueFromComposite(compositeDefinition, conditionDataPathSegments[0], subConditionDataPath, values, contextualParameters); 439 } 440 else if (modelItem instanceof RepeaterDefinition repeaterDefinition) 441 { 442 return _getValueFromRepeater(repeaterDefinition, conditionDataPathSegments[0], subConditionDataPath, values, contextualParameters); 443 } 444 else if (modelItem instanceof ContentElementDefinition contentElementDefinition) 445 { 446 return _getValueFromContentValue(contentElementDefinition, conditionDataPathSegments[0], subConditionDataPath, values, contextualParameters); 447 } 448 else 449 { 450 throw new BadItemTypeException("Unable to retrieve the value at the given path. the the segment '" + conditionDataPathSegments[0] + "' is not a model item accessor"); 451 } 452 } 453 } 454 455 @SuppressWarnings("unchecked") 456 private Object _getValueFromComposite(CompositeDefinition compositeDefinition, String compositeName, String subConditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) throws BadItemTypeException 457 { 458 Object value = values.get(compositeName); 459 if (value == null) 460 { 461 return null; 462 } 463 464 if (value instanceof Map) 465 { 466 return getValueFromMap(List.of(compositeDefinition), subConditionDataPath, (Map<String, Object>) value, contextualParameters); 467 } 468 else 469 { 470 throw new BadItemTypeException("Unable to retrieve the value at the given path. the value of the segment '" + compositeName + "' for composite is not a Map"); 471 } 472 } 473 474 @SuppressWarnings("unchecked") 475 private Object _getValueFromRepeater(RepeaterDefinition repeaterDefinition, String repeaterNameAndPositionSegment, String subConditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) throws BadItemTypeException 476 { 477 Pair<String, Integer> repeaterNameAndEntryPosition = DataHolderHelper.getRepeaterNameAndEntryPosition(repeaterNameAndPositionSegment); 478 String repeaterName = repeaterNameAndEntryPosition.getLeft(); 479 480 Object value = values.get(repeaterName); 481 if (value == null) 482 { 483 return null; 484 } 485 486 if (value instanceof List || value instanceof SynchronizableRepeater) 487 { 488 Integer entryPosition = repeaterNameAndEntryPosition.getRight(); 489 List<Map<String, Object>> entries = value instanceof List ? (List<Map<String, Object>>) value : ((SynchronizableRepeater) value).getEntries(); 490 SynchronizableRepeater.Mode mode = value instanceof SynchronizableRepeater ? ((SynchronizableRepeater) value).getMode() : SynchronizableRepeater.Mode.REPLACE_ALL; 491 492 if (mode == SynchronizableRepeater.Mode.REPLACE_ALL) 493 { 494 Map<String, Object> entry = entries.get(entryPosition - 1); 495 return getValueFromMap(List.of(repeaterDefinition), subConditionDataPath, entry, contextualParameters); 496 } 497 else if (mode == SynchronizableRepeater.Mode.REPLACE) 498 { 499 int indexOfEntryInReplaceEntries = ((SynchronizableRepeater) value).getReplacePositions().indexOf(entryPosition); 500 Map<String, Object> entry = entries.get(indexOfEntryInReplaceEntries); 501 return getValueFromMap(List.of(repeaterDefinition), subConditionDataPath, entry, contextualParameters); 502 } 503 else // mode == SynchronizableRepeater.Mode.APPEND 504 { 505 Set<Integer> removedEntries = ((SynchronizableRepeater) value).getRemovedEntries(); 506 if (removedEntries.contains(entryPosition)) 507 { 508 // Non sense, we should not evaluate disable conditions on a data in a repeater entry that will be removed 509 return null; 510 } 511 512 ModelAwareRepeater repeater = null; 513 if (contextualParameters.containsKey(__DATA_HOLDER_PARAMETER_KEY)) 514 { 515 ModelAwareDataAwareAmetysObject ametysObject = (ModelAwareDataAwareAmetysObject) contextualParameters.get(__DATA_HOLDER_PARAMETER_KEY); 516 repeater = ametysObject.getRepeater(repeaterDefinition.getPath()); 517 } 518 519 int actualRepeaterSize = repeater != null ? repeater.getSize() : 0; 520 521 int repeaterSizeAfterRemoving = actualRepeaterSize - removedEntries.size(); 522 int positionInAppendedEntries = entryPosition - repeaterSizeAfterRemoving; 523 int indexInAppendedEntries = positionInAppendedEntries - 1; 524 Map<String, Object> entry = entries.get(indexInAppendedEntries); 525 return getValueFromMap(List.of(repeaterDefinition), subConditionDataPath, entry, contextualParameters); 526 } 527 } 528 else 529 { 530 throw new BadItemTypeException("Unable to retrieve the value at the given path. the value of the segment '" + repeaterNameAndPositionSegment + "' for repeater is not a List<Map>"); 531 } 532 } 533 534 private Object _getValueFromContentValue(ContentElementDefinition contentElementDefinition, String dataName, String subConditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) 535 { 536 ElementType<ContentValue> type = contentElementDefinition.getType(); 537 Object contentValue = _getElementValueFromMap(contentElementDefinition, dataName, values, contextualParameters); 538 539 if (contentValue == null) 540 { 541 return null; 542 } 543 544 if (contentValue.getClass().isArray()) 545 { 546 return Arrays.stream((Object[]) contentValue) 547 .map(type::castValue) 548 .map(ContentValue::getContent) 549 .map(content -> content.getValue(subConditionDataPath)) 550 .toArray(); 551 } 552 553 return Optional.of(contentValue) 554 .map(type::castValue) 555 .map(ContentValue::getContent) 556 .map(content -> content.getValue(subConditionDataPath)) 557 .orElse(null); 558 } 559 560 private Object _getElementValueFromMap(ModelItem definition, String dataName, Map<String, Object> values, Map<String, Object> contextualParameters) 561 { 562 Object value = values.get(dataName); 563 if (value == null) 564 { 565 return null; 566 } 567 568 Object valueFromSyncValue = value; 569 SynchronizationContext synchronizationContext = (SynchronizationContext) contextualParameters.get(SYNCHRONIZATION_CONTEXT_PARAMETER_KEY); 570 if (synchronizationContext != null) 571 { 572 valueFromSyncValue = _getValueFromSynchronizableValue(definition, value, synchronizationContext, contextualParameters); 573 574 if (valueFromSyncValue == null 575 || value instanceof SynchronizableValue syncValue && syncValue.getMode() == Mode.REMOVE) 576 { 577 return null; 578 } 579 } 580 581 return valueFromSyncValue; 582 } 583 584 private Object _getValueFromSynchronizableValue(ModelItem definition, Object value, SynchronizationContext synchronizationContext, Map<String, Object> contextualParameters) 585 { 586 Object valueFromSyncValue = value instanceof SynchronizableValue synchronizableValue ? synchronizableValue.getLocalValue() : value; 587 if (contextualParameters.containsKey(__DATA_HOLDER_PARAMETER_KEY)) 588 { 589 ModelAwareDataAwareAmetysObject ametysObject = (ModelAwareDataAwareAmetysObject) contextualParameters.get(__DATA_HOLDER_PARAMETER_KEY); 590 @SuppressWarnings("unchecked") 591 Optional<String> oldConditionDataPath = (Optional<String>) contextualParameters.get(__OLD_CONDITION_PATH_PARAMETER_KEY); 592 valueFromSyncValue = DataHolderHelper.getValueFromSynchronizableValue(value, ametysObject, definition, oldConditionDataPath, synchronizationContext); 593 } 594 595 return valueFromSyncValue; 596 } 597 598 /** 599 * Check if the given disable condition is external 600 * The condition is external if there is a view in the context and the relative element pointed by the condition is not in the view 601 * @param condition the disable condition to check 602 * @param context the definition context 603 * @return <code>true</code> if the given disable condition is external, <code>false</code> otherwise 604 */ 605 public boolean isExternal(DisableCondition condition, DefinitionContext context) 606 { 607 Optional<ModelItem> modelItem = context.getModelItem(); 608 Optional<View> view = context.getView(); 609 610 if (modelItem.isPresent() && view.isPresent()) 611 { 612 String absolutePath = ModelHelper.getDisableConditionAbsolutePath(condition, modelItem.get().getPath()); 613 614 // If the view item has not been found in the view, the condition is external 615 return !_hasViewItem(view.get(), absolutePath); 616 } 617 else 618 { 619 // No information permit to check if the condition is external or not, consider it as not external 620 return false; 621 } 622 } 623 624 private boolean _hasViewItem(ViewItemContainer viewItemContainer, String path) 625 { 626 String[] pathSegments = StringUtils.split(path, ModelItem.ITEM_PATH_SEPARATOR); 627 628 if (pathSegments.length == 1) 629 { 630 // Check if the referenced item is in the view 631 return viewItemContainer.hasModelViewItem(pathSegments[0]); 632 } 633 else 634 { 635 // Check first segment of the path 636 if (viewItemContainer.hasModelViewItem(pathSegments[0])) 637 { 638 ModelViewItem modelViewItem = viewItemContainer.getModelViewItem(pathSegments[0]); 639 if (modelViewItem instanceof ViewItemContainer subViewItemContainer) 640 { 641 String subPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 1, pathSegments.length); 642 return _hasViewItem(subViewItemContainer, subPath); 643 } 644 else 645 { 646 // If the first segment is an accessor but not a container (linked contents), 647 // we do not check the other segments. The condition will be exploded 648 return true; 649 } 650 } 651 else 652 { 653 // The item of the first path segment has not been found in the view 654 return false; 655 } 656 } 657 } 658}