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}