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.ModifiableModelAwareComposite;
041import org.ametys.plugins.repository.data.holder.group.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.ViewItemAccessor;
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 void dataToSAX(ContentHandler contentHandler, ViewItemAccessor viewItemAccessor, DataContext context) throws SAXException, BadItemTypeException
349    {
350        getContent().dataToSAX(contentHandler, viewItemAccessor, context);
351    }
352    
353    public void dataToSAXForEdition(ContentHandler contentHandler, ViewItemAccessor viewItemAccessor, DataContext context) throws SAXException, BadItemTypeException
354    {
355        getContent().dataToSAXForEdition(contentHandler, viewItemAccessor, context);
356    }
357    
358    public Object dataToJSON(String dataPath, DataContext context)
359    {
360        return getContent().dataToJSON(dataPath, context);
361    }
362    
363    public Map<String, Object> dataToJSON(ViewItemAccessor viewItemAccessor, DataContext context) throws BadItemTypeException
364    {
365        return getContent().dataToJSON(viewItemAccessor, context);
366    }
367    
368    public Map<String, Object> dataToJSONForEdition(ViewItemAccessor viewItemAccessor, DataContext context) throws BadItemTypeException
369    {
370        return getContent().dataToJSONForEdition(viewItemAccessor, context);
371    }
372    
373    public Map<String, Object> dataToMap(ViewItemAccessor viewItemAccessor, DataContext context)
374    {
375        return getContent().dataToMap(viewItemAccessor, context);
376    }
377    
378    public boolean hasDifferences(ViewItemAccessor viewItemAccessor, Map<String, Object> values) throws UndefinedItemPathException, BadItemTypeException
379    {
380        return getContent().hasDifferences(viewItemAccessor, values);
381    }
382    
383    public boolean hasDifferences(ViewItemAccessor viewItemAccessor, Map<String, Object> values, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException
384    {
385        return getContent().hasDifferences(viewItemAccessor, values, context);
386    }
387    
388    public Collection<ModelItem> getDifferences(ViewItemAccessor viewItemAccessor, Map<String, Object> values) throws UndefinedItemPathException, BadItemTypeException
389    {
390        return getContent().getDifferences(viewItemAccessor, values);
391    }
392    
393    public Collection<ModelItem> getDifferences(ViewItemAccessor viewItemAccessor, Map<String, Object> values, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException
394    {
395        return getContent().getDifferences(viewItemAccessor, values, context);
396    }
397    
398    public ModifiableModelAwareComposite getComposite(String compositePath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
399    {
400        return getContentIfExists()
401                .map(c -> c.getComposite(compositePath, createNew))
402                .orElse(null);
403    }
404    
405    public ModifiableModelAwareComposite getLocalComposite(String compositePath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
406    {
407        return getContentIfExists()
408                .map(c -> c.getLocalComposite(compositePath, createNew))
409                .orElse(null);
410    }
411    
412    public ModifiableModelAwareComposite getExternalComposite(String compositePath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
413    {
414        return getContentIfExists()
415                .map(c -> c.getExternalComposite(compositePath, createNew))
416                .orElse(null);
417    }
418
419    public ModifiableModelAwareRepeater getRepeater(String repeaterPath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
420    {
421        return getContentIfExists()
422                .map(c -> c.getRepeater(repeaterPath, createNew))
423                .orElse(null);
424    }
425    
426    public ModifiableModelAwareRepeater getLocalRepeater(String repeaterPath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
427    {
428        return getContentIfExists()
429                .map(c -> c.getLocalRepeater(repeaterPath, createNew))
430                .orElse(null);
431    }
432    
433    public ModifiableModelAwareRepeater getExternalRepeater(String repeaterPath, boolean createNew) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
434    {
435        return getContentIfExists()
436                .map(c -> c.getExternalRepeater(repeaterPath, createNew))
437                .orElse(null);
438    }
439    
440    public <T extends SynchronizationResult> T synchronizeValues(Map<String, Object> values) throws UndefinedItemPathException, BadItemTypeException
441    {
442        return getContent().synchronizeValues(values);
443    }
444    
445    public <T extends SynchronizationResult> T synchronizeValues(Map<String, Object> values, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException
446    {
447        return getContent().synchronizeValues(values, context);
448    }
449    
450    public <T extends SynchronizationResult> T synchronizeValues(ViewItemAccessor viewItemAccessor, Map<String, Object> values) throws UndefinedItemPathException, BadItemTypeException
451    {
452        return getContent().synchronizeValues(viewItemAccessor, values);
453    }
454    
455    public <T extends SynchronizationResult> T synchronizeValues(ViewItemAccessor viewItemAccessor, Map<String, Object> values, SynchronizationContext context) throws UndefinedItemPathException, BadItemTypeException
456    {
457        return getContent().synchronizeValues(viewItemAccessor, values, context);
458    }
459
460    public void setValue(String dataPath, Object value) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
461    {
462        getContent().setValue(dataPath, value);
463    }
464    
465    public void setLocalValue(String dataPath, Object localValue) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
466    {
467        getContent().setLocalValue(dataPath, localValue);
468    }
469    
470    public void setExternalValue(String dataPath, Object externalValue) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
471    {
472        getContent().setExternalValue(dataPath, externalValue);
473    }
474    
475    public void setStatus(String dataPath, ExternalizableDataStatus status) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, BadDataPathCardinalityException
476    {
477        getContent().setStatus(dataPath, status);
478    }
479    
480    public void setComments(String dataName, List<DataComment> comments) throws IllegalArgumentException, UndefinedItemPathException
481    {
482        getContent().setComments(dataName, comments);
483    }
484
485    public void removeValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, UnknownDataException, BadDataPathCardinalityException
486    {
487        getContent().removeValue(dataPath);
488    }
489    
490    public void removeLocalValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, UnknownDataException, BadDataPathCardinalityException
491    {
492        getContent().removeLocalValue(dataPath);
493    }
494    
495    public void removeExternalValue(String dataPath) throws IllegalArgumentException, UndefinedItemPathException, BadItemTypeException, UnknownDataException, BadDataPathCardinalityException
496    {
497        getContent().removeExternalValue(dataPath);
498    }
499    
500    public void removeExternalizableMetadataIfExists(String dataPath) throws IllegalArgumentException, BadItemTypeException, UndefinedItemPathException, BadDataPathCardinalityException
501    {
502        getContent().removeExternalizableMetadataIfExists(dataPath);
503    }
504    
505    public ModifiableRepositoryData getRepositoryData()
506    {
507        return getContent().getRepositoryData();
508    }
509    
510    public Optional<? extends ModifiableModelAwareDataHolder> getParentDataHolder()
511    {
512        return getContent().getParentDataHolder();
513    }
514    
515    public ModifiableModelAwareDataHolder getRootDataHolder()
516    {
517        return getContent().getRootDataHolder();
518    }
519    
520    @Override
521    public String toString()
522    {
523        return _contentId;
524    }
525}