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; 017 018import java.io.IOException; 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.xml.sax.ContentHandler; 026import org.xml.sax.SAXException; 027 028import org.ametys.plugins.repository.AmetysObject; 029import org.ametys.plugins.repository.data.DataComment; 030import org.ametys.plugins.repository.data.external.ExternalizableDataProvider.ExternalizableDataStatus; 031import org.ametys.plugins.repository.data.holder.group.impl.ModelAwareComposite; 032import org.ametys.plugins.repository.data.holder.group.impl.ModelAwareRepeater; 033import org.ametys.plugins.repository.data.holder.impl.DataHolderHelper; 034import org.ametys.plugins.repository.data.type.RepositoryModelItemType; 035import org.ametys.plugins.repository.model.RepeaterDefinition; 036import org.ametys.runtime.model.ElementDefinition; 037import org.ametys.runtime.model.ModelItem; 038import org.ametys.runtime.model.ModelItemContainer; 039import org.ametys.runtime.model.ViewHelper; 040import org.ametys.runtime.model.ViewItemAccessor; 041import org.ametys.runtime.model.exception.BadDataPathCardinalityException; 042import org.ametys.runtime.model.exception.BadItemTypeException; 043import org.ametys.runtime.model.exception.UndefinedItemPathException; 044import org.ametys.runtime.model.type.DataContext; 045 046/** 047 * Interface for data containers with models 048 */ 049public interface ModelAwareDataHolder extends DataHolder 050{ 051 /** Suffix used for the alternative value */ 052 public static final String ALTERNATIVE_SUFFIX = "__alt"; 053 /** Suffix used for the status value */ 054 public static final String STATUS_SUFFIX = "__status"; 055 /** Suffix used for the comments */ 056 public static final String COMMENTS_SUFFIX = "_comments"; 057 058 /** 059 * {@inheritDoc} 060 * @throws UndefinedItemPathException if the given composite path is not defined by the model 061 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 062 */ 063 @Override 064 public ModelAwareComposite getComposite(String compositePath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException; 065 066 /** 067 * Retrieves the local composite at the given path 068 * @param compositePath path of the externalizable composite to retrieve 069 * @return the composite or <code>null</code> if not exists or is empty 070 * @throws IllegalArgumentException if the given composite path is null or empty 071 * @throws BadItemTypeException if the stored value at the given path is not a composite 072 * @throws UndefinedItemPathException if the given composite path is not defined by the model 073 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 074 */ 075 public ModelAwareComposite getLocalComposite(String compositePath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException; 076 077 /** 078 * Retrieves the external composite at the given path 079 * @param compositePath path of the externalizable composite to retrieve 080 * @return the composite or <code>null</code> if not exists or is empty 081 * @throws IllegalArgumentException if the given composite path is null or empty 082 * @throws BadItemTypeException if the stored value at the given path is not a composite 083 * @throws UndefinedItemPathException if the given composite path is not defined by the model 084 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 085 */ 086 public ModelAwareComposite getExternalComposite(String compositePath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException; 087 088 /** 089 * Retrieves the repeater at the given path 090 * @param repeaterPath path of the repeater to retrieve 091 * @return the repeater or <code>null</code> if not exists or is empty 092 * @throws IllegalArgumentException if the given repeater path is null or empty 093 * @throws BadItemTypeException if the stored value at the given path is not a repeater 094 * @throws UndefinedItemPathException if the given repeater path is not defined by the model 095 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 096 */ 097 public ModelAwareRepeater getRepeater(String repeaterPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException; 098 099 /** 100 * Retrieves the local repeater at the given path 101 * @param repeaterPath path of the externalizable repeater to retrieve 102 * @return the repeater or <code>null</code> if not exists or is empty 103 * @throws IllegalArgumentException if the given repeater path is null or empty 104 * @throws BadItemTypeException if the stored value at the given path is not a repeater 105 * @throws UndefinedItemPathException if the given repeater path is not defined by the model 106 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 107 */ 108 public ModelAwareRepeater getLocalRepeater(String repeaterPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException; 109 110 /** 111 * Retrieves the external repeater at the given path 112 * @param repeaterPath path of the externalizable repeater to retrieve 113 * @return the repeater or <code>null</code> if not exists or is empty 114 * @throws IllegalArgumentException if the given repeater path is null or empty 115 * @throws BadItemTypeException if the stored value at the given path is not a repeater 116 * @throws UndefinedItemPathException if the given repeater path is not defined by the model 117 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 118 */ 119 public ModelAwareRepeater getExternalRepeater(String repeaterPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException; 120 121 /** 122 * {@inheritDoc} 123 * @return <code>true</code> if the data at the given path is defined by the model, if there is a value for the data (even empty) and if the type of this value matches the type of the definition. <code>false</code> otherwise 124 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 125 */ 126 @Override 127 public boolean hasValue(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException; 128 129 /** 130 * Checks if there is a local value for the data at the given path 131 * @param dataPath path of the externalizable data 132 * @return <code>true</code> if the data at the given path is defined by the model, if there is a local value for the data (even empty) and if the type of this value matches the type of the definition. <code>false</code> otherwise 133 * @throws IllegalArgumentException if the given data path is null or empty 134 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 135 */ 136 public boolean hasLocalValue(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException; 137 138 /** 139 * Checks if there is an external value for the data at the given path 140 * @param dataPath path of the externalizable data 141 * @return <code>true</code> if the data at the given path is defined by the model, if there is an external value for the data (even empty) and if the type of this value matches the type of the definition. <code>false</code> otherwise 142 * @throws IllegalArgumentException if the given data path is null or empty 143 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 144 */ 145 public boolean hasExternalValue(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException; 146 147 /** 148 * {@inheritDoc} 149 * @return <code>true</code> if the data at the given path is defined by the model, if there is a non empty value for the data and if the type of this value matches the type of the definition. <code>false</code> otherwise 150 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 151 */ 152 @Override 153 public boolean hasNonEmptyValue(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException; 154 155 /** 156 * Checks if there is a non empty local value for the data at the given path 157 * @param dataPath path of the externalizable data 158 * @return <code>true</code> if the data at the given path is defined by the model, if there is a non empty local value for the data and if the type of this value matches the type of the definition. <code>false</code> otherwise 159 * @throws IllegalArgumentException if the given data path is null or empty 160 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 161 */ 162 public boolean hasNonEmptyLocalValue(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException; 163 164 /** 165 * Checks if there is a non empty external value for the data at the given path 166 * @param dataPath path of the externalizable data 167 * @return <code>true</code> if the data at the given path is defined by the model, if there is a non empty external value for the data and if the type of this value matches the type of the definition. <code>false</code> otherwise 168 * @throws IllegalArgumentException if the given data path is null or empty 169 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 170 */ 171 public boolean hasNonEmptyExternalValue(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException; 172 173 /** 174 * Checks if there is are comments on the data with the given name 175 * @param dataName name of the data 176 * @return <code>true</code> if there are comments on the data, <code>false</code> otherwise 177 * @throws IllegalArgumentException if the given data name is null or empty 178 * @throws UndefinedItemPathException if the given data name is not defined by the model 179 */ 180 public boolean hasComments(String dataName) throws IllegalArgumentException, UndefinedItemPathException; 181 182 /** 183 * {@inheritDoc} 184 * @return the names of the data contained by this data holder and that are defined by the model 185 */ 186 @Override 187 public Collection<String> getDataNames(); 188 189 /** 190 * Retrieves the value of the data at the given path 191 * @param <T> type of the value to retrieve 192 * @param dataPath path of the data 193 * @return the value of the data or <code>null</code> if not exists or is empty. The object returned may be of a generic class defined by the storage (if the model is unknown). For example, an url may be returned as a String. 194 * @throws IllegalArgumentException if the given data path is null or empty 195 * @throws UndefinedItemPathException if the given data path is not defined by the model 196 * @throws BadItemTypeException if the type defined by the model doesn't match the type of the stored value 197 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 198 */ 199 public default <T extends Object> T getValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException 200 { 201 return getValue(dataPath, false); 202 } 203 204 /** 205 * Retrieves the value of the data at the given path 206 * @param <T> type of the value to retrieve 207 * @param dataPath path of the data 208 * @param allowMultiValuedPathSegments <code>true</code> to allow multi-valued segments in the path (not necessarily at the last segment), <code>false</code> otherwise. 209 * If <code>true</code>, if there is no indicated entry for a repeater, the values of all the entries are retrieved 210 * If <code>true</code> and if there are multiple values, all data are retrieved in one array 211 * @return the value of the data or <code>null</code> if allowMultiValuedPathSegments is <code>false</code> and there is no non empty value. The object returned may be of a generic class defined by the storage (if the model is unknown). For example, an url may be returned as a String. 212 * @throws IllegalArgumentException if the given data path is null or empty 213 * @throws UndefinedItemPathException if the given data path is not defined by the model 214 * @throws BadItemTypeException if the type defined by the model doesn't match the type of the stored value 215 * @throws BadDataPathCardinalityException if the managesMultiples boolean is <code>false</code> and the definition of a part of the data path is multiple. Only the last part can be multiple 216 */ 217 public <T extends Object> T getValue(String dataPath, boolean allowMultiValuedPathSegments) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException; 218 219 /** 220 * Retrieves the value of the data at the given path, or the default value 221 * The returned value is one of those ones, in the order: 222 * <ol> 223 * <li>The value of the data if exists and is not empty</li> 224 * <li>The default value from the model if useDefaultFromModel is <code>true</code> and there is a default value defined by the model</li> 225 * <li>The given default value</li> 226 * </ol> 227 * @param <T> type of the value to retrieve 228 * @param dataPath path of the data 229 * @param useDefaultFromModel true to use the default value from the model, false to use the given default value 230 * @param defaultValue default value used if value is null and useDefaultFromModel is false, or if there is no default value on model 231 * @return the value of the data at the given path 232 * @throws IllegalArgumentException if the given data path is null or empty 233 * @throws UndefinedItemPathException if the given data path is not defined by the model 234 * @throws BadItemTypeException if the type defined by the model doesn't match the type of the stored value 235 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 236 */ 237 public <T extends Object> T getValue(String dataPath, boolean useDefaultFromModel, T defaultValue) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException; 238 239 /** 240 * Retrieves the local value of the data at the given path 241 * @param <T> type of the value to retrieve 242 * @param dataPath path of the externalizable data 243 * @return the local value of the data or <code>null</code> if not exists or is empty. The object returned may be of a generic class defined by the storage (if the model is unknown). For example, an url may be returned as a String. 244 * @throws IllegalArgumentException if the given data path is null or empty 245 * @throws UndefinedItemPathException if the given data path is not defined by the model 246 * @throws BadItemTypeException if the type defined by the model doesn't match the type of the stored value 247 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 248 */ 249 public <T extends Object> T getLocalValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException; 250 251 /** 252 * Retrieves the external value of the data at the given path 253 * @param <T> type of the value to retrieve 254 * @param dataPath path of the externalizable data 255 * @return the external value of the data or <code>null</code> if not exists or is empty. The object returned may be of a generic class defined by the storage (if the model is unknown). For example, an url may be returned as a String. 256 * @throws IllegalArgumentException if the given data path is null or empty 257 * @throws UndefinedItemPathException if the given data path is not defined by the model 258 * @throws BadItemTypeException if the type defined by the model doesn't match the type of the stored value 259 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 260 */ 261 public <T extends Object> T getExternalValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException; 262 263 /** 264 * Retrieves the status of the externalizable data at the given path 265 * @param dataPath path of the externalizable data 266 * @return the status of the externalizable data at the given path 267 * @throws IllegalArgumentException if the given data path is null or empty 268 * @throws UndefinedItemPathException if the given data path is not defined by the model 269 * @throws BadDataPathCardinalityException if the definition of a part of the data path is multiple. Only the last part can be multiple 270 */ 271 public ExternalizableDataStatus getStatus(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadDataPathCardinalityException; 272 273 /** 274 * Retrieve the comments of the data with the given name 275 * @param dataName name of the data 276 * @return the comments of the data 277 * @throws IllegalArgumentException if the given data name is null or empty 278 * @throws UndefinedItemPathException if the given data name is not defined by the model 279 */ 280 public List<DataComment> getComments(String dataName) throws IllegalArgumentException, UndefinedItemPathException; 281 282 /** 283 * Checks if the definition of the element at the given path is multiple 284 * @param path path of the element. No matter if it is a definition or data path (with repeater entry positions) 285 * @return <code>true</code> if the element is multiple, <code>false</code> otherwise 286 * @throws IllegalArgumentException if the given path is null or empty 287 * @throws UndefinedItemPathException if the given path is not defined by the model 288 */ 289 public default boolean isMultiple(String path) throws IllegalArgumentException, UndefinedItemPathException 290 { 291 ModelItem item = getDefinition(path); 292 if (item instanceof ElementDefinition) 293 { 294 return ((ElementDefinition) item).isMultiple(); 295 } 296 else if (item instanceof RepeaterDefinition) 297 { 298 // If the given path represents a repeater , but with no specified entry, consider the data as multiple 299 return !DataHolderHelper.isRepeaterEntryPath(path); 300 } 301 else 302 { 303 // Composites are not multiples 304 return false; 305 } 306 } 307 308 /** 309 * Retrieves the type of the data at the given path 310 * @param path path of the data. No matter if it is a definition or data path (with repeater entry positions) 311 * @return the type of the data 312 * @throws IllegalArgumentException if the given data path is null or empty 313 * @throws UndefinedItemPathException if the given data path is not defined by the model 314 */ 315 public default RepositoryModelItemType getType(String path) throws IllegalArgumentException, UndefinedItemPathException 316 { 317 return (RepositoryModelItemType) getDefinition(path).getType(); 318 } 319 320 /** 321 * Retrieves the data holder's model 322 * @return the data holder's model 323 */ 324 public Collection<? extends ModelItemContainer> getModel(); 325 326 /** 327 * Retrieves the definition of the data at the given path 328 * @param path path of the data. No matter if it is a definition or data path (with repeater entry positions) 329 * @return the definition of the data 330 * @throws IllegalArgumentException if the given path is null or empty 331 * @throws UndefinedItemPathException if the given path is not defined by the model 332 */ 333 public ModelItem getDefinition(String path) throws IllegalArgumentException, UndefinedItemPathException; 334 335 /** 336 * Checks if there is a definition at the given path 337 * @param path path of the data. No matter if it is a definition or data path (with repeater entry positions) 338 * @return <code>true</code> if there is definition at the given path, <code>false</code> otherwise 339 * @throws IllegalArgumentException if the given path is null or empty 340 */ 341 public boolean hasDefinition(String path) throws IllegalArgumentException; 342 343 /** 344 * Generates SAX events for the data in the model of the current {@link DataHolder} 345 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 346 * @throws SAXException if an error occurs during the SAX events generation 347 * @throws IOException if an error occurs while reading a value using the I/O API 348 * @throws BadItemTypeException if the saxed value's type does not matches the stored data 349 */ 350 public default void dataToSAX(ContentHandler contentHandler) throws SAXException, IOException, BadItemTypeException 351 { 352 dataToSAX(contentHandler, DataContext.newInstance()); 353 } 354 355 /** 356 * Generates SAX events for the data in the model of the current {@link DataHolder} 357 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 358 * @param context The context of the data to SAX 359 * @throws SAXException if an error occurs during the SAX events generation 360 * @throws IOException if an error occurs while reading a value using the I/O API 361 * @throws BadItemTypeException if the saxed value's type does not matches the stored data 362 */ 363 public default void dataToSAX(ContentHandler contentHandler, DataContext context) throws SAXException, IOException, BadItemTypeException 364 { 365 ViewItemAccessor viewItemAccessor = ViewHelper.createViewItemAccessor(getModel()); 366 dataToSAX(contentHandler, viewItemAccessor, context); 367 } 368 369 /** 370 * Generates SAX events for the data in the given view in the current {@link DataHolder} 371 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 372 * @param viewItemAccessor the {@link ViewItemAccessor} referencing the items for which generate SAX events 373 * @throws SAXException if an error occurs during the SAX events generation 374 * @throws IOException if an error occurs while reading a value using the I/O API 375 * @throws BadItemTypeException if the saxed value's type does not matches the stored data 376 */ 377 public default void dataToSAX(ContentHandler contentHandler, ViewItemAccessor viewItemAccessor) throws SAXException, IOException, BadItemTypeException 378 { 379 dataToSAX(contentHandler, viewItemAccessor, DataContext.newInstance()); 380 } 381 382 /** 383 * Generates SAX events for the data in the given view in the current {@link DataHolder} 384 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 385 * @param viewItemAccessor the {@link ViewItemAccessor} referencing the items for which generate SAX events 386 * @param context The context of the data to SAX 387 * @throws SAXException if an error occurs during the SAX events generation 388 * @throws IOException if an error occurs while reading a value using the I/O API 389 * @throws BadItemTypeException if the saxed value's type does not matches the stored data 390 */ 391 public default void dataToSAX(ContentHandler contentHandler, ViewItemAccessor viewItemAccessor, DataContext context) throws SAXException, IOException, BadItemTypeException 392 { 393 ModelAwareDataHolder root = getRootDataHolder(); 394 if (root instanceof AmetysObject) 395 { 396 context.withObjectId(((AmetysObject) root).getId()); 397 } 398 399 DataHolderHelper.dataToSAX(this, contentHandler, viewItemAccessor, context, false); 400 } 401 402 /** 403 * Generates SAX events for the data in the given view in edition mode in the current {@link DataHolder} 404 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 405 * @param viewItemAccessor the {@link ViewItemAccessor} referencing the items for which generate SAX events 406 * @param context The context of the data to SAX 407 * @throws SAXException if an error occurs during the SAX events generation 408 * @throws IOException if an error occurs while reading a value using the I/O API 409 * @throws BadItemTypeException if the saxed value's type does not matches the stored data 410 */ 411 public default void dataToSAXForEdition(ContentHandler contentHandler, ViewItemAccessor viewItemAccessor, DataContext context) throws SAXException, IOException, BadItemTypeException 412 { 413 ModelAwareDataHolder root = getRootDataHolder(); 414 if (root instanceof AmetysObject) 415 { 416 context.withObjectId(((AmetysObject) root).getId()); 417 } 418 419 DataHolderHelper.dataToSAX(this, contentHandler, viewItemAccessor, context, true); 420 } 421 422 /** 423 * Generates SAX events for the comments of the data in the given view in the current {@link DataHolder} 424 * @param contentHandler the {@link ContentHandler} that will receive the SAX events 425 * @param viewItemAccessor the {@link ViewItemAccessor} referencing the items for which generate SAX events 426 * @throws SAXException if an error occurs during the SAX events generation 427 */ 428 public default void commentsToSAX(ContentHandler contentHandler, ViewItemAccessor viewItemAccessor) throws SAXException 429 { 430 DataHolderHelper.commentsToSAX(this, contentHandler, viewItemAccessor, StringUtils.EMPTY); 431 } 432 433 /** 434 * Retrieves all data of this DataHolder as a typed-values Map. 435 * @return a Map containing all data. 436 */ 437 public default Map<String, Object> dataToMap() 438 { 439 return dataToMap(DataContext.newInstance()); 440 } 441 442 /** 443 * Retrieves all data of this DataHolder as a typed-values Map. 444 * @param context The context of the data 445 * @return a Map containing all data. 446 */ 447 public default Map<String, Object> dataToMap(DataContext context) 448 { 449 ViewItemAccessor viewItemAccessor = ViewHelper.createViewItemAccessor(getModel()); 450 return dataToMap(viewItemAccessor, context); 451 } 452 453 /** 454 * Retrieves data of this DataHolder as a typed-values Map. 455 * @param viewItemAccessor the {@link ViewItemAccessor} referencing the items to include in the resulting Map 456 * @return a Map containing all data. 457 */ 458 public default Map<String, Object> dataToMap(ViewItemAccessor viewItemAccessor) 459 { 460 return dataToMap(viewItemAccessor, DataContext.newInstance()); 461 } 462 463 /** 464 * Retrieves data of this DataHolder as a typed-values Map. 465 * @param viewItemAccessor the {@link ViewItemAccessor} referencing the items to include in the resulting Map 466 * @param context The context of the data 467 * @return a Map containing all data. 468 */ 469 public default Map<String, Object> dataToMap(ViewItemAccessor viewItemAccessor, DataContext context) 470 { 471 ModelAwareDataHolder root = getRootDataHolder(); 472 if (root instanceof AmetysObject) 473 { 474 context.withRootObjectId(((AmetysObject) root).getId()); 475 } 476 477 return DataHolderHelper.dataToMap(this, viewItemAccessor, context); 478 } 479 480 @Override 481 public Optional<? extends ModelAwareDataHolder> getParentDataHolder(); 482 483 @Override 484 public ModelAwareDataHolder getRootDataHolder(); 485}