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.ArrayList;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.stream.Collectors;
027
028import org.apache.commons.lang3.StringUtils;
029import org.apache.jackrabbit.util.Text;
030
031import org.ametys.cms.data.type.ResourceElementTypeHelper;
032import org.ametys.plugins.repository.RepositoryConstants;
033import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
034
035/**
036 * CLass representing a rich text
037 */
038public class RichText extends Resource
039{
040    private static final String __ATTACHMENT_CONTENT_IDENTIFIER = "content";
041    
042    private static final Set<String> __AVAILABLE_MIME_TYPES_FOR_RICH_TEXT;
043    private static final Set<String> __AVAILABLE_ENCODINGS_FOR_RICH_TEXT;
044    
045    static
046    {
047        __AVAILABLE_MIME_TYPES_FOR_RICH_TEXT = new HashSet<>();
048        __AVAILABLE_MIME_TYPES_FOR_RICH_TEXT.add("text/xml");
049        __AVAILABLE_MIME_TYPES_FOR_RICH_TEXT.add("application/xml");
050        __AVAILABLE_MIME_TYPES_FOR_RICH_TEXT.add("text/plain");
051        
052        __AVAILABLE_ENCODINGS_FOR_RICH_TEXT = new HashSet<>();
053        __AVAILABLE_ENCODINGS_FOR_RICH_TEXT.add("UTF-8");
054    }
055    
056    /** the rich text's annotations */
057    protected Map<String, List<String>> _annotations = new HashMap<>();
058    
059    /** The resource's repository data */
060    protected RepositoryData _folderData;
061    
062    /** the files contained in the rich text */
063    protected Map<String, NamedResource> _fetchedAttachments = new HashMap<>();
064    
065    /** the files contained in the rich text */
066    protected Collection<String> _removedAttachments = new ArrayList<>();
067    
068    /**
069     * Default constructor
070     */
071    public RichText()
072    {
073        // Empty contructor
074    }
075    
076    /**
077     * Constructor to use when reading the rich text from the repository
078     * @param richTextData the repository data containing the rich text's data
079     * @param folderData the repository data containing the folder's data
080     */
081    public RichText(RepositoryData richTextData, RepositoryData folderData)
082    {
083        super(richTextData);
084        _folderData = folderData;
085    }
086    
087    @Override
088    public void setMimeType(String mimeType)
089    {
090        if (__AVAILABLE_MIME_TYPES_FOR_RICH_TEXT.stream().anyMatch(availableMimeType -> availableMimeType.equalsIgnoreCase(mimeType)))
091        {
092            super.setMimeType(mimeType);
093        }
094        else
095        {
096            String availableMimeTypes = StringUtils.join(__AVAILABLE_MIME_TYPES_FOR_RICH_TEXT, ", ");
097            throw new IllegalArgumentException("The mime type '" + mimeType + "' is not available for rich texts. Available mime types are: '" + availableMimeTypes + "'.");
098        }
099    }
100    
101    @Override
102    public void setEncoding(String encoding)
103    {
104        if (__AVAILABLE_ENCODINGS_FOR_RICH_TEXT.stream().anyMatch(availableEncoding -> availableEncoding.equalsIgnoreCase(encoding)))
105        {
106            super.setEncoding(encoding);
107        }
108        else
109        {
110            String availableEncodings = StringUtils.join(__AVAILABLE_ENCODINGS_FOR_RICH_TEXT, ", ");
111            throw new IllegalArgumentException("The encoding '" + encoding + "' is not available for rich texts. Available encodings are: '" + availableEncodings + "'.");
112        }
113    }
114
115    /**
116     * Retrieves the rich text's annotations
117     * @return the rich text's annotations
118     */
119    public Map<String, List<String>> getAllAnnotations()
120    {
121        return _annotations;
122    }
123
124    /**
125     * Retrieves the rich text's annotations of the given name
126     * @param name the name of the annotations to retrieve
127     * @return the rich text's annotations of the given name
128     */
129    public List<String> getAnnotations(String name)
130    {
131        return _annotations.containsKey(name) ? _annotations.get(name) : new ArrayList<>();
132    }
133
134    /**
135     * Add annotations to the rich text
136     * @param name the name of the annotations to add
137     * @param values annotations to add
138     */
139    public void addAnnotations(String name, String... values)
140    {
141        List<String> allValues = new ArrayList<>();
142        if (_annotations.containsKey(name))
143        {
144            allValues.addAll(_annotations.get(name));
145        }
146        
147        allValues.addAll(Arrays.asList(values));
148        _annotations.put(name, allValues);
149    }
150
151    /**
152     * Removes all rich text's annotations
153     */
154    public void removeAllAnnotations()
155    {
156        _annotations = new HashMap<>();
157    }
158
159    /**
160     * Removes the rich text's annotations of the given name
161     * @param name the name of the annotations to remove
162     */
163    public void removeAnnotations(String name)
164    {
165        _annotations.remove(name);
166    }
167    
168    /**
169     * Retrieves the names of the rich text's attachments
170     * @return the names of the rich text's attachments
171     */
172    public Collection<String> getAttachmentNames()
173    {
174        Collection<String> attachnmentNames = new HashSet<>();
175        attachnmentNames.addAll(_fetchedAttachments.keySet());
176        
177        if (_folderData != null)
178        {
179            attachnmentNames.addAll(_folderData.getDataNames(StringUtils.EMPTY).stream()
180                    .map(Text::unescapeIllegalJcrChars)
181                    .filter(name -> !_fetchedAttachments.containsKey(name))
182                    .filter(name -> !_removedAttachments.contains(name))
183                    .collect(Collectors.toSet()));
184        }
185        
186        return attachnmentNames;
187    }
188    
189    /**
190     * Retrieves the rich text's attachments
191     * @return the rich text's attachments
192     */
193    public Collection<NamedResource> getAttachments()
194    {
195        _fetchAttachments();
196        return _fetchedAttachments.values();
197    }
198    
199    private void _fetchAttachments()
200    {
201        for (String filename : getAttachmentNames())
202        {
203            _fetchAttachment(filename);
204        }
205    }
206    
207    /**
208     * Retrieves the attachments that have already been fetched  
209     * @return the fetched attachments
210     */
211    public Collection<NamedResource> getFetchedAttachments()
212    {
213        return _fetchedAttachments.values();
214    }
215    
216    /**
217     * Retrieves the names of the removed attachments of the rich text
218     * @return the names of the removed attachments
219     */
220    public Collection<String> getRemovedAttachments()
221    {
222        return _removedAttachments;
223    }
224    
225    /**
226     * Checks if the rich text has an attachment with the given name
227     * @param filename the name of the attachment
228     * @return <code>true</code> if the rich text has an attachment with the given name, <code>false</code> otherwise
229     */
230    public boolean hasAttachment(String filename)
231    {
232        return getAttachmentNames().contains(filename);
233    }
234
235    /**
236     * Retrieves the rich text's attachment with the given name, or <code>null</code> if there is no such attachment
237     * @param filename the name of the attachment to retrieve
238     * @return the rich text's attachment with the given name
239     */
240    public NamedResource getAttachment(String filename)
241    {
242        _fetchAttachment(filename);
243        return _fetchedAttachments.get(filename);
244    }
245    
246    private void _fetchAttachment(String filename)
247    {
248        if (!_fetchedAttachments.containsKey(filename) && _folderData != null)
249        {
250            String escapedFilename = Text.escapeIllegalJcrChars(filename);
251            RepositoryData attachmentData = ResourceElementTypeHelper.getRepositoryData(_folderData, RepositoryConstants.FILE_NODETYPE, escapedFilename, StringUtils.EMPTY);
252            
253            if (attachmentData != null) // The attachment to fetch could be missing in the repository
254            {
255                RepositoryData resourceData = ResourceElementTypeHelper.getRepositoryData(attachmentData, RepositoryConstants.RESOURCE_NODETYPE, __ATTACHMENT_CONTENT_IDENTIFIER, ResourceElementTypeHelper.METADATA_PREFIX);
256                
257                NamedResource attachment = new NamedResource(resourceData);
258                ResourceElementTypeHelper.readResourceData(resourceData, attachment);
259                attachment.setFilename(filename);
260                
261                _fetchedAttachments.put(filename, attachment);
262            }
263        }
264    }
265    
266    /**
267     * Adds an attachment to the rich text
268     * @param attachment the attachment to add
269     */
270    public void addAttachment(NamedResource attachment)
271    {
272        String filename = attachment.getFilename();
273        
274        _fetchedAttachments.put(filename, attachment);
275        
276        if (_removedAttachments.contains(filename))
277        {
278            _removedAttachments.remove(filename);
279        }
280    }
281
282    /**
283     * Remove an attachment from the rich text
284     * @param filename the name of the attachment to remove
285     */
286    public void removeAttachment(String filename)
287    {
288        _removedAttachments.add(filename);
289        _fetchedAttachments.remove(filename);
290    }
291    
292    /**
293     * Remove all the attachments from the rich text
294     */
295    public void removeAttachments()
296    {
297        _removedAttachments.addAll(getAttachmentNames());
298        _fetchedAttachments = new HashMap<>();
299    }
300}