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.util.Collection; 019import java.util.List; 020import java.util.Optional; 021import java.util.stream.Collectors; 022 023import org.apache.commons.lang3.StringUtils; 024import org.xml.sax.ContentHandler; 025import org.xml.sax.SAXException; 026 027import org.ametys.plugins.repository.RepositoryConstants; 028import org.ametys.plugins.repository.data.UnknownDataException; 029import org.ametys.plugins.repository.data.ametysobject.DataAwareAmetysObject; 030import org.ametys.plugins.repository.data.holder.DataHolder; 031import org.ametys.plugins.repository.data.holder.ModelLessDataHolder; 032import org.ametys.plugins.repository.data.holder.group.impl.ModelLessComposite; 033import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 034import org.ametys.plugins.repository.data.type.ModelItemTypeConstants; 035import org.ametys.plugins.repository.data.type.RepositoryElementType; 036import org.ametys.plugins.repository.data.type.RepositoryModelItemGroupType; 037import org.ametys.plugins.repository.data.type.RepositoryModelItemType; 038import org.ametys.runtime.model.ModelItem; 039import org.ametys.runtime.model.exception.BadDataPathCardinalityException; 040import org.ametys.runtime.model.exception.BadItemTypeException; 041import org.ametys.runtime.model.exception.NotUniqueTypeException; 042import org.ametys.runtime.model.exception.UnknownTypeException; 043import org.ametys.runtime.model.type.DataContext; 044import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint; 045 046/** 047 * Default implementation for data holder without model 048 */ 049public class DefaultModelLessDataHolder implements ModelLessDataHolder 050{ 051 /** Extension point to use to get available element types */ 052 protected AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> _typeExtensionPoint; 053 054 /** Repository data to use to store data in the repository */ 055 protected RepositoryData _repositoryData; 056 057 /** Parent of the current {@link DataHolder} */ 058 protected Optional<? extends ModelLessDataHolder> _parent; 059 060 /** Root {@link DataHolder} */ 061 protected ModelLessDataHolder _root; 062 063 /** 064 * Creates a default model free data holder 065 * @param typeExtensionPoint the extension point to use to get available element types 066 * @param repositoryData the repository data to use 067 */ 068 public DefaultModelLessDataHolder(AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> typeExtensionPoint, RepositoryData repositoryData) 069 { 070 this(typeExtensionPoint, repositoryData, Optional.empty(), Optional.empty()); 071 } 072 073 /** 074 * Creates a default model free data holder 075 * @param typeExtensionPoint the extension point to use to get available element types 076 * @param parent the parent of the created {@link DataHolder}, empty if the created {@link DataHolder} is the root {@link DataHolder} 077 * @param root the root {@link DataAwareAmetysObject} 078 * @param repositoryData the repository data to use 079 */ 080 public DefaultModelLessDataHolder(AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> typeExtensionPoint, RepositoryData repositoryData, Optional<? extends ModelLessDataHolder> parent, Optional<? extends ModelLessDataHolder> root) 081 { 082 _typeExtensionPoint = typeExtensionPoint; 083 _repositoryData = repositoryData; 084 085 _parent = parent; 086 _root = root.map(ModelLessDataHolder.class::cast) 087 .or(() -> _parent.map(ModelLessDataHolder::getRootDataHolder)) // if no root is specified but a parent, the root is the parent's root 088 .orElse(this); // if no root or parent is specified, the root is the current DataHolder 089 } 090 091 public ModelLessComposite getComposite(String compositePath) throws IllegalArgumentException, BadItemTypeException 092 { 093 Object value = getValueOfType(compositePath, ModelItemTypeConstants.COMPOSITE_TYPE_ID); 094 if (value == null) 095 { 096 return null; 097 } 098 else if (value instanceof ModelLessComposite) 099 { 100 return (ModelLessComposite) value; 101 } 102 else 103 { 104 throw new BadItemTypeException("The item at path '" + compositePath + "' is not a composite."); 105 } 106 } 107 108 public <T> T getValue(String dataPath) throws IllegalArgumentException, UnknownTypeException, NotUniqueTypeException 109 { 110 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 111 112 if (pathSegments == null || pathSegments.length < 1) 113 { 114 throw new IllegalArgumentException("Unable to retrieve the data at the given path. This path is empty."); 115 } 116 else if (pathSegments.length == 1) 117 { 118 try 119 { 120 if (_repositoryData.hasValue(dataPath + RepositoryModelItemType.EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL)) 121 { 122 return null; 123 } 124 else 125 { 126 return getValueOfType(dataPath, getType(dataPath).getId()); 127 } 128 } 129 catch (UnknownDataException e) 130 { 131 return null; 132 } 133 } 134 else 135 { 136 // Path where current part is a data holder 137 ModelLessDataHolder parent = _getParentValue(this, dataPath); 138 if (parent == null) 139 { 140 return null; 141 } 142 else 143 { 144 String childName = dataPath.substring(dataPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR) + 1); 145 return parent.getValue(childName); 146 } 147 } 148 } 149 150 public <T> T getValue(String dataPath, T defaultValue) throws IllegalArgumentException, UnknownTypeException, NotUniqueTypeException 151 { 152 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 153 154 if (pathSegments == null || pathSegments.length < 1) 155 { 156 throw new IllegalArgumentException("Unable to retrieve the data at the given path. This path is empty."); 157 } 158 else if (pathSegments.length == 1) 159 { 160 try 161 { 162 if (_repositoryData.hasValue(dataPath + RepositoryModelItemType.EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL)) 163 { 164 return defaultValue; 165 } 166 else 167 { 168 return getValueOfType(dataPath, getType(dataPath).getId(), defaultValue); 169 } 170 } 171 catch (UnknownDataException e) 172 { 173 return defaultValue; 174 } 175 } 176 else 177 { 178 // Path where current part is a data holder 179 ModelLessDataHolder parent = _getParentValue(this, dataPath); 180 if (parent == null) 181 { 182 return defaultValue; 183 } 184 else 185 { 186 String childName = dataPath.substring(dataPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR) + 1); 187 return parent.getValue(childName, defaultValue); 188 } 189 } 190 } 191 192 public <T> T getValueOfType(String dataPath, String dataTypeId, T defaultValue) throws IllegalArgumentException, BadItemTypeException 193 { 194 if (!_typeExtensionPoint.hasExtension(dataTypeId)) 195 { 196 String availableTypes = StringUtils.join(_typeExtensionPoint.getExtensionsIds(), ", "); 197 throw new UnknownTypeException("The type '" + dataTypeId + "' is not available for the extension point '" + _typeExtensionPoint + "'. Available types are: '" + availableTypes + "'."); 198 } 199 RepositoryModelItemType type = _typeExtensionPoint.getExtension(dataTypeId); 200 201 if (_hasValue(this, dataPath, type)) 202 { 203 return getValueOfType(dataPath, dataTypeId); 204 } 205 206 return defaultValue; 207 } 208 209 @SuppressWarnings("unchecked") 210 public <T> T getValueOfType(String dataPath, String dataTypeId) throws IllegalArgumentException, UnknownTypeException, BadItemTypeException, BadDataPathCardinalityException 211 { 212 if (!_typeExtensionPoint.hasExtension(dataTypeId)) 213 { 214 String availableTypes = StringUtils.join(_typeExtensionPoint.getExtensionsIds(), ", "); 215 throw new UnknownTypeException("The type '" + dataTypeId + "' is not available for the extension point '" + _typeExtensionPoint + "'. Available types are: '" + availableTypes + "'."); 216 } 217 218 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 219 220 if (pathSegments == null || pathSegments.length < 1) 221 { 222 throw new IllegalArgumentException("Unable to retrieve the data at the given path. This path is empty."); 223 } 224 else if (pathSegments.length == 1) 225 { 226 // Simple path => get the value 227 RepositoryModelItemType type = _typeExtensionPoint.getExtension(dataTypeId); 228 if (type instanceof RepositoryElementType) 229 { 230 return (T) ((RepositoryElementType) type).read(_repositoryData, dataPath); 231 } 232 else 233 { 234 return (T) _getComposite(dataPath); 235 } 236 } 237 else 238 { 239 // Path where current part is a data holder 240 ModelLessDataHolder parent = _getParentValue(this, dataPath); 241 if (parent == null) 242 { 243 return null; 244 } 245 else 246 { 247 String childName = dataPath.substring(dataPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR) + 1); 248 return parent.getValueOfType(childName, dataTypeId); 249 } 250 } 251 } 252 253 /** 254 * Retrieves the composite with the given name 255 * @param name name of the composite to retrieve 256 * @return the composite 257 * @throws BadItemTypeException if the value stored in the repository with the given name is not a composite 258 */ 259 protected ModelLessComposite _getComposite(String name) throws BadItemTypeException 260 { 261 RepositoryModelItemGroupType type = (RepositoryModelItemGroupType) _typeExtensionPoint.getExtension(ModelItemTypeConstants.COMPOSITE_TYPE_ID); 262 RepositoryData compositeRepositoryData = type.read(_repositoryData, name); 263 264 if (compositeRepositoryData != null) 265 { 266 return new ModelLessComposite(_typeExtensionPoint, compositeRepositoryData, this, _root); 267 } 268 else 269 { 270 return null; 271 } 272 } 273 274 public boolean hasValue(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException 275 { 276 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 277 278 if (pathSegments == null || pathSegments.length < 1) 279 { 280 throw new IllegalArgumentException("The data path is empty. It is not possible to determine if it has a value or not."); 281 } 282 else if (pathSegments.length == 1) 283 { 284 try 285 { 286 RepositoryModelItemType type = getType(dataPath); 287 return type.hasNonEmptyValue(_repositoryData, dataPath); 288 } 289 catch (UnknownDataException | UnknownTypeException | NotUniqueTypeException e) 290 { 291 return false; 292 } 293 } 294 else 295 { 296 // Path where current part is a data holder 297 ModelLessDataHolder parent = _getParentValue(this, dataPath); 298 if (parent == null) 299 { 300 return false; 301 } 302 else 303 { 304 String childName = StringUtils.substringAfterLast(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 305 return parent.hasValue(childName); 306 } 307 } 308 } 309 310 public boolean hasValueOrEmpty(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException 311 { 312 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 313 314 if (pathSegments == null || pathSegments.length < 1) 315 { 316 throw new IllegalArgumentException("The data path is empty. It is not possible to determine if it has a value or not."); 317 } 318 else if (pathSegments.length == 1) 319 { 320 return _repositoryData.hasValue(dataPath) || _repositoryData.hasValue(dataPath + RepositoryModelItemType.EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL); 321 } 322 else 323 { 324 // Path where current part is a data holder 325 ModelLessDataHolder parent = _getParentValue(this, dataPath); 326 if (parent == null) 327 { 328 return false; 329 } 330 else 331 { 332 String childName = StringUtils.substringAfterLast(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 333 return parent.hasValueOrEmpty(childName); 334 } 335 } 336 } 337 338 /** 339 * Checks if there is a non empty value for the data at the given path 340 * @param dataHolder the data holder 341 * @param dataPath path of the data 342 * @param dataType the type of the data 343 * @return <code>true</code> if there is a non empty value for the data, <code>false</code> otherwise 344 * @throws IllegalArgumentException if the given data path is null or empty 345 * @throws BadDataPathCardinalityException if the value of a part of the data path is multiple. Only the last part can be multiple 346 */ 347 protected static boolean _hasValue(ModelLessDataHolder dataHolder, String dataPath, RepositoryModelItemType dataType) throws IllegalArgumentException, BadDataPathCardinalityException 348 { 349 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 350 351 if (pathSegments == null || pathSegments.length < 1) 352 { 353 throw new IllegalArgumentException("The data path is empty. It is not possible to determine if it has a value or not."); 354 } 355 else if (pathSegments.length == 1) 356 { 357 return dataType.hasNonEmptyValue(dataHolder.getRepositoryData(), dataPath); 358 } 359 else 360 { 361 // Path where current part is a data holder 362 ModelLessDataHolder parent = _getParentValue(dataHolder, dataPath); 363 if (parent == null) 364 { 365 return false; 366 } 367 else 368 { 369 String childName = StringUtils.substringAfterLast(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 370 return _hasValue(parent, childName, dataType); 371 } 372 } 373 } 374 375 public boolean isMultiple(String dataPath) throws IllegalArgumentException, UnknownDataException, NotUniqueTypeException, BadDataPathCardinalityException 376 { 377 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 378 379 if (pathSegments == null || pathSegments.length < 1) 380 { 381 throw new IllegalArgumentException("The data path is empty. It is not possible to determine if it is multiple or not."); 382 } 383 else if (pathSegments.length == 1) 384 { 385 RepositoryModelItemType type = getType(dataPath); 386 return type.isMultiple(_repositoryData, dataPath); 387 } 388 else 389 { 390 // Path where current part is a data holder 391 ModelLessDataHolder parent = _getParentValue(this, dataPath); 392 if (parent == null) 393 { 394 throw new UnknownDataException("The data at path '" + dataPath + "' does not exist. It is not possible to determine if it is multiple or not."); 395 } 396 else 397 { 398 String childName = dataPath.substring(dataPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR) + 1); 399 return parent.isMultiple(childName); 400 } 401 } 402 } 403 404 public RepositoryModelItemType getType(String dataPath) throws IllegalArgumentException, UnknownTypeException, UnknownDataException, NotUniqueTypeException 405 { 406 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 407 408 if (pathSegments == null || pathSegments.length < 1) 409 { 410 throw new IllegalArgumentException("The data path is empty. It is not possible to determine its type."); 411 } 412 else if (pathSegments.length == 1) 413 { 414 return _getType(dataPath); 415 } 416 else 417 { 418 // Path where current part is a data holder 419 ModelLessDataHolder parent = _getParentValue(this, dataPath); 420 if (parent == null) 421 { 422 throw new UnknownDataException("The data at path '" + dataPath + "' does not exist. It is not possible to determine its type."); 423 } 424 else 425 { 426 String childName = dataPath.substring(dataPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR) + 1); 427 return parent.getType(childName); 428 } 429 } 430 } 431 432 private RepositoryModelItemType _getType(String dataName) throws UnknownTypeException, UnknownDataException, NotUniqueTypeException 433 { 434 List<RepositoryModelItemType> compatibleTypes = _typeExtensionPoint.getExtensionsIds().stream() 435 .map(id -> _typeExtensionPoint.getExtension(id)) 436 .filter(type -> type.isCompatible(_repositoryData, dataName)) 437 .collect(Collectors.toList()); 438 if (compatibleTypes.isEmpty()) 439 { 440 // The type has not been found, thrown an UnknownTypeException 441 throw new UnknownTypeException("Unable to retrieve the type of the data '" + dataName + "'. No compatible type have been found."); 442 } 443 else if (compatibleTypes.size() > 1) 444 { 445 // Many types have been found, thrown an UnknownTypeException 446 List<String> compatibleTypesIds = compatibleTypes.stream().map(type -> type.getId()).collect(Collectors.toList()); 447 throw new NotUniqueTypeException("Unable to retrieve the type of the data '" + dataName + "'. Many compatible types have been found, there is no way to determine which one is the good one. Compatible types found are: " + StringUtils.join(compatibleTypesIds, ", ")); 448 } 449 else 450 { 451 return compatibleTypes.get(0); 452 } 453 } 454 455 /** 456 * Retrieves the data holder, last parent segment of the given data path 457 * Example : call this method with a path like 'my-composite1/my-composite2/my-data' will retrieve the composite 'my-composite2' in the composite 'my-composite1' 458 * @param dataHolder the data holder 459 * @param dataPath the data path 460 * @return the parent data holder 461 * @throws BadDataPathCardinalityException if the value of a part of the data path is multiple. Only the last part can be multiple 462 * @throws BadItemTypeException if the value at the given data path is not a composite 463 */ 464 protected static ModelLessDataHolder _getParentValue(ModelLessDataHolder dataHolder, String dataPath) throws BadDataPathCardinalityException, BadItemTypeException 465 { 466 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 467 String parentPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 0, pathSegments.length - 1); 468 469 return dataHolder.getValueOfType(parentPath, ModelItemTypeConstants.COMPOSITE_TYPE_ID); 470 } 471 472 public Collection<String> getDataNames() 473 { 474 return _repositoryData.getDataNames(); 475 } 476 477 public void dataToSAX(ContentHandler contentHandler, String dataPath, DataContext context) throws SAXException, UnknownTypeException, NotUniqueTypeException 478 { 479 DataHolderHelper.dataToSAX(this, contentHandler, dataPath, context.withDataPath(dataPath)); 480 } 481 482 public void dataToSAX(ContentHandler contentHandler, DataContext context) throws SAXException, UnknownTypeException, NotUniqueTypeException 483 { 484 DataHolderHelper.dataToSAX(this, contentHandler, context); 485 } 486 487 public RepositoryData getRepositoryData() 488 { 489 return _repositoryData; 490 } 491 492 public Optional<? extends ModelLessDataHolder> getParentDataHolder() 493 { 494 return _parent; 495 } 496 497 public ModelLessDataHolder getRootDataHolder() 498 { 499 return _root; 500 } 501}