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