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.type.impl;
017
018import java.io.IOException;
019import java.io.InputStream;
020import java.util.ArrayList;
021import java.util.List;
022import java.util.Map;
023import java.util.Optional;
024
025import org.apache.commons.lang3.StringUtils;
026import org.apache.jackrabbit.util.Text;
027
028import org.ametys.cms.data.NamedResource;
029import org.ametys.cms.data.RichText;
030import org.ametys.cms.data.type.AbstractRichTextElementType;
031import org.ametys.cms.data.type.ResourceElementTypeHelper;
032import org.ametys.plugins.repository.AmetysRepositoryException;
033import org.ametys.plugins.repository.RepositoryConstants;
034import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
035import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
036import org.ametys.plugins.repository.data.type.ComplexRepositoryElementType;
037import org.ametys.runtime.model.type.DataContext;
038
039/**
040 * Class for rich text type of elements stored in the repository
041 */
042public class RichTextRepositoryElementType extends AbstractRichTextElementType implements ComplexRepositoryElementType<RichText>
043{
044    private static final String __ANNOTATIONS_IDENTIFIER = "annotations";
045    private static final String __FOLDER_IDENTIFIER = "data";
046    private static final String __FILE_CONTENT_IDENTIFIER = "content";
047    
048    public boolean isSingleValueEmpty(RepositoryData singleValueData)
049    {
050        return ResourceElementTypeHelper.isResourceDataEmpty(singleValueData);
051    }
052
053    public RichText readSingleValue(RepositoryData singleValueData)
054    {
055        RepositoryData folderData = ResourceElementTypeHelper.getRepositoryData(singleValueData, RepositoryConstants.FOLDER_NODETYPE, __FOLDER_IDENTIFIER, StringUtils.EMPTY);
056        
057        RichText richText = new RichText(singleValueData, folderData);
058        ResourceElementTypeHelper.readResourceData(singleValueData, richText);
059
060        // Read annotations
061        if (singleValueData.hasValue(__ANNOTATIONS_IDENTIFIER, StringUtils.EMPTY))
062        {
063            RepositoryData annotationsData = ResourceElementTypeHelper.getRepositoryData(singleValueData, RepositoryConstants.COMPOSITE_METADTA_NODETYPE, __ANNOTATIONS_IDENTIFIER, StringUtils.EMPTY);
064            for (String annotationName : annotationsData.getDataNames())
065            {
066                String[] annotationValues = ResourceElementTypeHelper.getStringValues(annotationsData, annotationName, RepositoryConstants.NAMESPACE_PREFIX);
067                if (annotationValues != null && annotationValues.length > 0)
068                {
069                    richText.addAnnotations(annotationName, annotationValues);
070                }
071            }
072        }
073
074        return richText;
075    }
076    
077    public void emptySingleValue(ModifiableRepositoryData parentData, String name)
078    {
079        ResourceElementTypeHelper.emptyResourceData(parentData, name, getRepositoryDataType());
080
081        // Remove attachments (because it could be big)
082        ModifiableRepositoryData richTextData = parentData.hasValue(name) ? parentData.getRepositoryData(name) : parentData.addRepositoryData(name, getRepositoryDataType());
083        if (richTextData.hasValue(__FOLDER_IDENTIFIER, StringUtils.EMPTY))
084        {
085            ModifiableRepositoryData folderData = ResourceElementTypeHelper.getRepositoryData(richTextData, RepositoryConstants.FOLDER_NODETYPE, __FOLDER_IDENTIFIER, StringUtils.EMPTY);
086            for (String attachmentNodeName : folderData.getDataNames(StringUtils.EMPTY))
087            {
088                folderData.removeValue(attachmentNodeName, StringUtils.EMPTY);
089            }
090        }
091    }
092
093    public void writeSingleValue(ModifiableRepositoryData parentData, String name, RichText value)
094    {
095        InputStream stream = value.getInputStream();
096        if (stream == null)
097        {
098            emptySingleValue(parentData, name);
099        }
100        else
101        {
102            ModifiableRepositoryData richTextData;
103            if (parentData.hasValue(name))
104            {
105                richTextData = ResourceElementTypeHelper.getRepositoryData(parentData, getRepositoryDataType(), name, RepositoryConstants.NAMESPACE_PREFIX);
106            }
107            else
108            {
109                richTextData = parentData.addRepositoryData(name, getRepositoryDataType());
110            }
111            
112            richTextData.setValue(ResourceElementTypeHelper.EMPTY_RESOURCE_IDENTIFIER, false, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
113
114            ResourceElementTypeHelper.writeResourceData(richTextData, value);
115            richTextData.setValue(ResourceElementTypeHelper.DATA_IDENTIFIER, stream, ResourceElementTypeHelper.METADATA_PREFIX);
116
117            _writeAnnotations(richTextData, value.getAllAnnotations());
118            _writeAttachments(richTextData, value);
119        }
120    }
121    
122    private void _writeAnnotations(ModifiableRepositoryData richTextData, Map<String, List<String>> annotations)
123    {
124        if (richTextData.hasValue(__ANNOTATIONS_IDENTIFIER, StringUtils.EMPTY))
125        {
126            richTextData.removeValue(__ANNOTATIONS_IDENTIFIER, StringUtils.EMPTY);
127        }
128        
129        ModifiableRepositoryData annotationsData = richTextData.addRepositoryData(__ANNOTATIONS_IDENTIFIER, RepositoryConstants.COMPOSITE_METADTA_NODETYPE, StringUtils.EMPTY);
130        for (Map.Entry<String, List<String>> annotationEntry : annotations.entrySet())
131        {
132            List<String> annotationValues = annotationEntry.getValue();
133            annotationsData.setValues(annotationEntry.getKey(), annotationValues.toArray(new String[annotationValues.size()]));
134        }
135    }
136    
137    private void _writeAttachments(ModifiableRepositoryData richTextData, RichText richText)
138    {
139        ModifiableRepositoryData folderData;
140        if (richTextData.hasValue(__FOLDER_IDENTIFIER, StringUtils.EMPTY))
141        {
142            folderData = ResourceElementTypeHelper.getRepositoryData(richTextData, RepositoryConstants.FOLDER_NODETYPE, __FOLDER_IDENTIFIER, StringUtils.EMPTY);
143        }
144        else
145        {
146            folderData = richTextData.addRepositoryData(__FOLDER_IDENTIFIER, RepositoryConstants.FOLDER_NODETYPE, StringUtils.EMPTY);
147        }
148        
149        // Fetched attachments
150        for (NamedResource attachment : richText.getFetchedAttachments())
151        {
152            String escapedFilename = Text.escapeIllegalJcrChars(attachment.getFilename());
153            ModifiableRepositoryData resourceData = _getFileResourceRepositoryData(folderData, escapedFilename);
154            
155            ResourceElementTypeHelper.writeResourceData(resourceData, attachment);
156            Optional.ofNullable(attachment.getInputStream()).ifPresent(stream -> resourceData.setValue(ResourceElementTypeHelper.DATA_IDENTIFIER, stream, ResourceElementTypeHelper.METADATA_PREFIX));
157        }
158        
159        // Removed attachments
160        for (String filename : richText.getRemovedAttachments())
161        {
162            String escapedFilename = Text.escapeIllegalJcrChars(filename);
163            if (folderData.hasValue(escapedFilename, StringUtils.EMPTY))
164            {
165                folderData.removeValue(escapedFilename, StringUtils.EMPTY);
166            }
167        }
168        
169        // Add unfetched attachments
170        // Ex: when copying a rich text from a data holder to another, the untouched attachments are not yet in the destination repository data 
171        for (String filename : richText.getAttachmentNames())
172        {
173            String escapedFilename = Text.escapeIllegalJcrChars(filename);
174            if (!folderData.hasValue(escapedFilename, StringUtils.EMPTY))
175            {
176                NamedResource attachment = richText.getAttachment(filename);
177                ModifiableRepositoryData resourceData = _getFileResourceRepositoryData(folderData, escapedFilename);
178                
179                ResourceElementTypeHelper.writeResourceData(resourceData, attachment);
180                Optional.ofNullable(attachment.getInputStream()).ifPresent(stream -> resourceData.setValue(ResourceElementTypeHelper.DATA_IDENTIFIER, stream, ResourceElementTypeHelper.METADATA_PREFIX));
181            }
182        }
183    }
184    
185    private ModifiableRepositoryData _getFileResourceRepositoryData(ModifiableRepositoryData folderData, String filename)
186    {
187        if (folderData.hasValue(filename, StringUtils.EMPTY))
188        {
189            ModifiableRepositoryData fileData = ResourceElementTypeHelper.getRepositoryData(folderData, RepositoryConstants.FILE_NODETYPE, filename, StringUtils.EMPTY);
190            if (fileData.hasValue(__FILE_CONTENT_IDENTIFIER, ResourceElementTypeHelper.METADATA_PREFIX))
191            {
192                return ResourceElementTypeHelper.getRepositoryData(fileData, RepositoryConstants.RESOURCE_NODETYPE, __FILE_CONTENT_IDENTIFIER, ResourceElementTypeHelper.METADATA_PREFIX);
193            }
194            else
195            {
196                return fileData.addRepositoryData(__FILE_CONTENT_IDENTIFIER, RepositoryConstants.RESOURCE_NODETYPE, ResourceElementTypeHelper.METADATA_PREFIX);
197            }
198        }
199        else
200        {
201            ModifiableRepositoryData fileData = folderData.addRepositoryData(filename, RepositoryConstants.FILE_NODETYPE, StringUtils.EMPTY);
202            return fileData.addRepositoryData(__FILE_CONTENT_IDENTIFIER, RepositoryConstants.RESOURCE_NODETYPE, ResourceElementTypeHelper.METADATA_PREFIX);
203        }
204    }
205    
206    public Object externalizableValueToSAX(Object value, DataContext context)
207    {
208        if (value == null)
209        {
210            return null;
211        }
212        else if (value instanceof RichText)
213        {
214            return _richTextAsString((RichText) value, context);
215        }
216        else if (value instanceof RichText[])
217        {
218            List<Object> jsonList = new ArrayList<>();
219            for (RichText singleValue : (RichText[]) value)
220            {
221                jsonList.add(_richTextAsString(singleValue, context));
222            }
223            return jsonList;
224        }
225        else
226        {
227            throw new IllegalArgumentException("Try to convert the non " + getManagedClass().getName() + " value '" + value + "' to JSON");
228        }
229    }
230    
231    private String _richTextAsString (RichText richText, DataContext context)
232    {
233        StringBuilder result = new StringBuilder(2048);
234        
235        try
236        {
237            _richTextTransformer.transformForEditing(richText, context, result);
238        }
239        catch (IOException e)
240        {
241            throw new AmetysRepositoryException("Unable to transform a rich text into a string", e);
242        }
243        
244        return result.toString();
245    }
246    
247    @Override
248    public boolean removeValueBeforeWritingIt()
249    {
250        return false;
251    }
252
253    public String getRepositoryDataType()
254    {
255        return RepositoryConstants.RICH_TEXT_NODETYPE;
256    }
257}