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