001/* 002 * Copyright 2026 Anyware Services 003 * 004 * Licensed under the Apache License, Version 2.0 (the "License"); 005 * you may not use this file except in compliance with the License. 006 * You may obtain a copy of the License at 007 * 008 * http://www.apache.org/licenses/LICENSE-2.0 009 * 010 * Unless required by applicable law or agreed to in writing, software 011 * distributed under the License is distributed on an "AS IS" BASIS, 012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 013 * See the License for the specific language governing permissions and 014 * limitations under the License. 015 */ 016package org.ametys.cms.data.holder.impl; 017 018import java.util.Collection; 019import java.util.List; 020import java.util.Optional; 021 022import org.xml.sax.ContentHandler; 023import org.xml.sax.SAXException; 024 025import org.ametys.cms.data.ametysobject.IndexableAmetysObject; 026import org.ametys.cms.data.holder.ModifiableIndexableDataHolder; 027import org.ametys.cms.data.holder.group.ModifiableIndexableComposite; 028import org.ametys.cms.data.holder.group.ModifiableIndexableRepeater; 029import org.ametys.cms.data.holder.group.ModifiableIndexableRepeaterEntry; 030import org.ametys.cms.data.holder.group.impl.DefaultModifiableIndexableComposite; 031import org.ametys.cms.data.holder.group.impl.DefaultModifiableIndexableRepeater; 032import org.ametys.cms.model.properties.Property; 033import org.ametys.plugins.repository.data.external.ExternalizableDataProvider.ExternalizableDataStatus; 034import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelAwareDataHolder; 035import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData; 036import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 037import org.ametys.plugins.repository.lock.LockableAmetysObject; 038import org.ametys.plugins.repository.model.CompositeDefinition; 039import org.ametys.plugins.repository.model.RepeaterDefinition; 040import org.ametys.runtime.model.ElementDefinition; 041import org.ametys.runtime.model.ModelItem; 042import org.ametys.runtime.model.ModelItemContainer; 043import org.ametys.runtime.model.exception.BadDataPathCardinalityException; 044import org.ametys.runtime.model.exception.BadItemTypeException; 045import org.ametys.runtime.model.exception.UndefinedItemPathException; 046import org.ametys.runtime.model.type.DataContext; 047 048/** 049 * Default implementation for modifiable and indexable data holder with model. 050 * Extends the repository-level {@link DefaultModifiableModelAwareDataHolder} and adds 051 * CMS-specific behaviour such as {@link Property} and system property support. 052 */ 053public class DefaultModifiableIndexableDataHolder extends DefaultModifiableModelAwareDataHolder implements ModifiableIndexableDataHolder 054{ 055 /** 056 * Creates a default modifiable indexable data holder 057 * @param repositoryData the repository data to use 058 * @param itemContainer the model container to use to get information about definitions 059 */ 060 public DefaultModifiableIndexableDataHolder(ModifiableRepositoryData repositoryData, ModelItemContainer itemContainer) 061 { 062 this(repositoryData, itemContainer, Optional.empty(), Optional.empty(), Optional.empty()); 063 } 064 065 /** 066 * Creates a default modifiable indexable data holder 067 * @param repositoryData the repository data to use 068 * @param itemContainer the model container to use to get information about definitions 069 * @param lockableAmetysObject the ametys object that can be locked on data modification 070 */ 071 public DefaultModifiableIndexableDataHolder(ModifiableRepositoryData repositoryData, ModelItemContainer itemContainer, Optional<LockableAmetysObject> lockableAmetysObject) 072 { 073 this(repositoryData, itemContainer, lockableAmetysObject, Optional.empty(), Optional.empty()); 074 } 075 076 /** 077 * Creates a default modifiable indexable data holder 078 * @param repositoryData the repository data to use 079 * @param itemContainer the model container to use to get information about definitions 080 * @param lockableAmetysObject the ametys object that can be locked on data modification 081 * @param parent the optional parent {@link ModifiableIndexableDataHolder} 082 * @param root the root {@link ModifiableIndexableDataHolder} 083 */ 084 public DefaultModifiableIndexableDataHolder(ModifiableRepositoryData repositoryData, ModelItemContainer itemContainer, Optional<LockableAmetysObject> lockableAmetysObject, Optional<? extends ModifiableIndexableDataHolder> parent, Optional<? extends ModifiableIndexableDataHolder> root) 085 { 086 this(repositoryData, List.of(itemContainer), lockableAmetysObject, parent, root); 087 } 088 089 /** 090 * Creates a default modifiable indexable data holder 091 * @param repositoryData the repository data to use 092 * @param itemContainers the model containers to use to get information about definitions 093 * @param lockableAmetysObject the ametys object that can be locked on data modification 094 * @param parent the optional parent {@link ModifiableIndexableDataHolder} 095 * @param root the root {@link ModifiableIndexableDataHolder} 096 */ 097 public DefaultModifiableIndexableDataHolder(ModifiableRepositoryData repositoryData, Collection<? extends ModelItemContainer> itemContainers, Optional<LockableAmetysObject> lockableAmetysObject, Optional<? extends ModifiableIndexableDataHolder> parent, Optional<? extends ModifiableIndexableDataHolder> root) 098 { 099 super(repositoryData, itemContainers, lockableAmetysObject, parent, root); 100 } 101 102 // ----------------------------------------------------------------------- 103 // Covariant return type overrides 104 // ----------------------------------------------------------------------- 105 106 @Override 107 public ModifiableIndexableComposite getComposite(String compositePath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 108 { 109 return (ModifiableIndexableComposite) super.getComposite(compositePath); 110 } 111 112 @Override 113 public ModifiableIndexableComposite getLocalComposite(String compositePath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 114 { 115 return (ModifiableIndexableComposite) super.getLocalComposite(compositePath); 116 } 117 118 @Override 119 public ModifiableIndexableComposite getExternalComposite(String compositePath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 120 { 121 return (ModifiableIndexableComposite) super.getExternalComposite(compositePath); 122 } 123 124 @Override 125 public ModifiableIndexableRepeater getRepeater(String repeaterPath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 126 { 127 return (ModifiableIndexableRepeater) super.getRepeater(repeaterPath); 128 } 129 130 @Override 131 public ModifiableIndexableRepeater getLocalRepeater(String repeaterPath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 132 { 133 return (ModifiableIndexableRepeater) super.getLocalRepeater(repeaterPath); 134 } 135 136 @Override 137 public ModifiableIndexableRepeater getExternalRepeater(String repeaterPath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 138 { 139 return (ModifiableIndexableRepeater) super.getExternalRepeater(repeaterPath); 140 } 141 142 @Override 143 public ModifiableIndexableComposite getComposite(String compositePath, boolean createNew) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 144 { 145 return (ModifiableIndexableComposite) super.getComposite(compositePath, createNew); 146 } 147 148 @Override 149 public ModifiableIndexableComposite getLocalComposite(String compositePath, boolean createNew) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 150 { 151 return (ModifiableIndexableComposite) super.getLocalComposite(compositePath, createNew); 152 } 153 154 @Override 155 public ModifiableIndexableComposite getExternalComposite(String compositePath, boolean createNew) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 156 { 157 return (ModifiableIndexableComposite) super.getExternalComposite(compositePath, createNew); 158 } 159 160 @Override 161 protected ModifiableIndexableComposite createCompositeInstance(RepositoryData compositeRepositoryData, CompositeDefinition compositeDefinition) 162 { 163 return new DefaultModifiableIndexableComposite((ModifiableRepositoryData) compositeRepositoryData, compositeDefinition, _lockableAmetysObject, this, getRootDataHolder()); 164 } 165 166 @Override 167 public ModifiableIndexableRepeater getRepeater(String repeaterPath, boolean createNew) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 168 { 169 return (ModifiableIndexableRepeater) super.getRepeater(repeaterPath, createNew); 170 } 171 172 @Override 173 public ModifiableIndexableRepeater getLocalRepeater(String repeaterPath, boolean createNew) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 174 { 175 return (ModifiableIndexableRepeater) super.getLocalRepeater(repeaterPath, createNew); 176 } 177 178 @Override 179 public ModifiableIndexableRepeater getExternalRepeater(String repeaterPath, boolean createNew) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 180 { 181 return (ModifiableIndexableRepeater) super.getExternalRepeater(repeaterPath, createNew); 182 } 183 184 @Override 185 protected ModifiableIndexableRepeater createRepeaterInstance(RepositoryData repeaterRepositoryData, RepeaterDefinition repeaterDefinition) 186 { 187 return new DefaultModifiableIndexableRepeater((ModifiableRepositoryData) repeaterRepositoryData, repeaterDefinition, _lockableAmetysObject, this, getRootDataHolder()); 188 } 189 190 // ----------------------------------------------------------------------- 191 // hasValue with Property support 192 // ----------------------------------------------------------------------- 193 194 @Override 195 protected boolean hasValueForName(String dataName, Optional<ExternalizableDataStatus> status) 196 { 197 return getDefinition(dataName) instanceof Property property 198 ? IndexableDataHolderHelper.hasPropertyValue(this, property) 199 : super.hasValueForName(dataName, status); 200 } 201 202 @Override 203 protected boolean hasValueOrEmptyForName(String dataName, Optional<ExternalizableDataStatus> status) 204 { 205 return getDefinition(dataName) instanceof Property property 206 ? IndexableDataHolderHelper.hasPropertyValue(this, property) 207 : super.hasValueOrEmptyForName(dataName, status); 208 } 209 210 // ----------------------------------------------------------------------- 211 // getValue with Property support 212 // ----------------------------------------------------------------------- 213 214 @Override 215 protected <T> T getElementValue(ElementDefinition definition, String dataName) 216 { 217 return definition instanceof Property property 218 ? IndexableDataHolderHelper.getPropertyValue(this, property) 219 : super.getElementValue(definition, dataName); 220 } 221 222 // ----------------------------------------------------------------------- 223 // Managed class helpers 224 // ----------------------------------------------------------------------- 225 226 @Override 227 protected Class getCompositeClass() 228 { 229 return ModifiableIndexableComposite.class; 230 } 231 232 @Override 233 protected Class getRepeaterClass() 234 { 235 return ModifiableIndexableRepeater.class; 236 } 237 238 @Override 239 protected Class getRepeaterEntryClass() 240 { 241 return ModifiableIndexableRepeaterEntry.class; 242 } 243 244 // ----------------------------------------------------------------------- 245 // Definition lookup with SystemProperty support 246 // ----------------------------------------------------------------------- 247 248 @Override 249 public ModelItem getDefinition(String path) throws IllegalArgumentException, UndefinedItemPathException 250 { 251 try 252 { 253 return super.getDefinition(path); 254 } 255 catch (UndefinedItemPathException e) 256 { 257 return IndexableDataHolderHelper.getSystemProperty(this, path) 258 .orElseThrow(() -> e); 259 } 260 } 261 262 // ----------------------------------------------------------------------- 263 // dataToSAX / dataToJSON with Property support 264 // ----------------------------------------------------------------------- 265 266 @SuppressWarnings("unchecked") 267 @Override 268 protected void dataToSAX(ContentHandler contentHandler, ModelItem modelItem, String dataPath, DataContext context) throws SAXException, BadItemTypeException 269 { 270 if (modelItem instanceof Property property) 271 { 272 IndexableAmetysObject ametysObject = IndexableDataHolderHelper.getPropertyAmetysObject(this, dataPath); 273 property.valueToSAX(contentHandler, modelItem.getName(), ametysObject, context); 274 } 275 else 276 { 277 super.dataToSAX(contentHandler, modelItem, dataPath, context); 278 } 279 } 280 281 @SuppressWarnings("unchecked") 282 @Override 283 protected Object dataToJSON(ModelItem modelItem, String dataPath, DataContext context) throws BadItemTypeException 284 { 285 if (modelItem instanceof Property property) 286 { 287 IndexableAmetysObject ametysObject = IndexableDataHolderHelper.getPropertyAmetysObject(this, dataPath); 288 return property.valueToJSON(ametysObject, context); 289 } 290 else 291 { 292 return super.dataToJSON(modelItem, dataPath, context); 293 } 294 } 295 296 // ----------------------------------------------------------------------- 297 // Parent / root data holder covariant overrides 298 // ----------------------------------------------------------------------- 299 300 @Override 301 public Optional<? extends ModifiableIndexableDataHolder> getParentDataHolder() 302 { 303 return _modifiableParent.map(ModifiableIndexableDataHolder.class::cast); 304 } 305 306 @Override 307 public ModifiableIndexableDataHolder getRootDataHolder() 308 { 309 return (ModifiableIndexableDataHolder) _modifiableRoot; 310 } 311 312 // ----------------------------------------------------------------------- 313 // checkDefinition with Property support 314 // ----------------------------------------------------------------------- 315 316 @Override 317 protected void checkDefinition(String dataPath, boolean checkStatusAvailable, String errorMsg) 318 { 319 super.checkDefinition(dataPath, checkStatusAvailable, errorMsg); 320 321 // Externalizable data status is not available for properties 322 if (checkStatusAvailable && getDefinition(dataPath) instanceof Property) 323 { 324 throw new UndefinedItemPathException(errorMsg + " A property can't have an externalizable status."); 325 } 326 } 327}