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