001/*
002 *  Copyright 2019 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;
017
018import java.util.Collection;
019import java.util.List;
020import java.util.Map;
021import java.util.Objects;
022import java.util.Optional;
023
024import javax.jcr.RepositoryException;
025import javax.jcr.Session;
026
027import org.apache.commons.collections4.CollectionUtils;
028import org.xml.sax.ContentHandler;
029import org.xml.sax.SAXException;
030
031import org.ametys.cms.data.type.AbstractContentElementType;
032import org.ametys.cms.repository.ModifiableContent;
033import org.ametys.plugins.repository.AmetysObjectResolver;
034import org.ametys.plugins.repository.AmetysRepositoryException;
035import org.ametys.plugins.repository.UnknownAmetysObjectException;
036import org.ametys.plugins.repository.data.DataComment;
037import org.ametys.plugins.repository.data.UnknownDataException;
038import org.ametys.plugins.repository.data.external.ExternalizableDataProvider.ExternalizableDataStatus;
039import org.ametys.plugins.repository.data.holder.ModifiableModelAwareDataHolder;
040import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareComposite;
041import org.ametys.plugins.repository.data.holder.group.impl.ModifiableModelAwareRepeater;
042import org.ametys.plugins.repository.data.holder.values.SynchronizationContext;
043import org.ametys.plugins.repository.data.holder.values.SynchronizationResult;
044import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
045import org.ametys.runtime.model.ModelItem;
046import org.ametys.runtime.model.ModelItemContainer;
047import org.ametys.runtime.model.ViewItemContainer;
048import org.ametys.runtime.model.exception.BadDataPathCardinalityException;
049import org.ametys.runtime.model.exception.BadItemTypeException;
050import org.ametys.runtime.model.exception.UndefinedItemPathException;
051import org.ametys.runtime.model.type.DataContext;
052
053import com.fasterxml.jackson.annotation.JsonIdentityInfo;
054import com.fasterxml.jackson.annotation.JsonIdentityReference;
055import com.fasterxml.jackson.annotation.ObjectIdGenerators;
056
057/**
058 * Content wrapper used by attributes of type content
059 * @see AbstractContentElementType
060 */
061@JsonIdentityInfo(generator = ObjectIdGenerators.PropertyGenerator.class, property = "contentId")
062@JsonIdentityReference(alwaysAsId = true)
063public class ContentValue implements ModifiableModelAwareDataHolder
064{
065    private AmetysObjectResolver _resolver;
066    private String _contentId;
067    private ModifiableContent _content;
068    private Session _session;
069
070    /**
071     * Constructor of the content wrapper
072     * @param content the existing content
073     */
074    public ContentValue(ModifiableContent content)
075    {
076        _content = content;
077        _contentId = content.getId();
078    }
079    
080    /**
081     * Constructor of the content wrapper
082     * @param resolver resolver used to get the content from its identifier
083     * @param contentId content identifier
084     */
085    public ContentValue(AmetysObjectResolver resolver, String contentId)
086    {
087        this(resolver, contentId, null);
088    }
089
090    /**
091     * Constructor of the content wrapper
092     * @param resolver resolver used to get the content from its identifier
093     * @param contentId content identifier
094     * @param session the current session. If <code>null</code>, a new session will be used to retrieve the content 
095     */
096    public ContentValue(AmetysObjectResolver resolver, String contentId, Session session)
097    {
098        _resolver = resolver;
099        _contentId = contentId;
100        _session = session;
101    }
102    
103    /**
104     * Retrieves the content's identifier
105     * @return the content's identifier
106     */
107    public String getContentId()
108    {
109        return _contentId;
110    }
111    
112    /**
113     * Retrieves the content
114     * @return the content
115     * @throws AmetysRepositoryException if an error occurs.
116     * @throws UnknownAmetysObjectException if no content exists for the identifier
117     */
118    public ModifiableContent getContent() throws AmetysRepositoryException, UnknownAmetysObjectException
119    {
120        if (_content == null)
121        {
122            if (_session != null)
123            {
124                try
125                {
126                    _content = _resolver.resolveById(_contentId, _session);
127                }
128                catch (RepositoryException e)
129                {
130                    throw new AmetysRepositoryException("Unable to retrieve the content with the id '" + _contentId + "'.", e);
131                }
132            }
133            else
134            {
135                _content = _resolver.resolveById(_contentId);
136            }
137        }
138        
139        return _content; 
140    }
141    
142    /**
143     * Retrieves an {@link Optional} describing the content, or an empty {@link Optional} if the content does not exist 
144     * @return an {@link Optional} describing the content
145     */
146    public Optional<ModifiableContent> getContentIfExists()
147    {
148        try
149        {
150            return Optional.ofNullable(getContent());
151        }
152        catch (AmetysRepositoryException e)
153        {
154            return Optional.empty();
155        }
156    }
157    
158    @Override
159    public int hashCode()
160    {
161        return Objects.hash(_contentId);
162    }
163
164    @Override
165    public boolean equals(Object obj)
166    {
167        if (!(obj instanceof ContentValue))
168        {
169            return false;
170        }
171        
172        return Objects.equals(_contentId, ((ContentValue) obj)._contentId);
173    }
174
175    public boolean hasValue(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException
176    {
177        return getContentIfExists()
178                .map(c -> c.hasValue(dataPath))
179                .orElse(false);
180    }
181    
182    public boolean hasLocalValue(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException
183    {
184        return getContentIfExists()
185                .map(c -> c.hasLocalValue(dataPath))
186                .orElse(false);
187    }
188    
189    public boolean hasExternalValue(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException
190    {
191        return getContentIfExists()
192                .map(c -> c.hasExternalValue(dataPath))
193                .orElse(false);
194    }
195
196    public boolean hasValueOrEmpty(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException
197    {
198        return getContentIfExists()
199                .map(c -> c.hasValueOrEmpty(dataPath))
200                .orElse(false);
201    }
202    
203    public boolean hasLocalValueOrEmpty(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException
204    {
205        return getContentIfExists()
206                .map(c -> c.hasLocalValueOrEmpty(dataPath))
207                .orElse(false);
208    }
209    
210    public boolean hasExternalValueOrEmpty(String dataPath) throws IllegalArgumentException, BadDataPathCardinalityException
211    {
212        return getContentIfExists()
213                .map(c -> c.hasExternalValueOrEmpty(dataPath))
214                .orElse(false);
215    }
216    
217    public boolean hasComments(String dataName) throws IllegalArgumentException, UndefinedItemPathException
218    {
219        return getContentIfExists()
220                .map(c -> c.hasComments(dataName))
221                .orElse(false);
222    }
223
224    public Collection<String> getDataNames()
225    {
226        return getContentIfExists()
227                .map(c -> c.getDataNames())
228                .orElse(CollectionUtils.EMPTY_COLLECTION);
229    }
230    
231    public <T> T getValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
232    {
233        return getContentIfExists()
234                .map(c -> c.<T>getValue(dataPath))
235                .orElse(null);
236    }
237
238    public <T> T getValue(String dataPath, boolean allowMultiValuedPathSegments) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
239    {
240        return getContentIfExists()
241                .map(c -> c.<T>getValue(dataPath, allowMultiValuedPathSegments))
242                .orElse(null);
243    }
244
245    public <T> T getValue(String dataPath, boolean useDefaultFromModel, T defaultValue) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
246    {
247        return getContentIfExists()
248                .map(c -> c.<T>getValue(dataPath, useDefaultFromModel, defaultValue))
249                .orElse(null);
250    }
251    
252    public <T> T getLocalValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
253    {
254        return getContentIfExists()
255                .map(c -> c.<T>getLocalValue(dataPath))
256                .orElse(null);
257    }
258    
259    public <T> T getExternalValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
260    {
261        return getContentIfExists()
262                .map(c -> c.<T>getExternalValue(dataPath))
263                .orElse(null);
264    }
265    
266    public ExternalizableDataStatus getStatus(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadDataPathCardinalityException
267    {
268        return getContentIfExists()
269                .map(c -> c.getStatus(dataPath))
270                .orElse(ExternalizableDataStatus.LOCAL);
271    }
272    
273    public List<DataComment> getComments(String dataName) throws IllegalArgumentException, UndefinedItemPathException
274    {
275        return getContentIfExists()
276                .map(c -> c.getComments(dataName))
277                .orElse(null);
278    }
279    
280    public Collection< ? extends ModelItemContainer> getModel()
281    {
282        return getContentIfExists()
283                .map(c -> c.getModel())
284                .orElse(null);
285    }
286
287    public ModelItem getDefinition(String path) throws IllegalArgumentException, UndefinedItemPathException
288    {
289        return getContentIfExists()
290                .map(c -> c.getDefinition(path))
291                .orElse(null);
292    }
293    
294    public boolean hasDefinition(String path) throws IllegalArgumentException
295    {
296        return getContentIfExists()
297                .map(c -> c.hasDefinition(path))
298                .orElse(null);
299    }
300
301    public ModifiableModelAwareComposite getComposite(String compositePath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
302    {
303        return getContentIfExists()
304                .map(c -> c.getComposite(compositePath))
305                .orElse(null);
306    }
307    
308    public ModifiableModelAwareComposite getLocalComposite(String compositePath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
309    {
310        return getContentIfExists()
311                .map(c -> c.getLocalComposite(compositePath))
312                .orElse(null);
313    }
314    
315    public ModifiableModelAwareComposite getExternalComposite(String compositePath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
316    {
317        return getContentIfExists()
318                .map(c -> c.getExternalComposite(compositePath))
319                .orElse(null);
320    }
321
322    public ModifiableModelAwareRepeater getRepeater(String repeaterPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
323    {
324        return getContentIfExists()
325                .map(c -> c.getRepeater(repeaterPath))
326                .orElse(null);
327    }
328    
329    public ModifiableModelAwareRepeater getLocalRepeater(String repeaterPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
330    {
331        return getContentIfExists()
332                .map(c -> c.getLocalRepeater(repeaterPath))
333                .orElse(null);
334    }
335    
336    public ModifiableModelAwareRepeater getExternalRepeater(String repeaterPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
337    {
338        return getContentIfExists()
339                .map(c -> c.getExternalRepeater(repeaterPath))
340                .orElse(null);
341    }
342
343    public void dataToSAX(ContentHandler contentHandler, String dataPath, DataContext context) throws SAXException
344    {
345        getContent().dataToSAX(contentHandler, dataPath, context);
346    }
347    
348    public ModifiableModelAwareComposite getComposite(String compositePath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
349    {
350        return getContentIfExists()
351                .map(c -> c.getComposite(compositePath, createNew))
352                .orElse(null);
353    }
354    
355    public ModifiableModelAwareComposite getLocalComposite(String compositePath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
356    {
357        return getContentIfExists()
358                .map(c -> c.getLocalComposite(compositePath, createNew))
359                .orElse(null);
360    }
361    
362    public ModifiableModelAwareComposite getExternalComposite(String compositePath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
363    {
364        return getContentIfExists()
365                .map(c -> c.getExternalComposite(compositePath, createNew))
366                .orElse(null);
367    }
368
369    public ModifiableModelAwareRepeater getRepeater(String repeaterPath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
370    {
371        return getContentIfExists()
372                .map(c -> c.getRepeater(repeaterPath, createNew))
373                .orElse(null);
374    }
375    
376    public ModifiableModelAwareRepeater getLocalRepeater(String repeaterPath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
377    {
378        return getContentIfExists()
379                .map(c -> c.getLocalRepeater(repeaterPath, createNew))
380                .orElse(null);
381    }
382    
383    public ModifiableModelAwareRepeater getExternalRepeater(String repeaterPath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
384    {
385        return getContentIfExists()
386                .map(c -> c.getExternalRepeater(repeaterPath, createNew))
387                .orElse(null);
388    }
389    
390    public <T extends SynchronizationResult> T synchronizeValues(Map<String, Object> values) throws UndefinedItemPathException, BadItemTypeException
391    {
392        return getContent().synchronizeValues(values);
393    }
394    
395    public <T extends SynchronizationResult> T synchronizeValues(Map<String, Object> values, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException
396    {
397        return getContent().synchronizeValues(values, context);
398    }
399    
400    public <T extends SynchronizationResult> T synchronizeValues(ViewItemContainer viewItemContainer, Map<String, Object> values) throws UndefinedItemPathException, BadItemTypeException
401    {
402        return getContent().synchronizeValues(viewItemContainer, values);
403    }
404    
405    public <T extends SynchronizationResult> T synchronizeValues(ViewItemContainer viewItemContainer, Map<String, Object> values, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException
406    {
407        return getContent().synchronizeValues(viewItemContainer, values, context);
408    }
409
410    public void setValue(String dataPath, Object value) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
411    {
412        getContent().setValue(dataPath, value);
413    }
414    
415    public void setLocalValue(String dataPath, Object localValue) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
416    {
417        getContent().setLocalValue(dataPath, localValue);
418    }
419    
420    public void setExternalValue(String dataPath, Object externalValue) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
421    {
422        getContent().setExternalValue(dataPath, externalValue);
423    }
424    
425    public void setStatus(String dataPath, ExternalizableDataStatus status) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
426    {
427        getContent().setStatus(dataPath, status);
428    }
429    
430    public void setComments(String dataName, List<DataComment> comments) throws IllegalArgumentException, UndefinedItemPathException
431    {
432        getContent().setComments(dataName, comments);
433    }
434
435    public void removeValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, UnknownDataException, BadDataPathCardinalityException
436    {
437        getContent().removeValue(dataPath);
438    }
439    
440    public void removeLocalValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, UnknownDataException, BadDataPathCardinalityException
441    {
442        getContent().removeLocalValue(dataPath);
443    }
444    
445    public void removeExternalValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, UnknownDataException, BadDataPathCardinalityException
446    {
447        getContent().removeExternalValue(dataPath);
448    }
449    
450    public void removeExternalizableMetadataIfExists(String dataPath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException
451    {
452        getContent().removeExternalizableMetadataIfExists(dataPath);
453    }
454    
455    public ModifiableRepositoryData getRepositoryData()
456    {
457        return getContent().getRepositoryData();
458    }
459    
460    public Optional<? extends ModifiableModelAwareDataHolder> getParentDataHolder()
461    {
462        return getContent().getParentDataHolder();
463    }
464    
465    public ModifiableModelAwareDataHolder getRootDataHolder()
466    {
467        return getContent().getRootDataHolder();
468    }
469    
470    @Override
471    public String toString()
472    {
473        return _contentId;
474    }
475}