001/*
002 *  Copyright 2010 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.repository;
017
018import java.time.ZonedDateTime;
019import java.util.ArrayList;
020import java.util.Calendar;
021import java.util.Collection;
022import java.util.Date;
023import java.util.HashMap;
024import java.util.LinkedHashSet;
025import java.util.List;
026import java.util.Locale;
027import java.util.Map;
028import java.util.Optional;
029import java.util.Set;
030
031import javax.jcr.Node;
032import javax.jcr.NodeIterator;
033import javax.jcr.RepositoryException;
034import javax.jcr.Value;
035
036import org.xml.sax.ContentHandler;
037import org.xml.sax.SAXException;
038
039import org.ametys.cms.content.references.OutgoingReferences;
040import org.ametys.cms.content.references.OutgoingReferencesHelper;
041import org.ametys.cms.contenttype.ContentType;
042import org.ametys.cms.data.holder.IndexableDataHolder;
043import org.ametys.cms.data.holder.impl.DefaultModelAwareDataHolder;
044import org.ametys.cms.search.model.SystemPropertyExtensionPoint;
045import org.ametys.core.user.UserIdentity;
046import org.ametys.core.util.DateUtils;
047import org.ametys.plugins.explorer.resources.ResourceCollection;
048import org.ametys.plugins.repository.AmetysObject;
049import org.ametys.plugins.repository.AmetysObjectIterable;
050import org.ametys.plugins.repository.AmetysObjectResolver;
051import org.ametys.plugins.repository.AmetysRepositoryException;
052import org.ametys.plugins.repository.CopiableAmetysObject;
053import org.ametys.plugins.repository.ModifiableTraversableAmetysObject;
054import org.ametys.plugins.repository.RepositoryConstants;
055import org.ametys.plugins.repository.RepositoryIntegrityViolationException;
056import org.ametys.plugins.repository.UnknownAmetysObjectException;
057import org.ametys.plugins.repository.data.UnknownDataException;
058import org.ametys.plugins.repository.data.holder.ModifiableModelLessDataHolder;
059import org.ametys.plugins.repository.data.holder.impl.DefaultModifiableModelLessDataHolder;
060import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
061import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
062import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData;
063import org.ametys.plugins.repository.dublincore.DCMITypes;
064import org.ametys.plugins.repository.jcr.DefaultAmetysObject;
065import org.ametys.plugins.repository.jcr.DublinCoreHelper;
066import org.ametys.plugins.repository.jcr.JCRTraversableAmetysObject;
067import org.ametys.plugins.repository.lock.LockableAmetysObject;
068import org.ametys.plugins.repository.metadata.MultilingualString;
069import org.ametys.plugins.repository.tag.TaggableAmetysObjectHelper;
070import org.ametys.runtime.model.View;
071import org.ametys.runtime.model.type.DataContext;
072import org.ametys.runtime.model.type.ModelItemTypeConstants;
073
074/**
075 * Default implementation of a {@link Content}, also versionable, lockable and workflow-aware.
076 * @param <F> the actual type of factory.
077 */
078public class DefaultContent<F extends ContentFactory> extends DefaultAmetysObject<F> implements Content, CopiableAmetysObject, JCRTraversableAmetysObject, ReactionableObject, ReportableObject
079{
080    /** Constants for the root outgoing references node */
081    public static final String METADATA_ROOT_OUTGOING_REFERENCES = "root-outgoing-references";
082    
083    /** Constants for the outgoing references node */
084    public static final String METADATA_OUTGOING_REFERENCES = "outgoing-references";
085    
086    /** Constants for the outgoing references path property */
087    public static final String METADATA_OUTGOING_REFERENCES_PATH_PROPERTY = "path";
088    
089    /** Constants for the outgoing reference property */
090    public static final String METADATA_OUTGOING_REFERENCE_PROPERTY = "reference";
091    
092    /** Constants for the outgoing reference nodetype */
093    public static final String METADATA_OUTGOING_REFERENCE_NODETYPE = "reference";
094    
095    /** Constants for language Metadata* */
096    public static final String METADATA_LANGUAGE = "language";
097    
098    /** Constants for author Metadata* */
099    public static final String METADATA_CREATOR = "creator";
100    
101    /** Constants for lastModified Metadata* */
102    public static final String METADATA_CREATION = "creationDate";
103    
104    /** Constants for the first validator metadata */
105    public static final String METADATA_FIRST_VALIDATOR = "firstValidator";
106    
107    /** Constants for firstValidationDate Metadata* */
108    public static final String METADATA_FIRST_VALIDATION = "firstValidationDate";
109    
110    /** Constants for last validator Metadata* */
111    public static final String METADATA_LAST_VALIDATOR = "lastValidator";
112    
113    /** Constants for lastValidationDate Metadata* */
114    public static final String METADATA_LAST_VALIDATION = "lastValidationDate";
115    
116    /** Constants for last major validator Metadata* */
117    public static final String METADATA_LAST_MAJOR_VALIDATOR = "lastMajorValidator";
118    
119    /** Constants for lastMajorValidationDate Metadata* */
120    public static final String METADATA_LAST_MAJORVALIDATION = "lastMajorValidationDate";
121    
122    /** Constants for last contributor Metadata* */
123    public static final String METADATA_CONTRIBUTOR = "contributor";
124    
125    /** Constants for lastModified Metadata* */
126    public static final String METADATA_MODIFIED = "lastModified";
127    
128    /** Constants for contentType Metadata* */
129    public static final String METADATA_CONTENTTYPE = "contentType";
130    
131    /** Constants for contentType Metadata* */
132    public static final String METADATA_MIXINCONTENTTYPES = "mixins";
133    
134    /** Constant for the attachment node name. */
135    public static final String ATTACHMENTS_NODE_NAME = RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ":attachments";
136    
137    /** The default locale for content */
138    public static final Locale DEFAULT_CONTENT_LOCALE = Locale.ENGLISH;
139    
140    /** The type identifiers of this content */
141    protected String[] _types;
142    /** The mixin type identifiers of this content */
143    protected String[] _mixins;
144
145    /**
146     * Creates a JCR-based Content.
147     * @param node the JCR Node backing this Content.
148     * @param parentPath the parent path in the Ametys hierarchy.
149     * @param factory the corresponding {@link ContentFactory}.
150     */
151    public DefaultContent(Node node, String parentPath, F factory)
152    {
153        super(node, parentPath, factory);
154    }
155    
156    @Override
157    public String[] getTypes() throws AmetysRepositoryException
158    {
159        if (_types == null)
160        {
161            _types = getInternalDataHolder().getValueOfType(METADATA_CONTENTTYPE, ModelItemTypeConstants.STRING_TYPE_ID, new String[0]);
162        }
163        
164        return _types;
165    }
166    
167    @Override
168    public void setType(String type) throws AmetysRepositoryException
169    {
170        setTypes(new String[] {type});
171    }
172    
173    @Override
174    public void setTypes(String[] types) throws AmetysRepositoryException
175    {
176        getInternalDataHolder().setValue(DefaultContent.METADATA_CONTENTTYPE, types, ModelItemTypeConstants.STRING_TYPE_ID);
177        _types = types;
178    }
179    
180    @Override
181    public String[] getMixinTypes() throws AmetysRepositoryException
182    {
183        if (_mixins == null)
184        {
185            _mixins = getInternalDataHolder().getValueOfType(METADATA_MIXINCONTENTTYPES, ModelItemTypeConstants.STRING_TYPE_ID, new String[0]);
186        }
187        return _mixins;
188    }
189    
190    @Override
191    public void setMixinTypes(String[] mixins) throws AmetysRepositoryException
192    {
193        getInternalDataHolder().setValue(DefaultContent.METADATA_MIXINCONTENTTYPES, mixins, ModelItemTypeConstants.STRING_TYPE_ID);
194        _mixins = mixins;
195    }
196    
197    @Override
198    public String getLanguage() throws AmetysRepositoryException
199    {
200        return getInternalDataHolder().getValueOfType(METADATA_LANGUAGE, ModelItemTypeConstants.STRING_TYPE_ID);
201    }
202    
203    @Override
204    public void setLanguage(String language) throws AmetysRepositoryException
205    {
206        getInternalDataHolder().setValue(DefaultContent.METADATA_LANGUAGE, language, ModelItemTypeConstants.STRING_TYPE_ID);
207    }
208    
209    public String getTitle(Locale locale) throws UnknownDataException, AmetysRepositoryException
210    {
211        Object value = getValue(ATTRIBUTE_TITLE);
212        if (value == null)
213        {
214            throw new UnknownDataException("Unknown attribute " + ATTRIBUTE_TITLE + " for content " + getId());
215        }
216        else if (value instanceof MultilingualString)
217        {
218            MultilingualString multilingual = (MultilingualString) value;
219            
220            if (locale != null && multilingual.hasLocale(locale))
221            {
222                return multilingual.getValue(locale);
223            }
224            else if (multilingual.hasLocale(DEFAULT_CONTENT_LOCALE))
225            {
226                return multilingual.getValue(DEFAULT_CONTENT_LOCALE);
227            }
228            else if (multilingual.getValues().isEmpty())
229            {
230                throw new UnknownDataException("Unknown attribute " + ATTRIBUTE_TITLE + " for content " + getId());
231            }
232            else
233            {
234                return multilingual.getValues().get(0);
235            }
236        }
237        else
238        {
239            return (String) value;
240        }
241    }
242    
243    public String getTitle() throws UnknownDataException, AmetysRepositoryException
244    {
245        return getTitle(null);
246    }
247    
248    @Override
249    public UserIdentity getCreator() throws UnknownDataException, AmetysRepositoryException
250    {
251        try
252        {
253            RepositoryData repositoryData = new JCRRepositoryData(getNode());
254            RepositoryData creatorReposioryData = repositoryData.getRepositoryData(METADATA_CREATOR);
255            return new UserIdentity(creatorReposioryData.getString("login"), creatorReposioryData.getString("population"));
256        }
257        catch (AmetysRepositoryException e)
258        {
259            throw new AmetysRepositoryException("Error while getting creator property for content " + this, e);
260        }
261    }
262    
263    @Override
264    public ZonedDateTime getCreationDate() throws UnknownDataException, AmetysRepositoryException
265    {
266        RepositoryData repositoryData = new JCRRepositoryData(getNode());
267        Calendar date = repositoryData.getDate(METADATA_CREATION);
268        return DateUtils.asZonedDateTime(date);
269    }
270    
271    @Override
272    public UserIdentity getLastContributor() throws UnknownDataException, AmetysRepositoryException
273    {
274        try
275        {
276            RepositoryData repositoryData = new JCRRepositoryData(getNode());
277            RepositoryData contributorReposioryData = repositoryData.getRepositoryData(METADATA_CONTRIBUTOR);
278            return new UserIdentity(contributorReposioryData.getString("login"), contributorReposioryData.getString("population"));
279        }
280        catch (AmetysRepositoryException e)
281        {
282            throw new AmetysRepositoryException("Error while getting creator property for content " + this, e);
283        }
284    }
285    
286    @Override
287    public ZonedDateTime getLastModified() throws UnknownDataException, AmetysRepositoryException
288    {
289        RepositoryData repositoryData = new JCRRepositoryData(getNode());
290        Calendar date = repositoryData.getDate(METADATA_MODIFIED);
291        return DateUtils.asZonedDateTime(date);
292    }
293    
294    public Optional<UserIdentity> getFirstValidator() throws UnknownDataException, AmetysRepositoryException
295    {
296        RepositoryData repositoryData = new JCRRepositoryData(getNode());
297        if (repositoryData.hasValue(METADATA_FIRST_VALIDATOR))
298        {
299            try
300            {
301                RepositoryData contributorReposioryData = repositoryData.getRepositoryData(METADATA_FIRST_VALIDATOR);
302                return Optional.of(new UserIdentity(contributorReposioryData.getString("login"), contributorReposioryData.getString("population")));
303            }
304            catch (AmetysRepositoryException e)
305            {
306                throw new AmetysRepositoryException("Error while getting creator property for content " + this, e);
307            }
308        }
309        return Optional.empty();
310    }
311    
312    @Override
313    public ZonedDateTime getFirstValidationDate() throws UnknownDataException, AmetysRepositoryException
314    {
315        RepositoryData repositoryData = new JCRRepositoryData(getNode());
316        if (repositoryData.hasValue(METADATA_FIRST_VALIDATION))
317        {
318            Calendar date = repositoryData.getDate(METADATA_FIRST_VALIDATION);
319            return DateUtils.asZonedDateTime(date);
320        }
321        return null;
322    }
323    
324    public Optional<UserIdentity> getLastValidator() throws AmetysRepositoryException
325    {
326        RepositoryData repositoryData = new JCRRepositoryData(getNode());
327        if (repositoryData.hasValue(METADATA_LAST_VALIDATOR))
328        {
329            try
330            {
331                RepositoryData contributorReposioryData = repositoryData.getRepositoryData(METADATA_LAST_VALIDATOR);
332                return Optional.of(new UserIdentity(contributorReposioryData.getString("login"), contributorReposioryData.getString("population")));
333            }
334            catch (AmetysRepositoryException e)
335            {
336                throw new AmetysRepositoryException("Error while getting creator property for content " + this, e);
337            }
338        }
339        return Optional.empty();
340    }
341    
342    @Override
343    public ZonedDateTime getLastValidationDate() throws UnknownDataException, AmetysRepositoryException
344    {
345        RepositoryData repositoryData = new JCRRepositoryData(getNode());
346        if (repositoryData.hasValue(METADATA_LAST_VALIDATION))
347        {
348            Calendar date = repositoryData.getDate(METADATA_LAST_VALIDATION);
349            return DateUtils.asZonedDateTime(date);
350        }
351        return null;
352    }
353    
354    public Optional<UserIdentity> getLastMajorValidator() throws UnknownDataException, AmetysRepositoryException
355    {
356        RepositoryData repositoryData = new JCRRepositoryData(getNode());
357        if (repositoryData.hasValue(METADATA_LAST_MAJOR_VALIDATOR))
358        {
359            try
360            {
361                RepositoryData contributorReposioryData = repositoryData.getRepositoryData(METADATA_LAST_MAJOR_VALIDATOR);
362                return Optional.of(new UserIdentity(contributorReposioryData.getString("login"), contributorReposioryData.getString("population")));
363            }
364            catch (AmetysRepositoryException e)
365            {
366                throw new AmetysRepositoryException("Error while getting creator property for content " + this, e);
367            }
368        }
369        return Optional.empty();
370    }
371    
372    @Override
373    public ZonedDateTime getLastMajorValidationDate() throws AmetysRepositoryException
374    {
375        RepositoryData repositoryData = new JCRRepositoryData(getNode());
376        if (repositoryData.hasValue(METADATA_LAST_MAJORVALIDATION))
377        {
378            Calendar date = repositoryData.getDate(METADATA_LAST_MAJORVALIDATION);
379            return DateUtils.asZonedDateTime(date);
380        }
381        return null;
382    }
383    
384    // Tag management.
385    @Override
386    public Set<String> getTags() throws AmetysRepositoryException
387    {
388        return TaggableAmetysObjectHelper.getTags(this);
389    }
390    
391    @Override
392    public Map<String, OutgoingReferences> getOutgoingReferences() throws AmetysRepositoryException
393    {
394        Map<String, OutgoingReferences> outgoingReferencesByPath = new HashMap<>();
395        
396        try
397        {
398            Node contentNode = getNode();
399            if (contentNode.hasNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES))
400            {
401                Node rootOutgoingRefsNode = contentNode.getNode(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_ROOT_OUTGOING_REFERENCES);
402                
403                // Loop on outgoing ref node by (metadata) path.
404                NodeIterator outgoingRefsNodeIterator = rootOutgoingRefsNode.getNodes(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_OUTGOING_REFERENCES);
405                while (outgoingRefsNodeIterator.hasNext())
406                {
407                    Node outgoingRefsNode = outgoingRefsNodeIterator.nextNode();
408                    String path = outgoingRefsNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX_INTERNAL + ':' + METADATA_OUTGOING_REFERENCES_PATH_PROPERTY).getString();
409                    
410                    // Loop on each outgoing ref node values for each reference type and collecting outgoing references.
411                    OutgoingReferences outgoingReferences = new OutgoingReferences();
412                    NodeIterator outgoingReferenceNodeIterator = outgoingRefsNode.getNodes();
413                    while (outgoingReferenceNodeIterator.hasNext())
414                    {
415                        Node outgoingReferenceNode = outgoingReferenceNodeIterator.nextNode();
416                        Value[] referenceValues = outgoingReferenceNode.getProperty(RepositoryConstants.NAMESPACE_PREFIX + ':' + METADATA_OUTGOING_REFERENCE_PROPERTY).getValues();
417                        
418                        List<String> referenceValuesAsList = new ArrayList<>(referenceValues.length);
419                        for (Value value : referenceValues)
420                        {
421                            referenceValuesAsList.add(value.getString());
422                        }
423                        
424                        outgoingReferences.put(outgoingReferenceNode.getName(), referenceValuesAsList);
425                    }
426                    
427                    // Updating the outgoing references map
428                    if (!outgoingReferences.isEmpty())
429                    {
430                        if (outgoingReferencesByPath.containsKey(path))
431                        {
432                            outgoingReferencesByPath.get(path).merge(outgoingReferences);
433                        }
434                        else
435                        {
436                            outgoingReferencesByPath.put(path, outgoingReferences);
437                        }
438                    }
439                }
440            }
441        }
442        catch (RepositoryException e)
443        {
444            throw new AmetysRepositoryException(e);
445        }
446        
447        return outgoingReferencesByPath;
448    }
449    
450    @Override
451    public Collection<Content> getReferencingContents() throws AmetysRepositoryException
452    {
453        return _getReferencingContents(Long.MAX_VALUE).referencingContents();
454    }
455    
456    private ReferencingContentsSearch _getReferencingContents(long limit) throws AmetysRepositoryException
457    {
458        Set<Content> contents = new LinkedHashSet<>();
459        long total = 0;
460        boolean hasOtherReferences = false;
461        
462        try
463        {
464            NodeIterator results = OutgoingReferencesHelper.getContentOutgoingReferences(this);
465            total = results.getSize();
466            
467            AmetysObjectResolver resolver = _getFactory()._getAOResolver();
468            while (results.hasNext() && contents.size() < limit)
469            {
470                Node node = results.nextNode();
471                Node contentNode = node.getParent()  // go up towards node 'ametys-internal:outgoing-references
472                                       .getParent()  // go up towards node 'ametys-internal:root-outgoing-references
473                                       .getParent(); // go up towards node of the content
474                Content content = resolver.resolve(contentNode, false);
475                contents.add(content);
476            }
477            
478            hasOtherReferences = results.hasNext();
479        }
480        catch (RepositoryException e)
481        {
482            throw new AmetysRepositoryException("Unable to resolve references for content " + getId(), e);
483        }
484        
485        return new ReferencingContentsSearch(total, contents, hasOtherReferences);
486    }
487    
488    @Override
489    public ReferencingContentsSearch searchReferencingContents(int limit) throws AmetysRepositoryException
490    {
491        return _getReferencingContents(limit);
492    }
493    
494    public boolean hasReferencingContents() throws AmetysRepositoryException
495    {
496        try
497        {
498            return OutgoingReferencesHelper.getContentOutgoingReferences(this).getSize() != 0;
499        }
500        catch (RepositoryException e)
501        {
502            throw new AmetysRepositoryException("A repository exception occured: ", e);
503        }
504    }
505    
506    @Override
507    public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, List<String> restrictTo) throws AmetysRepositoryException
508    {
509        return copyTo(parent, name);
510    }
511    
512    @Override
513    public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name) throws AmetysRepositoryException
514    {
515        return copyTo(parent, name, 0, true, true);
516    }
517    
518    /**
519     * Copy the current {@link DefaultWorkflowAwareContent} to the given object. Be careful, this method save changes, but do not create a new version (checkpoint)
520     * @param parent The parent of the new object. Can not be null.
521     * @param name Name of the new object. Can be null. If null, the new name will be get from the copied object.
522     * @param initWorkflowActionId The initial workflow action id
523     * @param notifyObservers Set to false to do not fire observer events
524     * @param checkpoint true to check the content in if it is versionable
525     * @return the created object
526     * @throws AmetysRepositoryException if an error occurs.
527     */
528    public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, int initWorkflowActionId, boolean notifyObservers, boolean checkpoint) throws AmetysRepositoryException
529    {
530        return _getFactory()._getContentDAO().copy(this, parent, name, null, initWorkflowActionId, notifyObservers, checkpoint, false, false, DataContext.newInstance());
531    }
532    
533    /**
534     * Copy the current {@link DefaultWorkflowAwareContent} to the given object. Be careful, this method save changes, but do not create a new version (checkpoint)
535     * @param parent The parent of the new object. Can not be null.
536     * @param name Name of the new object. Can be null. If null, the new name will be get from the copied object.
537     * @param lang Language of the new object. Can be null. If null, the new language will be get from the copied object.
538     * @param initWorkflowActionId The initial workflow action id
539     * @param waitAsyncObservers if true, waits if necessary for the asynchronous observers to complete
540     * @param copyACL true to copy ACL of source content
541     * @param context The context of the data to copy
542     * @return the created object
543     * @throws AmetysRepositoryException if an error occurs.
544     */
545    public ModifiableContent copyTo(ModifiableTraversableAmetysObject parent, String name, String lang, int initWorkflowActionId, boolean waitAsyncObservers, boolean copyACL, DataContext context) throws AmetysRepositoryException
546    {
547        return _getFactory()._getContentDAO().copy(this, parent, name, lang, initWorkflowActionId, true, true, waitAsyncObservers, copyACL, context);
548    }
549
550    // Dublin Core metadata. //
551    
552    @Override
553    public String getDCTitle() throws AmetysRepositoryException
554    {
555        return DublinCoreHelper.getDCTitle(this, getTitle());
556    }
557    
558    @Override
559    public String getDCCreator() throws AmetysRepositoryException
560    {
561        return DublinCoreHelper.getDCCreator(this, UserIdentity.userIdentityToString(getCreator()));
562    }
563
564    @Override
565    public String[] getDCSubject() throws AmetysRepositoryException
566    {
567        return DublinCoreHelper.getDCSubject(this);
568    }
569
570    @Override
571    public String getDCDescription() throws AmetysRepositoryException
572    {
573        String seoDesc = null;
574        try
575        {
576            RepositoryData repositoryData = new JCRRepositoryData(getNode());
577            RepositoryData seoReposioryData = repositoryData.getRepositoryData("seo");
578            seoDesc = seoReposioryData.getString("description");
579        }
580        catch (UnknownDataException me)
581        {
582            seoDesc = null;
583        }
584        
585        return DublinCoreHelper.getDCDescription(this, seoDesc);
586    }
587
588    @Override
589    public String getDCPublisher() throws AmetysRepositoryException
590    {
591        return DublinCoreHelper.getDCPublisher(this, getLastValidator().map(UserIdentity::userIdentityToString).orElse(null));
592    }
593    
594    @Override
595    public String getDCContributor() throws AmetysRepositoryException
596    {
597        return DublinCoreHelper.getDCContributor(this, UserIdentity.userIdentityToString(getLastContributor()));
598    }
599    
600    @Override
601    public Date getDCDate() throws AmetysRepositoryException
602    {
603        Date defaultDCDate = Optional.ofNullable(getLastValidationDate())
604                                     .map(DateUtils::asDate)
605                                     .orElse(null);
606        return DublinCoreHelper.getDCDate(this, defaultDCDate);
607        
608    }
609    
610    @Override
611    public String getDCType() throws AmetysRepositoryException
612    {
613        return DublinCoreHelper.getDCType(this, DCMITypes.TEXT);
614    }
615    
616    @Override
617    public String getDCFormat() throws AmetysRepositoryException
618    {
619        return DublinCoreHelper.getDCFormat(this, "text/html");
620    }
621    
622    @Override
623    public String getDCIdentifier() throws AmetysRepositoryException
624    {
625        return DublinCoreHelper.getDCIdentifier(this, getId());
626    }
627    
628    @Override
629    public String getDCSource() throws AmetysRepositoryException
630    {
631        return DublinCoreHelper.getDCSource(this);
632    }
633    
634    @Override
635    public String getDCLanguage() throws AmetysRepositoryException
636    {
637        return DublinCoreHelper.getDCLanguage(this, getLanguage());
638    }
639    
640    @Override
641    public String getDCRelation() throws AmetysRepositoryException
642    {
643        return DublinCoreHelper.getDCRelation(this);
644    }
645    
646    @Override
647    public String getDCCoverage() throws AmetysRepositoryException
648    {
649        return DublinCoreHelper.getDCCoverage(this, getDCLanguage());
650    }
651    
652    @Override
653    public String getDCRights() throws AmetysRepositoryException
654    {
655        return DublinCoreHelper.getDCRights(this);
656    }
657
658    @Override
659    public ResourceCollection getRootAttachments() throws AmetysRepositoryException
660    {
661        ResourceCollection attachments = null;
662        
663        if (hasChild(ATTACHMENTS_NODE_NAME))
664        {
665            attachments = getChild(ATTACHMENTS_NODE_NAME);
666        }
667        
668        return attachments;
669    }
670    
671    @Override
672    public boolean hasChild(String name) throws AmetysRepositoryException
673    {
674        return _getFactory().hasChild(this, name);
675    }
676    
677    @SuppressWarnings("unchecked")
678    @Override
679    public <A extends AmetysObject> A createChild(String name, String type) throws AmetysRepositoryException, RepositoryIntegrityViolationException
680    {
681        return (A) _getFactory().createChild(this, name, type);
682    }
683    
684    @SuppressWarnings("unchecked")
685    @Override
686    public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException
687    {
688        return (A) _getFactory().getChild(this, path);
689    }
690    
691    @Override
692    public <A extends AmetysObject> AmetysObjectIterable<A> getChildren() throws AmetysRepositoryException
693    {
694        return _getFactory().getChildren(this);
695    }
696
697    public IndexableDataHolder getDataHolder()
698    {
699        // It is necessary to instantiate the repository data because of locks:
700        // when the content is locked, the lock token is released to be taken by anyone
701        // in repository data there is a lock checked boolean that need to be reset in order to take the token
702        // this boolean is only reset at instantiation
703        RepositoryData repositoryData = new JCRRepositoryData(getNode());
704        Collection<ContentType> contentTypes = _getFactory().getContentHelper().getContentTypes(this);
705        return new DefaultModelAwareDataHolder(repositoryData, contentTypes, Optional.empty(), Optional.of(this));
706    }
707    
708    public ModifiableModelLessDataHolder getInternalDataHolder()
709    {
710        // It is necessary to instantiate the repository data because of locks:
711        // when the content is locked, the lock token is released to be taken by anyone
712        // in repository data there is a lock checked boolean that need to be reset in order to take the token
713        // this boolean is only reset at instantiation
714        ModifiableRepositoryData repositoryData = new JCRRepositoryData(getNode(), RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
715        Optional<LockableAmetysObject> lockableAmetysObject = Optional.of(this)
716                                                                      .filter(LockableAmetysObject.class::isInstance)
717                                                                      .map(LockableAmetysObject.class:: cast);
718        return new DefaultModifiableModelLessDataHolder(_getFactory().getInternalDataTypesExtensionPoint(), repositoryData, lockableAmetysObject);
719    }
720    
721    public Optional<SystemPropertyExtensionPoint> getSystemPropertyExtensionPoint()
722    {
723        return Optional.of(_getFactory().getSystemPropertyExtensionPoint());
724    }
725    
726    @Override
727    public void addReaction(UserIdentity user, ReactionType reactionType)
728    {
729        ReactionableObjectHelper.addReaction(getUnversionedDataHolder(), user, reactionType);
730    }
731
732    @Override
733    public void removeReaction(UserIdentity user, ReactionType reactionType)
734    {
735        ReactionableObjectHelper.removeReaction(getUnversionedDataHolder(), user, reactionType);
736    }
737
738    @Override
739    public List<UserIdentity> getReactionUsers(ReactionType reactionType)
740    {
741        return ReactionableObjectHelper.getReactionUsers(getUnversionedDataHolder(), reactionType);
742    }
743
744    public void addReport()
745    {
746        ReportableObjectHelper.addReport(getUnversionedDataHolder());
747    }
748    
749    public void setReportsCount(long reportsCount)
750    {
751        ReportableObjectHelper.setReportsCount(getUnversionedDataHolder(), reportsCount);
752    }
753
754    public void clearReports()
755    {
756        ReportableObjectHelper.clearReports(getUnversionedDataHolder());
757    }
758
759    public long getReportsCount()
760    {
761        return ReportableObjectHelper.getReportsCount(getUnversionedDataHolder());
762    }
763    
764    public void toSAX(ContentHandler contentHandler, Locale locale, View view, boolean saxWorkflowStep) throws SAXException
765    {
766        _getFactory().getContentSaxer().saxContent(this, contentHandler, locale, view, "content", saxWorkflowStep, false, false, "attributes");
767    }
768}