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}