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.HashMap; 021import java.util.List; 022import java.util.Map; 023import java.util.Optional; 024import java.util.Set; 025 026import org.apache.avalon.framework.component.Component; 027import org.apache.avalon.framework.configuration.ConfigurationException; 028import org.apache.avalon.framework.service.ServiceException; 029import org.apache.avalon.framework.service.ServiceManager; 030import org.apache.avalon.framework.service.Serviceable; 031import org.apache.commons.lang3.StringUtils; 032import org.apache.commons.lang3.tuple.Pair; 033 034import org.ametys.cms.data.ContentValue; 035import org.ametys.cms.data.ametysobject.ModelAwareDataAwareAmetysObject; 036import org.ametys.cms.data.type.ModelItemTypeConstants; 037import org.ametys.cms.model.ContentElementDefinition; 038import org.ametys.cms.search.model.SystemProperty; 039import org.ametys.cms.search.model.SystemPropertyExtensionPoint; 040import org.ametys.plugins.repository.data.holder.DataHolder; 041import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder; 042import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeater; 043import org.ametys.plugins.repository.data.holder.impl.DataHolderHelper; 044import org.ametys.plugins.repository.data.holder.values.SynchronizableRepeater; 045import org.ametys.plugins.repository.data.holder.values.SynchronizableValue; 046import org.ametys.plugins.repository.data.holder.values.SynchronizableValue.Mode; 047import org.ametys.plugins.repository.data.holder.values.SynchronizationContext; 048import org.ametys.plugins.repository.data.holder.values.UntouchedValue; 049import org.ametys.plugins.repository.model.CompositeDefinition; 050import org.ametys.plugins.repository.model.RepeaterDefinition; 051import org.ametys.runtime.model.DefinitionAndValue; 052import org.ametys.runtime.model.DefinitionContext; 053import org.ametys.runtime.model.Model; 054import org.ametys.runtime.model.ModelHelper; 055import org.ametys.runtime.model.ModelItem; 056import org.ametys.runtime.model.ModelItemAccessor; 057import org.ametys.runtime.model.ModelViewItem; 058import org.ametys.runtime.model.View; 059import org.ametys.runtime.model.ViewItemContainer; 060import org.ametys.runtime.model.disableconditions.DisableCondition; 061import org.ametys.runtime.model.exception.BadItemTypeException; 062import org.ametys.runtime.model.exception.UndefinedItemPathException; 063import org.ametys.runtime.model.type.ElementType; 064 065/** 066 * {@link DisableCondition} for model aware {@link DataHolder} 067 */ 068public class DataHolderRelativeDisableConditionsHelper implements Component, Serviceable 069{ 070 /** The component role. */ 071 public static final String ROLE = DataHolderRelativeDisableConditionsHelper.class.getName(); 072 073 /** The contextual parameter key for synchronization context */ 074 public static final String SYNCHRONIZATION_CONTEXT_PARAMETER_KEY = "synchronizationContext"; 075 /** The JSON info containing exploded conditions when a condition points to an element that is in a distant content */ 076 public static final String EXPLODED_DISABLE_CONDITIONS = "explodedConditions"; 077 078 /** The contextual parameter key for data holder */ 079 protected static final String __DATA_HOLDER_PARAMETER_KEY = "dataHolder"; 080 /** The contextual parameter key for old condition path */ 081 protected static final String __OLD_CONDITION_PATH_PARAMETER_KEY = "oldCondtitionPath"; 082 083 private SystemPropertyExtensionPoint _systemPropertyExtensionPoint; 084 085 public void service(ServiceManager manager) throws ServiceException 086 { 087 _systemPropertyExtensionPoint = (SystemPropertyExtensionPoint) manager.lookup(SystemPropertyExtensionPoint.ROLE); 088 } 089 090 /** 091 * Retrieves the {@link SystemProperty} if the given data path represents one 092 * @param modelItemAccessor the relative accessor to the given data path 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(ModelItemAccessor modelItemAccessor, String dataPath) 097 { 098 String conditionPathLastSegment = dataPath.contains(ModelItem.ITEM_PATH_SEPARATOR) 099 ? StringUtils.substring(dataPath, dataPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR)) 100 : dataPath; 101 102 return _systemPropertyExtensionPoint.hasExtension(conditionPathLastSegment) 103 ? _systemPropertyExtensionPoint.getExtension(conditionPathLastSegment) 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 values {@link Map} contains a value for the condition 187 * @param modelItemAccessor the relative accessor to the given condition path 188 * @param conditionDataPath the absolute data path of the condition 189 * @param values the values {@link Map} 190 * @param contextualParameters the contextual parameters 191 * @return <code>true</code> if there is a value (even empty) for the condition in the values {@link Map}, <code>false</code> otherwise 192 * @throws BadItemTypeException if a value does not correspond to the model of given object 193 */ 194 public boolean containsValue(ModelItemAccessor modelItemAccessor, String conditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) throws BadItemTypeException 195 { 196 String[] conditionDataPathSegments = StringUtils.split(conditionDataPath, ModelItem.ITEM_PATH_SEPARATOR); 197 198 ModelItem modelItem = ModelHelper.getModelItem(conditionDataPathSegments[0], List.of(modelItemAccessor)); 199 if (conditionDataPathSegments.length == 1) 200 { 201 return _containsElementValue(modelItem, conditionDataPathSegments[0], values, contextualParameters); 202 } 203 else 204 { 205 String subConditionDataPath = StringUtils.join(conditionDataPathSegments, ModelItem.ITEM_PATH_SEPARATOR, 1, conditionDataPathSegments.length); 206 if (modelItem instanceof CompositeDefinition compositeDefinition) 207 { 208 return _doesCompositeContainValue(compositeDefinition, conditionDataPathSegments[0], subConditionDataPath, values, contextualParameters); 209 } 210 else if (modelItem instanceof RepeaterDefinition repeaterDefinition) 211 { 212 return _doesRepeaterContainValue(repeaterDefinition, conditionDataPathSegments[0], subConditionDataPath, values, contextualParameters); 213 } 214 else if (modelItem instanceof ContentElementDefinition contentElementDefinition) 215 { 216 return _containsElementValue(contentElementDefinition, conditionDataPathSegments[0], values, contextualParameters); 217 } 218 else 219 { 220 throw new BadItemTypeException("Unable to retrieve the value at the given path. the the segment '" + conditionDataPathSegments[0] + "' is not a model item accessor"); 221 } 222 } 223 } 224 225 @SuppressWarnings("unchecked") 226 private boolean _doesCompositeContainValue(CompositeDefinition compositeDefinition, String compositeName, String subConditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) 227 { 228 if (!values.containsKey(compositeName)) 229 { 230 // There is no corresponding value in the map 231 return false; 232 } 233 234 Object value = values.get(compositeName); 235 if (value == null) 236 { 237 // An empty value has been found 238 return true; 239 } 240 241 if (value instanceof UntouchedValue) 242 { 243 // If value is untouched, we should check in stored values 244 return false; 245 } 246 247 if (value instanceof Map) 248 { 249 return containsValue(compositeDefinition, subConditionDataPath, (Map<String, Object>) value, contextualParameters); 250 } 251 else 252 { 253 throw new BadItemTypeException("Unable to retrieve the value at the given path. the value of the segment '" + compositeName + "' for composite is not a Map"); 254 } 255 } 256 257 @SuppressWarnings("unchecked") 258 private boolean _doesRepeaterContainValue(RepeaterDefinition repeaterDefinition, String repeaterNameAndPositionSegment, String subConditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) throws BadItemTypeException 259 { 260 Pair<String, Integer> repeaterNameAndEntryPosition = DataHolderHelper.getRepeaterNameAndEntryPosition(repeaterNameAndPositionSegment); 261 String repeaterName = repeaterNameAndEntryPosition.getLeft(); 262 263 if (!values.containsKey(repeaterName)) 264 { 265 // There is no corresponding value in the map 266 return false; 267 } 268 269 Object value = values.get(repeaterName); 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 List || value instanceof SynchronizableRepeater) 283 { 284 Integer entryPosition = repeaterNameAndEntryPosition.getRight(); 285 List<Map<String, Object>> entries = value instanceof List ? (List<Map<String, Object>>) value : ((SynchronizableRepeater) value).getEntries(); 286 SynchronizableRepeater.Mode mode = value instanceof SynchronizableRepeater ? ((SynchronizableRepeater) value).getMode() : SynchronizableRepeater.Mode.REPLACE_ALL; 287 288 if (mode == SynchronizableRepeater.Mode.REPLACE_ALL) 289 { 290 Map<String, Object> entry = entries.get(entryPosition - 1); 291 return containsValue(repeaterDefinition, subConditionDataPath, entry, contextualParameters); 292 } 293 else if (mode == SynchronizableRepeater.Mode.REPLACE) 294 { 295 int indexOfEntryInReplaceEntries = ((SynchronizableRepeater) value).getReplacePositions().indexOf(entryPosition); 296 if (indexOfEntryInReplaceEntries >= 0) 297 { 298 Map<String, Object> entry = entries.get(indexOfEntryInReplaceEntries); 299 return containsValue(repeaterDefinition, subConditionDataPath, entry, contextualParameters); 300 } 301 else 302 { 303 return false; 304 } 305 } 306 else // mode == SynchronizableRepeater.Mode.APPEND 307 { 308 Set<Integer> removedEntries = ((SynchronizableRepeater) value).getRemovedEntries(); 309 if (removedEntries.contains(entryPosition)) 310 { 311 // Non sense, we should not evaluate disable conditions on a data in a repeater entry that will be removed 312 return true; 313 } 314 315 ModelAwareRepeater repeater = null; 316 if (contextualParameters.containsKey(__DATA_HOLDER_PARAMETER_KEY)) 317 { 318 ModelAwareDataAwareAmetysObject ametysObject = (ModelAwareDataAwareAmetysObject) contextualParameters.get(__DATA_HOLDER_PARAMETER_KEY); 319 repeater = ametysObject.getRepeater(repeaterDefinition.getPath()); 320 } 321 322 int actualRepeaterSize = repeater != null ? repeater.getSize() : 0; 323 if (removedEntries.size() > actualRepeaterSize) 324 { 325 throw new IllegalArgumentException("Try to remove more entries than exist in repeater at path '" + repeaterDefinition.getPath() + "'."); 326 } 327 328 int repeaterSizeAfterRemoving = actualRepeaterSize - removedEntries.size(); 329 return entryPosition > repeaterSizeAfterRemoving; 330 } 331 } 332 else 333 { 334 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>"); 335 } 336 } 337 338 private boolean _containsElementValue(ModelItem definition, String dataName, Map<String, Object> values, Map<String, Object> contextualParameters) 339 { 340 if (!values.containsKey(dataName)) 341 { 342 // There is no corresponding value in the map 343 return false; 344 } 345 346 Object value = values.get(dataName); 347 if (value == null) 348 { 349 // An empty value has been found 350 return true; 351 } 352 353 if (value instanceof UntouchedValue) 354 { 355 // If value is untouched, we should check in stored values 356 return false; 357 } 358 359 SynchronizationContext synchronizationContext = (SynchronizationContext) contextualParameters.get(SYNCHRONIZATION_CONTEXT_PARAMETER_KEY); 360 return synchronizationContext == null 361 || !(_getValueFromSynchronizableValue(definition, value, synchronizationContext, contextualParameters) instanceof UntouchedValue); 362 } 363 364 /** 365 * Retrieves the condition value from the values {@link Map} 366 * @param modelItemAccessor the relative accessor to the given condition path 367 * @param conditionDataPath the absolute data path of the condition 368 * @param values the values {@link Map} 369 * @param contextualParameters the contextual parameters 370 * @return the condition value found in the values {@link Map} 371 * @throws BadItemTypeException if a value does not correspond to the model of given object 372 */ 373 public Object getValueFromMap(ModelItemAccessor modelItemAccessor, String conditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) throws BadItemTypeException 374 { 375 String[] conditionDataPathSegments = StringUtils.split(conditionDataPath, ModelItem.ITEM_PATH_SEPARATOR); 376 377 ModelItem modelItem = ModelHelper.getModelItem(conditionDataPathSegments[0], List.of(modelItemAccessor)); 378 if (conditionDataPathSegments.length == 1) 379 { 380 return _getElementValueFromMap(modelItem, conditionDataPathSegments[0], values, contextualParameters); 381 } 382 else 383 { 384 String subConditionDataPath = StringUtils.join(conditionDataPathSegments, ModelItem.ITEM_PATH_SEPARATOR, 1, conditionDataPathSegments.length); 385 if (modelItem instanceof CompositeDefinition compositeDefinition) 386 { 387 return _getValueFromComposite(compositeDefinition, conditionDataPathSegments[0], subConditionDataPath, values, contextualParameters); 388 } 389 else if (modelItem instanceof RepeaterDefinition repeaterDefinition) 390 { 391 return _getValueFromRepeater(repeaterDefinition, conditionDataPathSegments[0], subConditionDataPath, values, contextualParameters); 392 } 393 else if (modelItem instanceof ContentElementDefinition contentElementDefinition) 394 { 395 return _getValueFromContentValue(contentElementDefinition, conditionDataPathSegments[0], subConditionDataPath, values, contextualParameters); 396 } 397 else 398 { 399 throw new BadItemTypeException("Unable to retrieve the value at the given path. the the segment '" + conditionDataPathSegments[0] + "' is not a model item accessor"); 400 } 401 } 402 } 403 404 @SuppressWarnings("unchecked") 405 private Object _getValueFromComposite(CompositeDefinition compositeDefinition, String compositeName, String subConditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) throws BadItemTypeException 406 { 407 Object value = values.get(compositeName); 408 if (value == null) 409 { 410 return null; 411 } 412 413 if (value instanceof Map) 414 { 415 return getValueFromMap(compositeDefinition, subConditionDataPath, (Map<String, Object>) value, contextualParameters); 416 } 417 else 418 { 419 throw new BadItemTypeException("Unable to retrieve the value at the given path. the value of the segment '" + compositeName + "' for composite is not a Map"); 420 } 421 } 422 423 @SuppressWarnings("unchecked") 424 private Object _getValueFromRepeater(RepeaterDefinition repeaterDefinition, String repeaterNameAndPositionSegment, String subConditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) throws BadItemTypeException 425 { 426 Pair<String, Integer> repeaterNameAndEntryPosition = DataHolderHelper.getRepeaterNameAndEntryPosition(repeaterNameAndPositionSegment); 427 String repeaterName = repeaterNameAndEntryPosition.getLeft(); 428 429 Object value = values.get(repeaterName); 430 if (value == null) 431 { 432 return null; 433 } 434 435 if (value instanceof List || value instanceof SynchronizableRepeater) 436 { 437 Integer entryPosition = repeaterNameAndEntryPosition.getRight(); 438 List<Map<String, Object>> entries = value instanceof List ? (List<Map<String, Object>>) value : ((SynchronizableRepeater) value).getEntries(); 439 SynchronizableRepeater.Mode mode = value instanceof SynchronizableRepeater ? ((SynchronizableRepeater) value).getMode() : SynchronizableRepeater.Mode.REPLACE_ALL; 440 441 if (mode == SynchronizableRepeater.Mode.REPLACE_ALL) 442 { 443 Map<String, Object> entry = entries.get(entryPosition - 1); 444 return getValueFromMap(repeaterDefinition, subConditionDataPath, entry, contextualParameters); 445 } 446 else if (mode == SynchronizableRepeater.Mode.REPLACE) 447 { 448 int indexOfEntryInReplaceEntries = ((SynchronizableRepeater) value).getReplacePositions().indexOf(entryPosition); 449 Map<String, Object> entry = entries.get(indexOfEntryInReplaceEntries); 450 return getValueFromMap(repeaterDefinition, subConditionDataPath, entry, contextualParameters); 451 } 452 else // mode == SynchronizableRepeater.Mode.APPEND 453 { 454 Set<Integer> removedEntries = ((SynchronizableRepeater) value).getRemovedEntries(); 455 if (removedEntries.contains(entryPosition)) 456 { 457 // Non sense, we should not evaluate disable conditions on a data in a repeater entry that will be removed 458 return null; 459 } 460 461 ModelAwareRepeater repeater = null; 462 if (contextualParameters.containsKey(__DATA_HOLDER_PARAMETER_KEY)) 463 { 464 ModelAwareDataAwareAmetysObject ametysObject = (ModelAwareDataAwareAmetysObject) contextualParameters.get(__DATA_HOLDER_PARAMETER_KEY); 465 repeater = ametysObject.getRepeater(repeaterDefinition.getPath()); 466 } 467 468 int actualRepeaterSize = repeater != null ? repeater.getSize() : 0; 469 470 int repeaterSizeAfterRemoving = actualRepeaterSize - removedEntries.size(); 471 int positionInAppendedEntries = entryPosition - repeaterSizeAfterRemoving; 472 int indexInAppendedEntries = positionInAppendedEntries - 1; 473 Map<String, Object> entry = entries.get(indexInAppendedEntries); 474 return getValueFromMap(repeaterDefinition, subConditionDataPath, entry, contextualParameters); 475 } 476 } 477 else 478 { 479 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>"); 480 } 481 } 482 483 private Object _getValueFromContentValue(ContentElementDefinition contentElementDefinition, String dataName, String subConditionDataPath, Map<String, Object> values, Map<String, Object> contextualParameters) 484 { 485 ElementType<ContentValue> type = contentElementDefinition.getType(); 486 Object contentValue = _getElementValueFromMap(contentElementDefinition, dataName, values, contextualParameters); 487 488 if (contentValue == null) 489 { 490 return null; 491 } 492 493 if (contentValue.getClass().isArray()) 494 { 495 return Arrays.stream((Object[]) contentValue) 496 .map(type::castValue) 497 .map(ContentValue::getContent) 498 .map(content -> content.getValue(subConditionDataPath)) 499 .toArray(); 500 } 501 502 return Optional.of(contentValue) 503 .map(type::castValue) 504 .map(ContentValue::getContent) 505 .map(content -> content.getValue(subConditionDataPath)) 506 .orElse(null); 507 } 508 509 private Object _getElementValueFromMap(ModelItem definition, String dataName, Map<String, Object> values, Map<String, Object> contextualParameters) 510 { 511 Object value = values.get(dataName); 512 if (value == null) 513 { 514 return null; 515 } 516 517 Object valueFromSyncValue = value; 518 SynchronizationContext synchronizationContext = (SynchronizationContext) contextualParameters.get(SYNCHRONIZATION_CONTEXT_PARAMETER_KEY); 519 if (synchronizationContext != null) 520 { 521 valueFromSyncValue = _getValueFromSynchronizableValue(definition, value, synchronizationContext, contextualParameters); 522 523 if (valueFromSyncValue == null 524 || value instanceof SynchronizableValue syncValue && syncValue.getMode() == Mode.REMOVE) 525 { 526 return null; 527 } 528 } 529 530 return valueFromSyncValue; 531 } 532 533 private Object _getValueFromSynchronizableValue(ModelItem definition, Object value, SynchronizationContext synchronizationContext, Map<String, Object> contextualParameters) 534 { 535 Object valueFromSyncValue = value instanceof SynchronizableValue synchronizableValue ? synchronizableValue.getLocalValue() : value; 536 if (contextualParameters.containsKey(__DATA_HOLDER_PARAMETER_KEY)) 537 { 538 ModelAwareDataAwareAmetysObject ametysObject = (ModelAwareDataAwareAmetysObject) contextualParameters.get(__DATA_HOLDER_PARAMETER_KEY); 539 @SuppressWarnings("unchecked") 540 Optional<String> oldConditionDataPath = (Optional<String>) contextualParameters.get(__OLD_CONDITION_PATH_PARAMETER_KEY); 541 valueFromSyncValue = DataHolderHelper.getValueFromSynchronizableValue(value, ametysObject, definition, oldConditionDataPath, synchronizationContext); 542 } 543 544 return valueFromSyncValue; 545 } 546 547 /** 548 * Check if the given disable condition is external 549 * 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 550 * @param condition the disable condition to check 551 * @param context the definition context 552 * @return <code>true</code> if the given disable condition is external, <code>false</code> otherwise 553 */ 554 public boolean isExternal(DisableCondition condition, DefinitionContext context) 555 { 556 Optional<ModelItem> modelItem = context.getModelItem(); 557 Optional<View> view = context.getView(); 558 559 if (modelItem.isPresent() && view.isPresent()) 560 { 561 String absolutePath = ModelHelper.getDisableConditionAbsolutePath(condition, modelItem.get().getPath()); 562 563 // If the view item has not been found in the view, the condition is external 564 return !_hasViewItem(view.get(), absolutePath); 565 } 566 else 567 { 568 // No information permit to check if the condition is external or not, consider it as not external 569 return false; 570 } 571 } 572 573 private boolean _hasViewItem(ViewItemContainer viewItemContainer, String path) 574 { 575 String[] pathSegments = StringUtils.split(path, ModelItem.ITEM_PATH_SEPARATOR); 576 577 if (pathSegments.length == 1) 578 { 579 // Check if the referenced item is in the view 580 return viewItemContainer.hasModelViewItem(pathSegments[0]); 581 } 582 else 583 { 584 // Check first segment of the path 585 if (viewItemContainer.hasModelViewItem(pathSegments[0])) 586 { 587 ModelViewItem modelViewItem = viewItemContainer.getModelViewItem(pathSegments[0]); 588 if (modelViewItem instanceof ViewItemContainer subViewItemContainer) 589 { 590 String subPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 1, pathSegments.length); 591 return _hasViewItem(subViewItemContainer, subPath); 592 } 593 else 594 { 595 // If the first segment is an accessor but not a container (linked contents), 596 // we do not check the other segments. The condition will be exploded 597 return true; 598 } 599 } 600 else 601 { 602 // The item of the first path segment has not been found in the view 603 return false; 604 } 605 } 606 } 607}