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