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