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.stream.Collectors; 021 022import org.apache.commons.lang3.StringUtils; 023import org.apache.commons.lang3.tuple.Pair; 024 025import org.ametys.plugins.repository.data.UnknownDataException; 026import org.ametys.plugins.repository.data.holder.ModelLessDataHolder; 027import org.ametys.plugins.repository.data.holder.group.Repeater; 028import org.ametys.plugins.repository.data.holder.group.impl.ModelLessComposite; 029import org.ametys.plugins.repository.data.holder.group.impl.ModelLessRepeater; 030import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 031import org.ametys.plugins.repository.data.type.ModelItemTypeConstants; 032import org.ametys.plugins.repository.data.type.RepositoryElementType; 033import org.ametys.plugins.repository.data.type.RepositoryModelItemGroupType; 034import org.ametys.plugins.repository.data.type.RepositoryModelItemType; 035import org.ametys.runtime.model.ModelItem; 036import org.ametys.runtime.model.exception.BadDataPathCardinalityException; 037import org.ametys.runtime.model.exception.BadItemTypeException; 038import org.ametys.runtime.model.exception.NotUniqueTypeException; 039import org.ametys.runtime.model.exception.UnknownTypeException; 040import org.ametys.runtime.model.type.ModelItemType; 041import org.ametys.runtime.plugin.component.AbstractThreadSafeComponentExtensionPoint; 042 043/** 044 * Default implementation for data holder without model 045 */ 046public class DefaultModelLessDataHolder implements ModelLessDataHolder 047{ 048 /** Extension point to use to get available element types */ 049 protected AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> _typeExtensionPoint; 050 051 /** Repository data to use to store data in the repository */ 052 protected RepositoryData _repositoryData; 053 054 /** 055 * Creates a default model free data holder 056 * @param typeExtensionPoint the extension point to use to get available element types 057 * @param repositoryData the repository data to use 058 */ 059 public DefaultModelLessDataHolder(AbstractThreadSafeComponentExtensionPoint<RepositoryModelItemType> typeExtensionPoint, RepositoryData repositoryData) 060 { 061 _typeExtensionPoint = typeExtensionPoint; 062 _repositoryData = repositoryData; 063 } 064 065 public ModelLessComposite getComposite(String compositePath) throws IllegalArgumentException, BadItemTypeException 066 { 067 Object value = getValueOfType(compositePath, ModelItemTypeConstants.COMPOSITE_TYPE_ID); 068 if (value == null) 069 { 070 return null; 071 } 072 else if (value instanceof ModelLessComposite) 073 { 074 return (ModelLessComposite) value; 075 } 076 else 077 { 078 throw new BadItemTypeException("The item at path '" + compositePath + "' is not a composite."); 079 } 080 } 081 082 public ModelLessRepeater getRepeater(String repeaterPath) throws IllegalArgumentException, BadItemTypeException 083 { 084 Object value = getValueOfType(repeaterPath, ModelItemTypeConstants.REPEATER_TYPE_ID); 085 if (value == null) 086 { 087 return null; 088 } 089 else if (value instanceof ModelLessRepeater) 090 { 091 return (ModelLessRepeater) value; 092 } 093 else 094 { 095 throw new BadItemTypeException("The data at path '" + repeaterPath + "' is not a repeater."); 096 } 097 } 098 099 public <T> T getValue(String dataPath) throws IllegalArgumentException, UnknownTypeException, NotUniqueTypeException 100 { 101 try 102 { 103 return getValueOfType(dataPath, getType(dataPath).getId()); 104 } 105 catch (UnknownDataException e) 106 { 107 return null; 108 } 109 } 110 111 public <T> T getValue(String dataPath, T defaultValue) throws IllegalArgumentException, UnknownTypeException, NotUniqueTypeException 112 { 113 try 114 { 115 return getValueOfType(dataPath, getType(dataPath).getId(), defaultValue); 116 } 117 catch (UnknownDataException e) 118 { 119 return defaultValue; 120 } 121 } 122 123 public <T> T getValueOfType(String dataPath, String dataTypeId, T defaultValue) throws IllegalArgumentException, BadItemTypeException 124 { 125 T value = getValueOfType(dataPath, dataTypeId); 126 return value != null ? value : defaultValue; 127 } 128 129 @SuppressWarnings("unchecked") 130 public <T> T getValueOfType(String dataPath, String dataTypeId) throws IllegalArgumentException, BadItemTypeException 131 { 132 if (!_typeExtensionPoint.hasExtension(dataTypeId)) 133 { 134 String availableTypes = StringUtils.join(_typeExtensionPoint.getExtensionsIds(), ", "); 135 throw new UnknownTypeException("The type '" + dataTypeId + "' is not available for the extension point '" + _typeExtensionPoint + "'. Available types are: '" + availableTypes + "'."); 136 } 137 138 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 139 140 if (pathSegments == null || pathSegments.length < 1) 141 { 142 throw new IllegalArgumentException("Unable to retrieve the data at the given path. This path is empty."); 143 } 144 else if (pathSegments.length == 1) 145 { 146 // Simple path => get the value 147 RepositoryModelItemType type = _typeExtensionPoint.getExtension(dataTypeId); 148 if (type instanceof RepositoryElementType) 149 { 150 return (T) ((RepositoryElementType) type).read(_repositoryData, dataPath); 151 } 152 else 153 { 154 Pair<String, Integer> repeaterNameAndEntryPosition = DataHolderHelper.getRepeaterNameAndEntryPosition(dataPath); 155 if (repeaterNameAndEntryPosition != null) 156 { 157 return (T) DataHolderHelper.getRepeaterEntry(this, repeaterNameAndEntryPosition.getLeft(), repeaterNameAndEntryPosition.getRight()); 158 } 159 else 160 { 161 if (ModelItemTypeConstants.REPEATER_TYPE_ID.equals(type.getId())) 162 { 163 return (T) _getRepeater(dataPath); 164 } 165 else 166 { 167 return (T) _getComposite(dataPath); 168 } 169 } 170 } 171 } 172 else 173 { 174 // Path where current part is a data holder 175 ModelLessDataHolder parent = _getParentValue(dataPath); 176 if (parent == null) 177 { 178 return null; 179 } 180 else 181 { 182 String childName = dataPath.substring(dataPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR) + 1); 183 return parent.getValueOfType(childName, dataTypeId); 184 } 185 } 186 } 187 188 /** 189 * Retrieves the composite with the given name 190 * @param name name of the composite to retrieve 191 * @return the composite 192 * @throws BadItemTypeException if the value stored in the repository with the given name is not a composite 193 */ 194 protected ModelLessComposite _getComposite(String name) throws BadItemTypeException 195 { 196 RepositoryModelItemGroupType type = (RepositoryModelItemGroupType) _typeExtensionPoint.getExtension(ModelItemTypeConstants.COMPOSITE_TYPE_ID); 197 RepositoryData compositeRepositoryData = type.read(_repositoryData, name); 198 199 if (compositeRepositoryData != null) 200 { 201 return new ModelLessComposite(_typeExtensionPoint, compositeRepositoryData); 202 } 203 else 204 { 205 return null; 206 } 207 } 208 209 /** 210 * Retrieves the repeater with the given name 211 * @param name name of the repeater to retrieve 212 * @return the repeater 213 * @throws BadItemTypeException if the value stored in the repository with the given name is not a repeater 214 */ 215 protected ModelLessRepeater _getRepeater(String name) throws BadItemTypeException 216 { 217 RepositoryModelItemGroupType type = (RepositoryModelItemGroupType) _typeExtensionPoint.getExtension(ModelItemTypeConstants.REPEATER_TYPE_ID); 218 RepositoryData repeaterRepositoryData = type.read(_repositoryData, name); 219 220 if (repeaterRepositoryData != null) 221 { 222 return new ModelLessRepeater(_typeExtensionPoint, repeaterRepositoryData); 223 } 224 else 225 { 226 return null; 227 } 228 } 229 230 public boolean hasValue(String dataPath) throws IllegalArgumentException 231 { 232 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 233 234 if (pathSegments == null || pathSegments.length < 1) 235 { 236 throw new IllegalArgumentException("The data path is empty. It is not possible to determine if it has a value or not."); 237 } 238 else if (pathSegments.length == 1) 239 { 240 Pair<String, Integer> repeaterNameAndEntryPosition = DataHolderHelper.getRepeaterNameAndEntryPosition(dataPath); 241 if (repeaterNameAndEntryPosition != null) 242 { 243 if (_repositoryData.hasValue(repeaterNameAndEntryPosition.getLeft())) 244 { 245 Repeater repeater = _getRepeater(repeaterNameAndEntryPosition.getLeft()); 246 return repeater.hasEntry(repeaterNameAndEntryPosition.getRight()); 247 } 248 else 249 { 250 return false; 251 } 252 } 253 else 254 { 255 return _repositoryData.hasValue(dataPath); 256 } 257 } 258 else 259 { 260 // Path where current part is a data holder 261 ModelLessDataHolder parent = _getParentValue(dataPath); 262 if (parent == null) 263 { 264 return false; 265 } 266 else 267 { 268 String childName = dataPath.substring(dataPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR) + 1); 269 return parent.hasValue(childName); 270 } 271 } 272 } 273 274 public boolean isMultiple(String dataPath) throws IllegalArgumentException, UnknownDataException 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 is multiple or not."); 281 } 282 else if (pathSegments.length == 1) 283 { 284 Pair<String, Integer> repeaterNameAndEntryPosition = DataHolderHelper.getRepeaterNameAndEntryPosition(dataPath); 285 if (repeaterNameAndEntryPosition != null) 286 { 287 return false; 288 } 289 else 290 { 291 return _repositoryData.isMultiple(dataPath); 292 } 293 } 294 else 295 { 296 // Path where current part is a data holder 297 ModelLessDataHolder parent = _getParentValue(dataPath); 298 if (parent == null) 299 { 300 throw new UnknownDataException("The data at path '" + dataPath + "' does not exist. It is not possible to determine if it is multiple or not."); 301 } 302 else 303 { 304 String childName = dataPath.substring(dataPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR) + 1); 305 return parent.isMultiple(childName); 306 } 307 } 308 } 309 310 public ModelItemType getType(String dataPath) throws IllegalArgumentException, UnknownTypeException, UnknownDataException, NotUniqueTypeException 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 its type."); 317 } 318 else if (pathSegments.length == 1) 319 { 320 return _getType(dataPath); 321 } 322 else 323 { 324 // Path where current part is a data holder 325 ModelLessDataHolder parent = _getParentValue(dataPath); 326 if (parent == null) 327 { 328 throw new UnknownDataException("The data at path '" + dataPath + "' does not exist. It is not possible to determine its type."); 329 } 330 else 331 { 332 String childName = dataPath.substring(dataPath.lastIndexOf(ModelItem.ITEM_PATH_SEPARATOR) + 1); 333 return parent.getType(childName); 334 } 335 } 336 } 337 338 private ModelItemType _getType(String dataName) throws UnknownTypeException, UnknownDataException, NotUniqueTypeException 339 { 340 Pair<String, Integer> repeaterNameAndEntryPosition = DataHolderHelper.getRepeaterNameAndEntryPosition(dataName); 341 if (repeaterNameAndEntryPosition != null) 342 { 343 if (_typeExtensionPoint.hasExtension(ModelItemTypeConstants.COMPOSITE_TYPE_ID)) 344 { 345 return _typeExtensionPoint.getExtension(ModelItemTypeConstants.COMPOSITE_TYPE_ID); 346 } 347 else 348 { 349 throw new UnknownTypeException("Unable to retrieve the type of the data '" + dataName + "'. This data is a repeater entry but composites are not allowed for this object"); 350 } 351 } 352 else 353 { 354 List<RepositoryModelItemType> compatibleTypes = _typeExtensionPoint.getExtensionsIds().stream() 355 .map(id -> _typeExtensionPoint.getExtension(id)) 356 .filter(type -> type.isCompatible(_repositoryData, dataName)) 357 .collect(Collectors.toList()); 358 if (compatibleTypes.isEmpty()) 359 { 360 // The type has not been found, thrown an UnknownTypeException 361 throw new UnknownTypeException("Unable to retrieve the type of the data '" + dataName + "'. No compatible type have been found."); 362 } 363 else if (compatibleTypes.size() > 1) 364 { 365 // Many types have been found, thrown an UnknownTypeException 366 List<String> compatibleTypesIds = compatibleTypes.stream().map(type -> type.getId()).collect(Collectors.toList()); 367 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, ", ")); 368 } 369 else 370 { 371 return compatibleTypes.get(0); 372 } 373 } 374 } 375 376 /** 377 * Retrieves the data holder, last parent segment of the given data path 378 * Example : call this method with a path like 'my-composite/my-repeater[1]/my-data' will retrieve the first repeater entry of the repeater 'my-repeater' in the composite 'my-composite' 379 * @param dataPath the data path 380 * @return the parent data holder 381 */ 382 protected ModelLessDataHolder _getParentValue(String dataPath) 383 { 384 String[] pathSegments = StringUtils.split(dataPath, ModelItem.ITEM_PATH_SEPARATOR); 385 String parentPath = StringUtils.join(pathSegments, ModelItem.ITEM_PATH_SEPARATOR, 0, pathSegments.length - 1); 386 387 try 388 { 389 // Multiple items are allowed only at the last segment of the data path 390 if (isMultiple(parentPath)) 391 { 392 throw new BadDataPathCardinalityException("Unable to get the parent of 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."); 393 } 394 } 395 catch (UnknownDataException e) 396 { 397 return null; 398 } 399 400 try 401 { 402 return getValue(parentPath); 403 } 404 catch (NotUniqueTypeException e) 405 { 406 // This case can occur whit old node type (compositeMetadata), used by both composites and repeaters 407 return getValueOfType(parentPath, ModelItemTypeConstants.COMPOSITE_TYPE_ID); 408 } 409 } 410 411 public Collection<String> getDataNames() 412 { 413 return _repositoryData.getDataNames(); 414 } 415}