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}