001/* 002 * Copyright 2018 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.impl; 017 018import java.lang.reflect.Array; 019import java.util.Collection; 020import java.util.List; 021import java.util.Map; 022import java.util.Optional; 023 024import org.apache.commons.lang3.StringUtils; 025import org.apache.commons.lang3.tuple.Pair; 026 027import org.ametys.cms.data.holder.ModifiableIndexableDataHolder; 028import org.ametys.cms.data.holder.group.ModifiableIndexableComposite; 029import org.ametys.cms.data.holder.group.ModifiableIndexableRepeater; 030import org.ametys.cms.data.holder.group.impl.DefaultModifiableModelAwareComposite; 031import org.ametys.cms.data.holder.group.impl.DefaultModifiableModelAwareRepeater; 032import org.ametys.plugins.repository.data.external.ExternalizableDataProvider.ExternalizableDataStatus; 033import org.ametys.plugins.repository.data.holder.DataHolder; 034import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder; 035import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareComposite; 036import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareRepeater; 037import org.ametys.plugins.repository.data.holder.group.ModifiableModelAwareRepeaterEntry; 038import org.ametys.plugins.repository.data.holder.impl.DataHolderHelper; 039import org.ametys.plugins.repository.data.holder.values.SynchronizableRepeater; 040import org.ametys.plugins.repository.data.holder.values.SynchronizableValue; 041import org.ametys.plugins.repository.data.holder.values.SynchronizationContext; 042import org.ametys.plugins.repository.data.holder.values.SynchronizationResult; 043import org.ametys.plugins.repository.data.holder.values.UntouchedValue; 044import org.ametys.plugins.repository.data.holder.values.ValueContext; 045import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData; 046import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 047import org.ametys.plugins.repository.data.type.RepositoryElementType; 048import org.ametys.plugins.repository.data.type.RepositoryModelItemGroupType; 049import org.ametys.plugins.repository.data.type.RepositoryModelItemType; 050import org.ametys.plugins.repository.lock.LockableAmetysObject; 051import org.ametys.plugins.repository.model.CompositeDefinition; 052import org.ametys.plugins.repository.model.RepeaterDefinition; 053import org.ametys.runtime.model.ElementDefinition; 054import org.ametys.runtime.model.ModelItem; 055import org.ametys.runtime.model.ModelItemContainer; 056import org.ametys.runtime.model.ModelViewItem; 057import org.ametys.runtime.model.ModelViewItemGroup; 058import org.ametys.runtime.model.ViewElement; 059import org.ametys.runtime.model.ViewItem; 060import org.ametys.runtime.model.ViewItemAccessor; 061import org.ametys.runtime.model.exception.BadDataPathCardinalityException; 062import org.ametys.runtime.model.exception.BadItemTypeException; 063import org.ametys.runtime.model.exception.UndefinedItemPathException; 064import org.ametys.runtime.model.type.ElementType; 065 066/** 067 * Default implementation for modifiable data holder with model 068 */ 069public class DefaultModifiableModelAwareDataHolder extends DefaultModelAwareDataHolder implements ModifiableIndexableDataHolder 070{ 071 private static final String __TEMP_SUFFIX = "__temp"; 072 073 /** Repository data to use to store data in the repository */ 074 protected ModifiableRepositoryData _modifiableRepositoryData; 075 076 /** Ametys object that can be locked on data modification */ 077 protected Optional<LockableAmetysObject> _lockableAmetysObject; 078 079 /** Parent of the current {@link DataHolder} */ 080 protected Optional<? extends ModifiableIndexableDataHolder> _modifiableParent; 081 082 /** Root {@link DataHolder} */ 083 protected ModifiableIndexableDataHolder _modifiableRoot; 084 085 /** 086 * Creates a modifiable default model aware data holder 087 * @param repositoryData the repository data to use 088 * @param itemContainer the model container to use to get information about definitions. Must match the given repository data. A repository data can have several item containers. For example, a content can have several content types. 089 */ 090 public DefaultModifiableModelAwareDataHolder(ModifiableRepositoryData repositoryData, ModelItemContainer itemContainer) 091 { 092 this(repositoryData, itemContainer, Optional.empty(), Optional.empty(), Optional.empty()); 093 } 094 095 /** 096 * Creates a modifiable default model aware data holder 097 * @param repositoryData the repository data to use 098 * @param itemContainer the model container to use to get information about definitions. Must match the given repository data. A repository data can have several item containers. For example, a content can have several content types. 099 * @param lockableAmetysObject the ametys object that can be locked on data modification 100 */ 101 public DefaultModifiableModelAwareDataHolder(ModifiableRepositoryData repositoryData, ModelItemContainer itemContainer, Optional<LockableAmetysObject> lockableAmetysObject) 102 { 103 this(repositoryData, itemContainer, lockableAmetysObject, Optional.empty(), Optional.empty()); 104 } 105 106 /** 107 * Creates a modifiable default model aware data holder 108 * @param repositoryData the repository data to use 109 * @param itemContainer the model container to use to get information about definitions. Must match the given repository data. A repository data can have several item containers. For example, a content can have several content types. 110 * @param lockableAmetysObject the ametys object that can be locked on data modification 111 * @param parent the parent of the created {@link DataHolder}, empty if the created {@link DataHolder} is the root {@link DataHolder} 112 * @param root the root {@link DataHolder} 113 */ 114 public DefaultModifiableModelAwareDataHolder(ModifiableRepositoryData repositoryData, ModelItemContainer itemContainer, Optional<LockableAmetysObject> lockableAmetysObject, Optional<? extends ModifiableIndexableDataHolder> parent, Optional<? extends ModifiableIndexableDataHolder> root) 115 { 116 this(repositoryData, List.of(itemContainer), lockableAmetysObject, parent, root); 117 } 118 119 120 /** 121 * Creates a modifiable default model aware data holder 122 * @param repositoryData the repository data to use 123 * @param itemContainers the model containers to use to get information about definitions. Must match the given repository data. A repository data can have several item containers. For example, a content can have several content types. 124 * @param lockableAmetysObject the ametys object that can be locked on data modification 125 * @param parent the parent of the created {@link DataHolder}, empty if the created {@link DataHolder} is the root {@link DataHolder} 126 * @param root the root {@link DataHolder} 127 */ 128 public DefaultModifiableModelAwareDataHolder(ModifiableRepositoryData repositoryData, Collection<? extends ModelItemContainer> itemContainers, Optional<LockableAmetysObject> lockableAmetysObject, Optional<? extends ModifiableIndexableDataHolder> parent, Optional<? extends ModifiableIndexableDataHolder> root) 129 { 130 super(repositoryData, itemContainers, parent, root); 131 _modifiableRepositoryData = repositoryData; 132 _lockableAmetysObject = lockableAmetysObject; 133 134 _modifiableParent = parent; 135 _modifiableRoot = root.map(ModifiableIndexableDataHolder.class::cast) 136 .or(() -> _modifiableParent.map(ModifiableIndexableDataHolder::getRootDataHolder)) // if no root is specified but a parent, the root is the parent's root 137 .orElse(this); // if no root or parent is specified, the root is the current DataHolder 138 } 139 140 @Override 141 public ModifiableIndexableComposite getComposite(String compositePath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 142 { 143 return (ModifiableIndexableComposite) super.getComposite(compositePath); 144 } 145 146 @Override 147 public ModifiableIndexableComposite getLocalComposite(String compositePath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 148 { 149 return (ModifiableIndexableComposite) super.getLocalComposite(compositePath); 150 } 151 152 @Override 153 public ModifiableIndexableComposite getExternalComposite(String compositePath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 154 { 155 return (ModifiableIndexableComposite) super.getExternalComposite(compositePath); 156 } 157 158 @Override 159 protected ModifiableIndexableComposite _getComposite(String name, CompositeDefinition compositeDefinition) throws BadItemTypeException 160 { 161 return _getComposite(name, compositeDefinition, false); 162 } 163 164 @Override 165 public ModifiableIndexableRepeater getRepeater(String repeaterPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 166 { 167 return (ModifiableIndexableRepeater) super.getRepeater(repeaterPath); 168 } 169 170 @Override 171 public ModifiableIndexableRepeater getLocalRepeater(String repeaterPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 172 { 173 return (ModifiableIndexableRepeater) super.getLocalRepeater(repeaterPath); 174 } 175 176 @Override 177 public ModifiableIndexableRepeater getExternalRepeater(String repeaterPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 178 { 179 return (ModifiableIndexableRepeater) super.getExternalRepeater(repeaterPath); 180 } 181 182 @Override 183 protected ModifiableIndexableRepeater _getRepeater(String name, RepeaterDefinition repeaterDefinition) throws BadItemTypeException 184 { 185 return _getRepeater(name, repeaterDefinition, false); 186 } 187 188 public ModifiableIndexableComposite getComposite(String compositePath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 189 { 190 return _getComposite(compositePath, createNew, Optional.empty()); 191 } 192 193 public ModifiableIndexableComposite getLocalComposite(String compositePath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 194 { 195 return _getComposite(compositePath, createNew, Optional.of(ExternalizableDataStatus.LOCAL)); 196 } 197 198 public ModifiableIndexableComposite getExternalComposite(String compositePath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 199 { 200 return _getComposite(compositePath, createNew, Optional.of(ExternalizableDataStatus.EXTERNAL)); 201 } 202 203 private ModifiableIndexableComposite _getComposite(String compositePath, boolean createNew, Optional<ExternalizableDataStatus> status) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 204 { 205 String[] pathSegments = StringUtils.split(compositePath, ModelItem.ITEM_PATH_SEPARATOR); 206 207 if (pathSegments == null || pathSegments.length < 1) 208 { 209 throw new IllegalArgumentException("Unable to retrieve the composite at the given path. This path is empty."); 210 } 211 else if (pathSegments.length == 1) 212 { 213 // Simple path => get composite value 214 ModelItem modelItem = getDefinition(compositePath); 215 if (modelItem instanceof CompositeDefinition) 216 { 217 String compositeName = _getFinalDataName(compositePath, status); 218 return _getComposite(compositeName, (CompositeDefinition) modelItem, createNew); 219 } 220 else 221 { 222 throw new BadItemTypeException("The data at path '" + compositePath + "' is not a composite."); 223 } 224 } 225 else 226 { 227 String parentPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 0, pathSegments.length - 1); 228 229 // Multiple items are allowed only at the last segment of the data path 230 if (isMultiple(parentPath)) 231 { 232 throw new BadDataPathCardinalityException("Unable to retrieve the composite at path '" + compositePath + "'. The segment '" + pathSegments[pathSegments.length - 2] + "' refers to a multiple data and can not be used inside the data path."); 233 } 234 235 Object parentValue = getValue(parentPath); 236 String childName = pathSegments[pathSegments.length - 1]; 237 if (parentValue != null && parentValue instanceof ModifiableIndexableDataHolder parent) 238 { 239 return status.isPresent() 240 ? ExternalizableDataStatus.EXTERNAL.equals(status.get()) 241 ? getExternalComposite(compositePath, createNew) 242 : getLocalComposite(compositePath, createNew) 243 : parent.getComposite(childName, createNew); 244 } 245 else 246 { 247 throw new BadItemTypeException("The data at path '" + parentPath + "' in the repository doesn't exist or is not a data holder. It can not contain the data named '" + childName + "'."); 248 } 249 } 250 } 251 252 /** 253 * Retrieves the composite with the given name 254 * @param name name of the composite to retrieve 255 * @param compositeDefinition the definition of the composite to retrieve 256 * @param createNew <code>true</code> to create the repeater if it does not exist, <code>false</code> otherwise 257 * @return the composite 258 * @throws BadItemTypeException if the value stored in the repository with the given name is not a composite 259 */ 260 protected ModifiableIndexableComposite _getComposite(String name, CompositeDefinition compositeDefinition, boolean createNew) throws BadItemTypeException 261 { 262 RepositoryModelItemGroupType type = (RepositoryModelItemGroupType) compositeDefinition.getType(); 263 RepositoryData compositeRepositoryData = type.read(_modifiableRepositoryData, name); 264 265 if (compositeRepositoryData != null) 266 { 267 return new DefaultModifiableModelAwareComposite((ModifiableRepositoryData) compositeRepositoryData, compositeDefinition, _lockableAmetysObject, this, _modifiableRoot); 268 } 269 else 270 { 271 if (createNew) 272 { 273 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 274 ModifiableRepositoryData createdRepositoryData = type.add(_modifiableRepositoryData, name); 275 return new DefaultModifiableModelAwareComposite(createdRepositoryData, compositeDefinition, _lockableAmetysObject, this, _modifiableRoot); 276 } 277 else 278 { 279 return null; 280 } 281 } 282 } 283 284 public ModifiableIndexableRepeater getRepeater(String repeaterPath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 285 { 286 return _getRepeater(repeaterPath, createNew, Optional.empty()); 287 } 288 289 public ModifiableIndexableRepeater getLocalRepeater(String repeaterPath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 290 { 291 return _getRepeater(repeaterPath, createNew, Optional.of(ExternalizableDataStatus.LOCAL)); 292 } 293 294 public ModifiableIndexableRepeater getExternalRepeater(String repeaterPath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 295 { 296 return _getRepeater(repeaterPath, createNew, Optional.of(ExternalizableDataStatus.EXTERNAL)); 297 } 298 299 private ModifiableIndexableRepeater _getRepeater(String repeaterPath, boolean createNew, Optional<ExternalizableDataStatus> status) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 300 { 301 String[] pathSegments = StringUtils.split(repeaterPath, ModelItem.ITEM_PATH_SEPARATOR); 302 303 if (pathSegments == null || pathSegments.length < 1) 304 { 305 throw new IllegalArgumentException("Unable to retrieve the repeater at the given path. This path is empty."); 306 } 307 else if (pathSegments.length == 1) 308 { 309 // Simple path => get composite value 310 ModelItem modelItem = getDefinition(repeaterPath); 311 if (modelItem instanceof RepeaterDefinition) 312 { 313 String repeaterName = _getFinalDataName(repeaterPath, status); 314 return _getRepeater(repeaterName, (RepeaterDefinition) modelItem, createNew); 315 } 316 else 317 { 318 throw new BadItemTypeException("The data at path '" + repeaterPath + "' is not a repeater."); 319 } 320 } 321 else 322 { 323 String parentPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 0, pathSegments.length - 1); 324 325 // Multiple items are allowed only at the last segment of the data path 326 if (isMultiple(parentPath)) 327 { 328 throw new BadDataPathCardinalityException("Unable to retrieve the repeater at path '" + repeaterPath + "'. The segment '" + pathSegments[pathSegments.length - 2] + "' refers to a multiple data and can not be used inside the data path."); 329 } 330 331 Object parentValue = getValue(parentPath); 332 String childName = pathSegments[pathSegments.length - 1]; 333 if (parentValue != null && parentValue instanceof ModifiableIndexableDataHolder parent) 334 { 335 return status.isPresent() 336 ? ExternalizableDataStatus.EXTERNAL.equals(status.get()) 337 ? parent.getExternalRepeater(repeaterPath, createNew) 338 : parent.getExternalRepeater(repeaterPath, createNew) 339 : parent.getRepeater(childName, createNew); 340 } 341 else 342 { 343 throw new BadItemTypeException("The data at path '" + parentPath + "' in the repository doesn't exist or is not a composite or a repeater entry. It can not contain the data named '" + childName + "'."); 344 } 345 } 346 } 347 348 /** 349 * Retrieves the repeater with the given name 350 * @param name name of the repeater to retrieve 351 * @param repeaterDefinition the definition of the repeater to retrieve 352 * @param createNew <code>true</code> to create the repeater if it does not exist, <code>false</code> otherwise 353 * @return the repeater 354 * @throws BadItemTypeException if the value stored in the repository with the given name is not a repeater 355 */ 356 protected ModifiableIndexableRepeater _getRepeater(String name, RepeaterDefinition repeaterDefinition, boolean createNew) throws BadItemTypeException 357 { 358 RepositoryModelItemGroupType type = (RepositoryModelItemGroupType) repeaterDefinition.getType(); 359 RepositoryData repeaterRepositoryData = type.read(_modifiableRepositoryData, name); 360 361 if (repeaterRepositoryData != null) 362 { 363 return new DefaultModifiableModelAwareRepeater((ModifiableRepositoryData) repeaterRepositoryData, repeaterDefinition, _lockableAmetysObject, this, _modifiableRoot); 364 } 365 else 366 { 367 if (createNew) 368 { 369 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 370 ModifiableRepositoryData createdRepositoryData = type.add(_modifiableRepositoryData, name); 371 return new DefaultModifiableModelAwareRepeater(createdRepositoryData, repeaterDefinition, _lockableAmetysObject, this, _modifiableRoot); 372 } 373 else 374 { 375 return null; 376 } 377 } 378 } 379 380 @Override 381 protected Class _getRepeaterEntryClass() 382 { 383 return ModifiableModelAwareRepeaterEntry.class; 384 } 385 386 @Override 387 protected Class _getRepeaterClass() 388 { 389 return ModifiableModelAwareRepeater.class; 390 } 391 392 @Override 393 protected Class _getCompositeClass() 394 { 395 return ModifiableModelAwareComposite.class; 396 } 397 398 public <T extends SynchronizationResult> T synchronizeValues(Map<String, Object> values) throws UndefinedItemPathException, BadItemTypeException 399 { 400 return synchronizeValues(values, _createSynchronizationContextInstance()); 401 } 402 403 public <T extends SynchronizationResult> T synchronizeValues(Map<String, Object> values, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException 404 { 405 ViewItemAccessor viewItemAccessor = DataHolderHelper.createViewItemAccessorFromValues(_itemContainers, values); 406 return synchronizeValues(viewItemAccessor, values, context); 407 } 408 409 public <T extends SynchronizationResult> T synchronizeValues(ViewItemAccessor viewItemAccessor, Map<String, Object> values) throws UndefinedItemPathException, BadItemTypeException 410 { 411 return synchronizeValues(viewItemAccessor, values, _createSynchronizationContextInstance()); 412 } 413 414 public <T extends SynchronizationResult> T synchronizeValues(ViewItemAccessor viewItemAccessor, Map<String, Object> values, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException 415 { 416 return _synchronizeValues(viewItemAccessor, values, context); 417 } 418 419 /** 420 * Synchronizes the given values with the current {@link ModifiableModelAwareDataHolder}'s ones 421 * @param <T> the type of the {@link SynchronizationResult} 422 * @param viewItemAccessor The {@link ViewItemAccessor} for all items to synchronize 423 * @param values the values to synchronize 424 * @param context the context of the synchronization 425 * @return the {@link SynchronizationResult} 426 * @throws UndefinedItemPathException if a key in the given Map refers to a data that is not defined by the model 427 * @throws BadItemTypeException if the type defined by the model of one of the Map's key doesn't match the corresponding value 428 */ 429 protected <T extends SynchronizationResult> T _synchronizeValues(ViewItemAccessor viewItemAccessor, Map<String, Object> values, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException 430 { 431 T result = _createSetValueResultInstance(); 432 433 for (ViewItem viewItem : viewItemAccessor.getViewItems()) 434 { 435 SynchronizationResult itemResult = null; 436 if (viewItem instanceof ModelViewItem) 437 { 438 if (viewItem instanceof ModelViewItemGroup modelViewItemGroup) 439 { 440 itemResult = _synchronizeGroup(modelViewItemGroup, values, context); 441 } 442 else if (viewItem instanceof ViewElement viewElement) 443 { 444 itemResult = _synchronizeElement(viewElement, values, context); 445 } 446 } 447 else if (viewItem instanceof ViewItemAccessor accessor) 448 { 449 itemResult = _synchronizeValues(accessor, values, context); 450 } 451 452 result.aggregateResult(itemResult); 453 } 454 455 return result; 456 } 457 458 /** 459 * Synchronizes the given values with the given group's ones 460 * @param <T> the type of the {@link SynchronizationResult} 461 * @param modelViewItemGroup The group containing items to synchronize 462 * @param values the values to synchronize 463 * @param synchronizationContext the context of the synchronization 464 * @return the {@link SynchronizationResult} 465 * @throws UndefinedItemPathException if a key in the given Map refers to a data that is not defined by the model 466 * @throws BadItemTypeException if the type defined by the model of one of the Map's key doesn't match the corresponding value 467 */ 468 @SuppressWarnings("unchecked") 469 protected <T extends SynchronizationResult> T _synchronizeGroup(ModelViewItemGroup modelViewItemGroup, Map<String, Object> values, SynchronizationContext synchronizationContext) throws UndefinedItemPathException, BadItemTypeException 470 { 471 ModelItem modelItem = modelViewItemGroup.getDefinition(); 472 String dataName = modelItem.getName(); 473 Object value = values.get(dataName); 474 T result = null; 475 476 if (value == null) 477 { 478 ValueContext valueContext = DataHolderHelper.createValueContextFromSynchronizationContext(this, dataName, synchronizationContext); 479 result = _createSetValueResultInstance(); 480 if (DataHolderHelper.hasValueOrEmpty(this, dataName, valueContext)) 481 { 482 _removeValueForSynchronize(dataName, valueContext); 483 result.setHasChanged(true); 484 } 485 } 486 else if (value instanceof UntouchedValue) 487 { 488 result = _createSetValueResultInstance(); 489 } 490 else if (modelItem instanceof RepeaterDefinition) 491 { 492 if (value instanceof SynchronizableRepeater || value instanceof List) 493 { 494 ModifiableModelAwareRepeater repeater = getRepeater(dataName, true); 495 SynchronizableRepeater repeaterValues = value instanceof SynchronizableRepeater ? (SynchronizableRepeater) value : SynchronizableRepeater.replaceAll((List<Map<String, Object>>) value, null); 496 result = repeater.synchronizeValues(modelViewItemGroup, repeaterValues, synchronizationContext); 497 } 498 else 499 { 500 throw new BadItemTypeException("Unable to synchronize the repeater named '" + dataName + "': the given value should be a list containing its entries"); 501 } 502 } 503 else 504 { 505 if (value instanceof Map) 506 { 507 ModifiableModelAwareComposite composite = getComposite(dataName, true); 508 result = composite.synchronizeValues(modelViewItemGroup, (Map<String, Object>) value, synchronizationContext); 509 } 510 else 511 { 512 throw new BadItemTypeException("Unable to synchronize the composite named '" + dataName + "': the given value should be a map containing values of all of its items"); 513 } 514 } 515 516 if (!DataHolderHelper.getExternalizableDataProviderExtensionPoint().isDataExternalizable(getRootDataHolder(), modelItem)) 517 { 518 _removeExternalizableMetadataIfExists(_modifiableRepositoryData, dataName); 519 } 520 521 return result; 522 } 523 524 /** 525 * Synchronizes the given values with the given element's ones 526 * @param <T> the type of the {@link SynchronizationResult} 527 * @param viewElement The element item to synchronize 528 * @param values the values to synchronize 529 * @param synchronizationContext the context of the synchronization 530 * @return the {@link SynchronizationResult} 531 * @throws UndefinedItemPathException if a key in the given Map refers to a data that is not defined by the model 532 * @throws BadItemTypeException if the type defined by the model of one of the Map's key doesn't match the corresponding value 533 */ 534 protected <T extends SynchronizationResult> T _synchronizeElement(ViewElement viewElement, Map<String, Object> values, SynchronizationContext synchronizationContext) throws UndefinedItemPathException, BadItemTypeException 535 { 536 ElementDefinition definition = viewElement.getDefinition(); 537 String dataName = definition.getName(); 538 539 Object valueFromMap = values.get(dataName); 540 ValueContext valueContext = DataHolderHelper.createValueContextFromSynchronizationContext(this, dataName, synchronizationContext); 541 542 SynchronizableValue syncValue = valueFromMap instanceof SynchronizableValue ? (SynchronizableValue) valueFromMap : new SynchronizableValue(valueFromMap, valueContext.getStatus().orElse(null)); 543 Object value = syncValue.getValue(valueContext.getStatus()); 544 545 T result = null; 546 547 if (!(value instanceof UntouchedValue)) 548 { 549 Object defaultValue = definition.getDefaultValue(); 550 if (value == null && synchronizationContext.useDefaultFromModel() && defaultValue != null) 551 { 552 result = _setValueForSynchronize(dataName, new SynchronizableValue(defaultValue, valueContext.getStatus().orElse(null)), valueContext); 553 } 554 else 555 { 556 if (values.containsKey(dataName)) 557 { 558 result = _setValueForSynchronize(dataName, syncValue, valueContext); 559 } 560 else 561 { 562 result = _removeValueForSynchronize(dataName, valueContext); 563 } 564 } 565 } 566 else 567 { 568 result = _createSetValueResultInstance(); 569 } 570 571 if (DataHolderHelper.getExternalizableDataProviderExtensionPoint().isDataExternalizable(getRootDataHolder(), definition)) 572 { 573 boolean statusHasChanged = _updateStatusForSynchronize(dataName, syncValue, valueContext, synchronizationContext.forceStatusIfNotPresent(), values.containsKey(dataName)); 574 if (statusHasChanged) 575 { 576 result.setHasChanged(true); 577 } 578 } 579 else 580 { 581 _removeExternalizableMetadataIfExists(_modifiableRepositoryData, dataName); 582 } 583 584 return result; 585 } 586 587 /** 588 * Updates the status of the data with the given name 589 * @param dataName name of the data 590 * @param value the value 591 * @param context context of the data 592 * @param forceStatus <code>true</code> to force the status if it is not present, <code>false</code> otherwise 593 * @param doValuesContainData <code>true</code> if the values contain the data, <code>false</code> otherwise 594 * @return <code>true</code> if the status has changed, <code>false</code> otherwise 595 * @throws IllegalArgumentException if the given data name is null or empty 596 * @throws UndefinedItemPathException if the given data name is not defined by the model 597 * @throws BadItemTypeException if the type defined by the model doesn't match the given value to set 598 */ 599 protected boolean _updateStatusForSynchronize(String dataName, SynchronizableValue value, ValueContext context, boolean forceStatus, boolean doValuesContainData) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException 600 { 601 boolean hasChanged = false; 602 603 ExternalizableDataStatus oldStatus = null; 604 if (_repositoryData.hasValue(dataName + STATUS_SUFFIX)) 605 { 606 String status = _repositoryData.getString(dataName + STATUS_SUFFIX); 607 oldStatus = ExternalizableDataStatus.valueOf(status.toUpperCase()); 608 } 609 ExternalizableDataStatus newStatus = value.getExternalizableStatus(); 610 Optional<ExternalizableDataStatus> contextStatus = context.getStatus(); 611 if (forceStatus && oldStatus == null && newStatus == null && doValuesContainData && contextStatus.isPresent()) 612 { 613 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 614 setStatus(dataName, contextStatus.get()); 615 hasChanged = true; 616 } 617 else if (newStatus != null && !newStatus.equals(oldStatus)) 618 { 619 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 620 setStatus(dataName, newStatus); 621 hasChanged = true; 622 } 623 624 return hasChanged; 625 } 626 627 /** 628 * Sets the value of the data with the given name 629 * @param <T> the type of the {@link SynchronizationResult} 630 * @param dataName name of the data 631 * @param value the value to set. Give <code>null</code> to empty the value. 632 * @param context context of the data to set 633 * @return the {@link SynchronizationResult} 634 * @throws IllegalArgumentException if the given data name is null or empty 635 * @throws UndefinedItemPathException if the given data name is not defined by the model 636 * @throws BadItemTypeException if the type defined by the model doesn't match the given value to set 637 */ 638 protected <T extends SynchronizationResult> T _setValueForSynchronize(String dataName, SynchronizableValue value, ValueContext context) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException 639 { 640 T result = _createSetValueResultInstance(); 641 642 if (SynchronizableValue.Mode.REMOVE.equals(value.getMode())) 643 { 644 if (DataHolderHelper.hasValueOrEmpty(this, dataName, context)) 645 { 646 if (isMultiple(dataName)) 647 { 648 Object oldValues = DataHolderHelper.getValue(this, dataName, context); 649 Object valuesToRemove = DataHolderHelper.getArrayValuesFromSynchronizableValue(value, context); 650 ElementType type = ((ElementDefinition) getDefinition(dataName)).getType(); 651 652 // Remove the given values from the existent ones 653 Object newValues = DataHolderHelper.removeValuesInArray(oldValues, valuesToRemove, type); 654 655 if (Array.getLength(oldValues) > Array.getLength(newValues)) 656 { 657 result.setHasChanged(true); 658 _setValue(dataName, newValues, context); 659 } 660 } 661 else 662 { 663 result.setHasChanged(true); 664 _removeValue(dataName, context); 665 } 666 } 667 } 668 else if (SynchronizableValue.Mode.APPEND.equals(value.getMode()) && isMultiple(dataName)) 669 { 670 Object valuesToAppend = DataHolderHelper.getArrayValuesFromSynchronizableValue(value, context); 671 if (Array.getLength(valuesToAppend) > 0) 672 { 673 Object oldValues = DataHolderHelper.getValue(this, dataName, context); 674 ElementType type = ((ElementDefinition) getDefinition(dataName)).getType(); 675 676 // Append the given values to the existent ones 677 Object newValues = oldValues != null 678 ? DataHolderHelper.appendValuesInArray(oldValues, valuesToAppend, type) 679 : valuesToAppend; 680 681 result.setHasChanged(true); 682 _setValue(dataName, newValues, context); 683 } 684 } 685 else 686 { 687 boolean hasValueOrEmpty = DataHolderHelper.hasValueOrEmpty(this, dataName, context); 688 boolean hasEmptyValue = hasValueOrEmpty && !DataHolderHelper.hasValue(this, dataName, context); 689 Object newValue = value.getValue(context.getStatus()); 690 691 if (newValue != null || !hasEmptyValue) 692 { 693 if (hasValueOrEmpty) 694 { 695 Object oldValue = DataHolderHelper.getValue(this, dataName, context); 696 ElementType type = ((ElementDefinition) getDefinition(dataName)).getType(); 697 if (type.compareValues(value.getValue(context.getStatus()), oldValue).count() > 0) 698 { 699 // There are differences between old and new value 700 result.setHasChanged(true); 701 _setValue(dataName, newValue, context); 702 } 703 } 704 else 705 { 706 // There was no values, set one 707 result.setHasChanged(true); 708 _setValue(dataName, newValue, context); 709 } 710 } 711 } 712 713 return result; 714 } 715 716 /** 717 * Removes the stored value of the data with the given name 718 * @param <T> the type of the {@link SynchronizationResult} 719 * @param dataName name of the data 720 * @param context context of the data to remove 721 * @return the {@link SynchronizationResult} 722 * @throws IllegalArgumentException if the given data name is null or empty 723 * @throws BadItemTypeException if the value of the parent with the given name is not an item container 724 * @throws UndefinedItemPathException if the given data name is not defined by the model 725 */ 726 protected <T extends SynchronizationResult> T _removeValueForSynchronize(String dataName, ValueContext context) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException 727 { 728 T result = _createSetValueResultInstance(); 729 730 if (DataHolderHelper.hasValueOrEmpty(this, dataName, context)) 731 { 732 _removeValue(dataName, context); 733 result.setHasChanged(true); 734 } 735 736 return result; 737 } 738 739 public void setValue(String dataPath, Object value) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 740 { 741 _setValue(dataPath, value, ValueContext.newInstance()); 742 } 743 744 public void setLocalValue(String dataPath, Object localValue) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 745 { 746 _setValue(dataPath, localValue, ValueContext.newInstance().withStatus(ExternalizableDataStatus.LOCAL)); 747 } 748 749 public void setExternalValue(String dataPath, Object externalValue) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 750 { 751 _setValue(dataPath, externalValue, ValueContext.newInstance().withStatus(ExternalizableDataStatus.EXTERNAL)); 752 } 753 754 /** 755 * Sets the value of the data at the given path 756 * @param dataPath path of the data 757 * @param value the value to set. Give <code>null</code> to empty the value. 758 * @param context context of the data to set 759 * @throws IllegalArgumentException if the given data path is null or empty 760 * @throws UndefinedItemPathException if the given data path is not defined by the model 761 * @throws BadItemTypeException if the type defined by the model doesn't match the given value to set 762 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 763 */ 764 protected void _setValue(String dataPath, Object value, ValueContext context) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 765 { 766 _checkModifiableDefinition(dataPath, "Unable to set the value '" + value + "' at path '" + dataPath + "'."); 767 768 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 769 770 if (pathSegments == null || pathSegments.length < 1) 771 { 772 throw new IllegalArgumentException("Unable to set the value '" + value + "' at the given path. This path is empty."); 773 } 774 else if (pathSegments.length == 1) 775 { 776 ModelItem modelItem = getDefinition(dataPath); 777 778 // Simple path => set the value 779 if (modelItem instanceof ElementDefinition) 780 { 781 String dataName = _getFinalDataName(dataPath, context.getStatus()); 782 _setElementValue((ElementDefinition) modelItem, dataName, value); 783 } 784 else 785 { 786 throw new BadItemTypeException("Unable to set the value '" + value + "' on the data at path '" + dataPath + "' in the repository because it is a group item."); 787 } 788 } 789 else 790 { 791 String parentPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 0, pathSegments.length - 1); 792 793 // Multiple items are allowed only at the last segment of the data path 794 if (isMultiple(parentPath)) 795 { 796 throw new BadDataPathCardinalityException("Unable to set the value '" + value + "' on the data at path '" + dataPath + "'. The segment '" + pathSegments[pathSegments.length - 2] + "' refers to a multiple data and can not be used inside the data path."); 797 } 798 799 Object parentValue = getValue(parentPath); 800 String childName = pathSegments[pathSegments.length - 1]; 801 if (parentValue != null && parentValue instanceof ModifiableModelAwareDataHolder) 802 { 803 ModifiableModelAwareDataHolder parent = (ModifiableModelAwareDataHolder) parentValue; 804 if (context.getStatus().isPresent()) 805 { 806 if (ExternalizableDataStatus.EXTERNAL.equals(context.getStatus().get())) 807 { 808 parent.setExternalValue(childName, value); 809 } 810 else 811 { 812 parent.setLocalValue(childName, value); 813 } 814 } 815 else 816 { 817 parent.setValue(childName, value); 818 } 819 } 820 else 821 { 822 throw new BadItemTypeException("The data at path '" + parentPath + "' in the repository doesn't exist or is not a composite or a repeater entry. It can not contain the data named '" + childName + "'."); 823 } 824 } 825 } 826 827 private void _setElementValue(ElementDefinition definition, String dataName, Object value) 828 { 829 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 830 RepositoryElementType type = (RepositoryElementType) definition.getType(); 831 832 if (definition.isMultiple()) 833 { 834 if (value == null) 835 { 836 type.write(_modifiableRepositoryData, dataName, Array.newInstance(type.getManagedClass(), 0)); 837 } 838 else if (!value.getClass().isArray()) 839 { 840 // The value is single but should be an array. Create the array with the single value 841 Object arrayValue = Array.newInstance(value.getClass(), 1); 842 Array.set(arrayValue, 0, value); 843 type.write(_modifiableRepositoryData, dataName, arrayValue); 844 } 845 else 846 { 847 type.write(_modifiableRepositoryData, dataName, value); 848 } 849 } 850 else 851 { 852 if (type.getManagedClassArray().isInstance(value)) 853 { 854 // The value is multiple but should be single. 855 if (Array.getLength(value) > 1) 856 { 857 throw new IllegalArgumentException("Unable to set the mutilple value '" + value + "' at path '" + dataName + "'. This item is not multiple."); 858 } 859 else 860 { 861 Object singleValue = Array.getLength(value) == 1 ? Array.get(value, 0) : null; 862 type.write(_modifiableRepositoryData, dataName, singleValue); 863 } 864 } 865 else 866 { 867 type.write(_modifiableRepositoryData, dataName, value); 868 } 869 } 870 } 871 872 public void setStatus(String dataPath, ExternalizableDataStatus status) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 873 { 874 _checkModifiableDefinition(dataPath, "Unable to set the status at path '" + dataPath + "'."); 875 876 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 877 878 if (pathSegments == null || pathSegments.length < 1) 879 { 880 throw new IllegalArgumentException("Unable to set the status at the given path. This path is empty."); 881 } 882 else if (pathSegments.length == 1) 883 { 884 ExternalizableDataStatus oldStatus = getStatus(dataPath); 885 boolean hasStatus = _repositoryData.hasValue(dataPath + STATUS_SUFFIX); 886 887 if (!hasStatus || oldStatus != status) 888 { 889 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 890 891 // Set the status if it has not been set yet or if it is different from the new one 892 _modifiableRepositoryData.setValue(dataPath + STATUS_SUFFIX, status.name().toLowerCase()); 893 } 894 895 if (oldStatus != status) 896 { 897 // Switch value and alternative value if the old status is different from the new one 898 ModelItem modelItem = getDefinition(dataPath); 899 if (modelItem instanceof CompositeDefinition) 900 { 901 _setStatus(dataPath, (CompositeDefinition) modelItem); 902 } 903 else if (modelItem instanceof RepeaterDefinition) 904 { 905 _setStatus(dataPath, (RepeaterDefinition) modelItem); 906 } 907 else 908 { 909 _setStatus(dataPath, (ElementDefinition) modelItem); 910 } 911 } 912 } 913 else 914 { 915 String parentPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 0, pathSegments.length - 1); 916 917 // Multiple items are allowed only at the last segment of the data path 918 if (isMultiple(parentPath)) 919 { 920 throw new BadDataPathCardinalityException("Unable to set the status on the data at path '" + dataPath + "'. The segment '" + pathSegments[pathSegments.length - 2] + "' refers to a multiple data and can not be used inside the data path."); 921 } 922 923 Object parentValue = getValue(parentPath); 924 String childName = pathSegments[pathSegments.length - 1]; 925 if (parentValue != null && parentValue instanceof ModifiableModelAwareDataHolder) 926 { 927 ModifiableModelAwareDataHolder parent = (ModifiableModelAwareDataHolder) parentValue; 928 parent.setStatus(childName, status); 929 } 930 else 931 { 932 throw new BadItemTypeException("The data at path '" + parentPath + "' in the repository doesn't exist or is not a composite or a repeater entry. It can not contain the data named '" + childName + "'."); 933 } 934 } 935 } 936 937 private void _setStatus(String dataPath, CompositeDefinition compositeDefinition) 938 { 939 if (_repositoryData.hasValue(dataPath)) 940 { 941 ModifiableModelAwareComposite composite = _getComposite(dataPath, compositeDefinition); 942 ModifiableModelAwareComposite tempComposite = _getComposite(dataPath + __TEMP_SUFFIX, compositeDefinition, true); 943 composite.copyTo(tempComposite); 944 945 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 946 _modifiableRepositoryData.removeValue(dataPath); 947 } 948 949 if (_repositoryData.hasValue(dataPath + ALTERNATIVE_SUFFIX)) 950 { 951 ModifiableModelAwareComposite altComposite = _getComposite(dataPath + ALTERNATIVE_SUFFIX, compositeDefinition); 952 ModifiableModelAwareComposite composite = _getComposite(dataPath, compositeDefinition, true); 953 altComposite.copyTo(composite); 954 955 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 956 _modifiableRepositoryData.removeValue(dataPath + ALTERNATIVE_SUFFIX); 957 } 958 959 if (_repositoryData.hasValue(dataPath + __TEMP_SUFFIX)) 960 { 961 ModifiableModelAwareComposite tempComposite = _getComposite(dataPath + __TEMP_SUFFIX, compositeDefinition); 962 ModifiableModelAwareComposite altComposite = _getComposite(dataPath + ALTERNATIVE_SUFFIX, compositeDefinition, true); 963 tempComposite.copyTo(altComposite); 964 965 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 966 _modifiableRepositoryData.removeValue(dataPath + __TEMP_SUFFIX); 967 } 968 } 969 970 private void _setStatus(String dataPath, RepeaterDefinition repeaterDefinition) 971 { 972 if (_repositoryData.hasValue(dataPath)) 973 { 974 ModifiableModelAwareRepeater repeater = _getRepeater(dataPath, repeaterDefinition); 975 ModifiableModelAwareRepeater tempRepeater = _getRepeater(dataPath + __TEMP_SUFFIX, repeaterDefinition, true); 976 repeater.copyTo(tempRepeater); 977 978 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 979 _modifiableRepositoryData.removeValue(dataPath); 980 } 981 982 if (_repositoryData.hasValue(dataPath + ALTERNATIVE_SUFFIX)) 983 { 984 ModifiableModelAwareRepeater altRepeater = _getRepeater(dataPath + ALTERNATIVE_SUFFIX, repeaterDefinition); 985 ModifiableModelAwareRepeater repeater = _getRepeater(dataPath, repeaterDefinition, true); 986 altRepeater.copyTo(repeater); 987 988 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 989 _modifiableRepositoryData.removeValue(dataPath + ALTERNATIVE_SUFFIX); 990 } 991 992 if (_repositoryData.hasValue(dataPath + __TEMP_SUFFIX)) 993 { 994 ModifiableModelAwareRepeater tempRepeater = _getRepeater(dataPath + __TEMP_SUFFIX, repeaterDefinition); 995 ModifiableModelAwareRepeater altRepeater = _getRepeater(dataPath + ALTERNATIVE_SUFFIX, repeaterDefinition, true); 996 tempRepeater.copyTo(altRepeater); 997 998 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 999 _modifiableRepositoryData.removeValue(dataPath + __TEMP_SUFFIX); 1000 } 1001 } 1002 1003 private void _setStatus(String dataName, ElementDefinition elementDefinition) 1004 { 1005 RepositoryElementType type = (RepositoryElementType) elementDefinition.getType(); 1006 if (type.hasValue(_repositoryData, dataName)) 1007 { 1008 Object value = type.read(_repositoryData, dataName); 1009 1010 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 1011 type.write(_modifiableRepositoryData, dataName + __TEMP_SUFFIX, value); 1012 type.remove(_modifiableRepositoryData, dataName); 1013 } 1014 1015 if (type.hasValue(_repositoryData, dataName + ALTERNATIVE_SUFFIX)) 1016 { 1017 Object altValue = type.read(_repositoryData, dataName + ALTERNATIVE_SUFFIX); 1018 1019 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 1020 type.write(_modifiableRepositoryData, dataName, altValue); 1021 type.remove(_modifiableRepositoryData, dataName + ALTERNATIVE_SUFFIX); 1022 } 1023 1024 if (type.hasValue(_repositoryData, dataName + __TEMP_SUFFIX)) 1025 { 1026 Object tempValue = type.read(_repositoryData, dataName + __TEMP_SUFFIX); 1027 1028 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 1029 type.write(_modifiableRepositoryData, dataName + ALTERNATIVE_SUFFIX, tempValue); 1030 type.remove(_modifiableRepositoryData, dataName + __TEMP_SUFFIX); 1031 } 1032 } 1033 1034 public void removeValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 1035 { 1036 _removeValue(dataPath, ValueContext.newInstance()); 1037 } 1038 1039 public void removeLocalValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 1040 { 1041 _removeValue(dataPath, ValueContext.newInstance().withStatus(ExternalizableDataStatus.LOCAL)); 1042 } 1043 1044 public void removeExternalValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 1045 { 1046 _removeValue(dataPath, ValueContext.newInstance().withStatus(ExternalizableDataStatus.EXTERNAL)); 1047 } 1048 1049 /** 1050 * Removes the stored value of the data at the given path 1051 * @param dataPath path of the data 1052 * @param context context of the data to remove 1053 * @throws IllegalArgumentException if the given data path is null or empty 1054 * @throws BadItemTypeException if the value of the parent of the given path is not an item container 1055 * @throws UndefinedItemPathException if the given data path is not defined by the model 1056 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 1057 */ 1058 protected void _removeValue(String dataPath, ValueContext context) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 1059 { 1060 _checkModifiableDefinition(dataPath, "Unable to retrieve the value at path '" + dataPath + "'."); 1061 1062 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 1063 1064 if (pathSegments == null || pathSegments.length < 1) 1065 { 1066 throw new IllegalArgumentException("Unable to remove the value at the given path. This path is empty."); 1067 } 1068 else if (pathSegments.length == 1) 1069 { 1070 _doRemoveValue(dataPath, context); 1071 } 1072 else 1073 { 1074 String parentPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 0, pathSegments.length - 1); 1075 1076 // Multiple items are allowed only at the last segment of the data path 1077 if (isMultiple(parentPath)) 1078 { 1079 throw new BadDataPathCardinalityException("Unable to remove the value at path '" + dataPath + "'. The segment '" + pathSegments[pathSegments.length - 2] + "' refers to a multiple data and can not be used inside the data path."); 1080 } 1081 1082 Object parentValue = getValue(parentPath); 1083 String childName = pathSegments[pathSegments.length - 1]; 1084 if (parentValue != null && parentValue instanceof ModifiableModelAwareDataHolder) 1085 { 1086 ModifiableModelAwareDataHolder parent = (ModifiableModelAwareDataHolder) parentValue; 1087 if (context.getStatus().isPresent()) 1088 { 1089 if (ExternalizableDataStatus.EXTERNAL.equals(context.getStatus().get())) 1090 { 1091 parent.removeExternalValue(childName); 1092 } 1093 else 1094 { 1095 parent.removeLocalValue(childName); 1096 } 1097 } 1098 else 1099 { 1100 parent.removeValue(childName); 1101 } 1102 } 1103 } 1104 } 1105 1106 private void _doRemoveValue(String dataName, ValueContext context) 1107 { 1108 ModelItem modelItem = getDefinition(dataName); 1109 String finalDataName = _getFinalDataName(dataName, context.getStatus()); 1110 RepositoryModelItemType type = getType(dataName); 1111 if (modelItem instanceof RepeaterDefinition && DataHolderHelper.isRepeaterEntryPath(finalDataName)) 1112 { 1113 Pair<String, Integer> repeaterNameAndEntryPosition = DataHolderHelper.getRepeaterNameAndEntryPosition(finalDataName); 1114 String repeaterName = repeaterNameAndEntryPosition.getLeft(); 1115 int entryPosition = repeaterNameAndEntryPosition.getRight(); 1116 ModifiableModelAwareRepeater repeater = _getRepeater(repeaterName, (RepeaterDefinition) modelItem); 1117 repeater.removeEntry(entryPosition); 1118 } 1119 else 1120 { 1121 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 1122 type.remove(_modifiableRepositoryData, finalDataName); 1123 } 1124 } 1125 1126 public void removeExternalizableMetadataIfExists(String dataPath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 1127 { 1128 _checkModifiableDefinition(dataPath, "Unable to retrieve the value at path '" + dataPath + "'."); 1129 1130 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 1131 1132 if (pathSegments == null || pathSegments.length < 1) 1133 { 1134 throw new IllegalArgumentException("Unable to remove the value at the given path. This path is empty."); 1135 } 1136 else if (pathSegments.length == 1) 1137 { 1138 _removeExternalizableMetadataIfExists(_modifiableRepositoryData, dataPath); 1139 } 1140 else 1141 { 1142 String parentPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 0, pathSegments.length - 1); 1143 1144 // Multiple items are allowed only at the last segment of the data path 1145 if (isMultiple(parentPath)) 1146 { 1147 throw new BadDataPathCardinalityException("Unable to remove the value at path '" + dataPath + "'. The segment '" + pathSegments[pathSegments.length - 2] + "' refers to a multiple data and can not be used inside the data path."); 1148 } 1149 1150 Object parentValue = getValue(parentPath); 1151 String childName = pathSegments[pathSegments.length - 1]; 1152 if (parentValue != null && parentValue instanceof ModifiableModelAwareDataHolder) 1153 { 1154 ModifiableModelAwareDataHolder parent = (ModifiableModelAwareDataHolder) parentValue; 1155 parent.removeExternalizableMetadataIfExists(childName); 1156 } 1157 } 1158 } 1159 1160 private void _removeExternalizableMetadataIfExists(ModifiableRepositoryData repositoryData, String dataName) 1161 { 1162 RepositoryModelItemType type = getType(dataName); 1163 if (type.hasValue(repositoryData, dataName + ALTERNATIVE_SUFFIX)) 1164 { 1165 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 1166 type.remove(repositoryData, dataName + ALTERNATIVE_SUFFIX); 1167 } 1168 1169 if (repositoryData.hasValue(dataName + STATUS_SUFFIX)) 1170 { 1171 _lockableAmetysObject.ifPresent(lao -> lao.setLockInfoOnCurrentContext()); 1172 repositoryData.removeValue(dataName + STATUS_SUFFIX); 1173 } 1174 } 1175 1176 /** 1177 * Creates an instance of {@link SynchronizationResult} 1178 * @param <T> the type of the {@link SynchronizationResult} 1179 * @return the created instance of {@link SynchronizationResult} 1180 */ 1181 @SuppressWarnings("unchecked") 1182 protected <T extends SynchronizationResult> T _createSetValueResultInstance() 1183 { 1184 return (T) new SynchronizationResult(); 1185 } 1186 1187 @Override 1188 public ModifiableRepositoryData getRepositoryData() 1189 { 1190 return _modifiableRepositoryData; 1191 } 1192 1193 @Override 1194 public Optional<? extends ModifiableIndexableDataHolder> getParentDataHolder() 1195 { 1196 return _modifiableParent; 1197 } 1198 1199 @Override 1200 public ModifiableIndexableDataHolder getRootDataHolder() 1201 { 1202 return _modifiableRoot; 1203 } 1204 1205 /** 1206 * Check definition for data path, for modification methods 1207 * @param dataPath the data path 1208 * @param errorMsg the error message to throw 1209 */ 1210 protected void _checkModifiableDefinition(String dataPath, String errorMsg) 1211 { 1212 super._checkDefinition(dataPath, errorMsg); 1213 1214 ModelItem modelItem = getDefinition(dataPath); 1215 if (modelItem instanceof ElementDefinition definition && !definition.isEditable()) 1216 { 1217 throw new UndefinedItemPathException(errorMsg + " The model item '" + modelItem.getPath() + "' can't be modified."); 1218 } 1219 } 1220}