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