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.IndexableDataHolder; 027import org.ametys.cms.data.holder.group.IndexableComposite; 028import org.ametys.cms.data.holder.group.IndexableRepeater; 029import org.ametys.cms.data.holder.group.IndexableRepeaterEntry; 030import org.ametys.cms.data.holder.group.impl.DefaultIndexableComposite; 031import org.ametys.cms.data.holder.group.impl.DefaultIndexableRepeater; 032import org.ametys.cms.model.properties.Property; 033import org.ametys.plugins.repository.data.external.ExternalizableDataProvider.ExternalizableDataStatus; 034import org.ametys.plugins.repository.data.holder.DataHolder; 035import org.ametys.plugins.repository.data.holder.impl.DefaultModelAwareDataHolder; 036import org.ametys.plugins.repository.data.repositorydata.RepositoryData; 037import org.ametys.plugins.repository.model.CompositeDefinition; 038import org.ametys.plugins.repository.model.RepeaterDefinition; 039import org.ametys.runtime.model.ElementDefinition; 040import org.ametys.runtime.model.ModelItem; 041import org.ametys.runtime.model.ModelItemContainer; 042import org.ametys.runtime.model.exception.BadDataPathCardinalityException; 043import org.ametys.runtime.model.exception.BadItemTypeException; 044import org.ametys.runtime.model.exception.UndefinedItemPathException; 045import org.ametys.runtime.model.type.DataContext; 046 047/** 048 * Default implementation for indexable data holder. 049 * Extends the repository-level {@link DefaultModelAwareDataHolder} and adds indexable-specific behaviour such as {@link Property} and system property support. 050 */ 051public class DefaultIndexableDataHolder extends DefaultModelAwareDataHolder implements IndexableDataHolder 052{ 053 /** 054 * Creates a default indexable data holder 055 * @param repositoryData the repository data to use 056 * @param itemContainer the model container 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. 057 */ 058 public DefaultIndexableDataHolder(RepositoryData repositoryData, ModelItemContainer itemContainer) 059 { 060 this(repositoryData, itemContainer, Optional.empty(), Optional.empty()); 061 } 062 063 /** 064 * Creates a default indexable data holder 065 * @param repositoryData the repository data to use 066 * @param itemContainer the model container 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. 067 * @param parent the optional parent of the created {@link DataHolder}, empty if the created {@link DataHolder} is the root {@link DataHolder} 068 * @param root the root {@link IndexableDataHolder} 069 */ 070 public DefaultIndexableDataHolder(RepositoryData repositoryData, ModelItemContainer itemContainer, Optional<? extends IndexableDataHolder> parent, Optional<? extends IndexableDataHolder> root) 071 { 072 this(repositoryData, List.of(itemContainer), parent, root); 073 } 074 075 /** 076 * Creates a default indexable data holder 077 * @param repositoryData the repository data to use 078 * @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. 079 * @param parent the optional parent of the created {@link DataHolder}, empty if the created {@link DataHolder} is the root {@link DataHolder} 080 * @param root the root {@link IndexableDataHolder} 081 */ 082 public DefaultIndexableDataHolder(RepositoryData repositoryData, Collection<? extends ModelItemContainer> itemContainers, Optional<? extends IndexableDataHolder> parent, Optional<? extends IndexableDataHolder> root) 083 { 084 super(repositoryData, itemContainers, parent, root); 085 } 086 087 // ----------------------------------------------------------------------- 088 // Covariant return type overrides for getComposite / getRepeater 089 // ----------------------------------------------------------------------- 090 091 @Override 092 public IndexableComposite getComposite(String compositePath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 093 { 094 return (IndexableComposite) super.getComposite(compositePath); 095 } 096 097 @Override 098 public IndexableComposite getLocalComposite(String compositePath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 099 { 100 return (IndexableComposite) super.getLocalComposite(compositePath); 101 } 102 103 @Override 104 public IndexableComposite getExternalComposite(String compositePath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 105 { 106 return (IndexableComposite) super.getExternalComposite(compositePath); 107 } 108 109 @Override 110 public IndexableRepeater getRepeater(String repeaterPath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 111 { 112 return (IndexableRepeater) super.getRepeater(repeaterPath); 113 } 114 115 @Override 116 public IndexableRepeater getLocalRepeater(String repeaterPath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 117 { 118 return (IndexableRepeater) super.getLocalRepeater(repeaterPath); 119 } 120 121 @Override 122 public IndexableRepeater getExternalRepeater(String repeaterPath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException 123 { 124 return (IndexableRepeater) super.getExternalRepeater(repeaterPath); 125 } 126 127 // ----------------------------------------------------------------------- 128 // hasValue with Property support 129 // ----------------------------------------------------------------------- 130 131 @Override 132 protected boolean hasValueForName(String dataName, Optional<ExternalizableDataStatus> status) 133 { 134 return getDefinition(dataName) instanceof Property property 135 ? IndexableDataHolderHelper.hasPropertyValue(this, property) 136 : super.hasValueForName(dataName, status); 137 } 138 139 @Override 140 protected boolean hasValueOrEmptyForName(String dataName, Optional<ExternalizableDataStatus> status) 141 { 142 return getDefinition(dataName) instanceof Property property 143 ? IndexableDataHolderHelper.hasPropertyValue(this, property) 144 : super.hasValueOrEmptyForName(dataName, status); 145 } 146 147 // ----------------------------------------------------------------------- 148 // getValue with Property support 149 // ----------------------------------------------------------------------- 150 151 @Override 152 protected <T> T getElementValue(ElementDefinition definition, String dataName) 153 { 154 return definition instanceof Property property 155 ? IndexableDataHolderHelper.getPropertyValue(this, property) 156 : super.getElementValue(definition, dataName); 157 } 158 159 // ----------------------------------------------------------------------- 160 // Factory methods – create Indexable group instances 161 // ----------------------------------------------------------------------- 162 163 @Override 164 protected IndexableComposite createCompositeInstance(RepositoryData compositeRepositoryData, CompositeDefinition compositeDefinition) 165 { 166 return new DefaultIndexableComposite(compositeRepositoryData, compositeDefinition, this, getRootDataHolder()); 167 } 168 169 @Override 170 protected IndexableRepeater createRepeaterInstance(RepositoryData repeaterRepositoryData, RepeaterDefinition repeaterDefinition) 171 { 172 return new DefaultIndexableRepeater(repeaterRepositoryData, repeaterDefinition, this, getRootDataHolder()); 173 } 174 175 // ----------------------------------------------------------------------- 176 // Managed class helpers 177 // ----------------------------------------------------------------------- 178 179 @Override 180 protected Class getCompositeClass() 181 { 182 return IndexableComposite.class; 183 } 184 185 @Override 186 protected Class getRepeaterClass() 187 { 188 return IndexableRepeater.class; 189 } 190 191 @Override 192 protected Class getRepeaterEntryClass() 193 { 194 return IndexableRepeaterEntry.class; 195 } 196 197 // ----------------------------------------------------------------------- 198 // Definition lookup with SystemProperty support 199 // ----------------------------------------------------------------------- 200 201 @Override 202 public ModelItem getDefinition(String path) throws IllegalArgumentException, UndefinedItemPathException 203 { 204 try 205 { 206 return super.getDefinition(path); 207 } 208 catch (UndefinedItemPathException e) 209 { 210 return IndexableDataHolderHelper.getSystemProperty(this, path) 211 .orElseThrow(() -> e); 212 } 213 } 214 215 // ----------------------------------------------------------------------- 216 // dataToSAX / dataToJSON with Property support 217 // ----------------------------------------------------------------------- 218 219 @SuppressWarnings("unchecked") 220 @Override 221 protected void dataToSAX(ContentHandler contentHandler, ModelItem modelItem, String dataPath, DataContext context) throws SAXException, BadItemTypeException 222 { 223 if (modelItem instanceof Property property) 224 { 225 IndexableAmetysObject ametysObject = IndexableDataHolderHelper.getPropertyAmetysObject(this, dataPath); 226 property.valueToSAX(contentHandler, modelItem.getName(), ametysObject, context); 227 } 228 else 229 { 230 super.dataToSAX(contentHandler, modelItem, dataPath, context); 231 } 232 } 233 234 @SuppressWarnings("unchecked") 235 @Override 236 protected Object dataToJSON(ModelItem modelItem, String dataPath, DataContext context) throws BadItemTypeException 237 { 238 if (modelItem instanceof Property property) 239 { 240 IndexableAmetysObject ametysObject = IndexableDataHolderHelper.getPropertyAmetysObject(this, dataPath); 241 return property.valueToJSON(ametysObject, context); 242 } 243 else 244 { 245 return super.dataToJSON(modelItem, dataPath, context); 246 } 247 } 248 249 // ----------------------------------------------------------------------- 250 // Parent / root data holder covariant overrides 251 // ----------------------------------------------------------------------- 252 253 @Override 254 public Optional<? extends IndexableDataHolder> getParentDataHolder() 255 { 256 return _parent.map(IndexableDataHolder.class::cast); 257 } 258 259 @Override 260 public IndexableDataHolder getRootDataHolder() 261 { 262 return (IndexableDataHolder) _root; 263 } 264 265 // ----------------------------------------------------------------------- 266 // checkDefinition with Property support 267 // ----------------------------------------------------------------------- 268 269 @Override 270 protected void checkDefinition(String dataPath, boolean checkStatusAvailable, String errorMsg) 271 { 272 super.checkDefinition(dataPath, checkStatusAvailable, errorMsg); 273 274 // Externalizable data status is not available for properties 275 if (checkStatusAvailable && getDefinition(dataPath) instanceof Property) 276 { 277 throw new UndefinedItemPathException(errorMsg + " A property can't have an externalizable status."); 278 } 279 } 280}