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.util.Arrays;
019import java.util.Optional;
020import java.util.stream.Collectors;
021
022import javax.jcr.Session;
023
024import org.apache.commons.lang3.StringUtils;
025
026import org.ametys.cms.data.Binary;
027import org.ametys.cms.data.ExplorerFile;
028import org.ametys.cms.data.File;
029import org.ametys.cms.data.type.AbstractFileElementType;
030import org.ametys.cms.data.type.ResourceElementTypeHelper;
031import org.ametys.plugins.repository.AmetysRepositoryException;
032import org.ametys.plugins.repository.RepositoryConstants;
033import org.ametys.plugins.repository.data.UnknownDataException;
034import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
035import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
036import org.ametys.plugins.repository.data.repositorydata.impl.JCRRepositoryData;
037import org.ametys.plugins.repository.data.type.ComplexRepositoryElementType;
038import org.ametys.plugins.repository.data.type.RepositoryElementType;
039import org.ametys.runtime.model.exception.BadItemTypeException;
040
041/**
042 * Class for file type of elements stored in the repository
043 */
044public class FileRepositoryElementType extends AbstractFileElementType implements RepositoryElementType<File>
045{
046    public Object read(RepositoryData parentData, String name) throws BadItemTypeException, AmetysRepositoryException
047    {
048        if (!parentData.hasValue(name))
049        {
050            return null;
051        }
052        
053        if (!isCompatible(parentData, name))
054        {
055            throw new BadItemTypeException("Try to get file value from the non file data '" + name + "' on '" + parentData + "'");
056        }
057        
058        if (RepositoryData.STRING_REPOSITORY_DATA_TYPE.equals(parentData.getType(name)))
059        {
060            return _readFileFromStringData(parentData, name);
061        }
062        else
063        {
064            return _readFileFromBinaryData(parentData, name);
065        }
066    }
067
068    private Object _readFileFromStringData(RepositoryData parentData, String name)
069    {
070        final Session session =  parentData instanceof JCRRepositoryData ? ((JCRRepositoryData) parentData).getSession() : null;
071        
072        if (parentData.isMultiple(name))
073        {
074            String[] values = parentData.getStrings(name);
075            return Arrays.stream(values)
076                         .map(resourceId -> new ExplorerFile(_resolver, resourceId, session))
077                         .collect(Collectors.toList())
078                         .toArray(new ExplorerFile[values.length]);
079        }
080        else
081        {
082            String value = parentData.getString(name);
083            return StringUtils.isNotEmpty(value) ? new ExplorerFile(_resolver, value, session) : null;
084        }
085    }
086
087    private Object _readFileFromBinaryData(RepositoryData parentData, String name)
088    {
089        if (parentData.isMultiple(name))
090        {
091            RepositoryData multipleParentData = parentData.getRepositoryData(name);
092            
093            return multipleParentData.getDataNames()
094                                     .stream()
095                                     .map(singleDataName -> multipleParentData.getRepositoryData(singleDataName))
096                                     .map(singleData -> ResourceElementTypeHelper.readBinaryData(singleData))
097                                     .filter(singleValue -> singleValue != null)
098                                     .toArray(Binary[]::new);
099        }
100        else
101        {
102            RepositoryData data = parentData.getRepositoryData(name);
103            if (!ResourceElementTypeHelper.isResourceDataEmpty(data))
104            {
105                return ResourceElementTypeHelper.readBinaryData(data);
106            }
107            else
108            {
109                return null;
110            }
111        }
112    }
113    
114    public boolean hasNonEmptyValue(RepositoryData parentData, String name) throws BadItemTypeException
115    {
116        if (!parentData.hasValue(name))
117        {
118            return false;
119        }
120        
121        if (!isCompatible(parentData, name))
122        {
123            throw new BadItemTypeException("Try to check file value from the non file data '" + name + "' on '" + parentData + "'");
124        }
125        
126        if (RepositoryData.STRING_REPOSITORY_DATA_TYPE.equals(parentData.getType(name)))
127        {
128            if (parentData.isMultiple(name))
129            {
130                return parentData.getStrings(name).length > 0;
131            }
132            else
133            {
134                return StringUtils.isNotEmpty(parentData.getString(name));
135            }
136        }
137        else
138        {
139            // There is a non string value, it cannot be empty
140            return true;
141        }
142    }
143
144    public void write(ModifiableRepositoryData parentData, String name, Object value) throws BadItemTypeException
145    {
146        if (value == null)
147        {
148            if (parentData.hasValue(name) && parentData.isMultiple(name))
149            {
150                parentData.setValues(name, new String[0]);
151            }
152            else
153            {
154                parentData.setValue(name, StringUtils.EMPTY);
155            }
156        }
157        else if (value instanceof String)
158        {
159            parentData.setValue(name, (String) value);
160        }
161        else if (value instanceof String[])
162        {
163            String[] values = Arrays.stream((String[]) value)
164                                    .map(v -> Optional.ofNullable(v)
165                                                      .orElseThrow(() -> new IllegalArgumentException("Try to set a null value into the multiple " + getId() + " data '" + name + "' on '" + parentData + "'")))
166                                    .collect(Collectors.toList())
167                                    .toArray(new String[((String[]) value).length]);
168            
169            parentData.setValues(name, values);
170        }
171        else if (value instanceof ExplorerFile)
172        {
173            writeExplorerFile(parentData, name, (ExplorerFile) value);
174        }
175        else if (value instanceof Binary)
176        {
177            _writeSingleBinaryValue(parentData, name, (Binary) value);
178        }
179        else if (value instanceof File[])
180        {
181            if (((File[]) value).length == 0)
182            {
183                parentData.setValues(name, new String[0]);
184            }
185            else if (value instanceof ExplorerFile[])
186            {
187                // FIXME : maybe handle the untouched value
188                String[] values = Arrays.stream((ExplorerFile[]) value)
189                                        .map(file -> Optional.ofNullable(file)
190                                                             .map(ExplorerFile::getResourceId)
191                                                             .orElseThrow(() -> new IllegalArgumentException("Try to set a null value into the multiple " + getId() + " data '" + name + "' on '" + parentData + "'")))
192                                        .collect(Collectors.toList())
193                                        .toArray(new String[((ExplorerFile[]) value).length]);
194                
195                parentData.setValues(name, values);
196            }
197            else if (value instanceof Binary[])
198            {
199                _writeMultipleBinaryValue(parentData, name, (Binary[]) value);
200            }
201        }
202        else
203        {
204            throw new BadItemTypeException("Try to set the non file value '" + value + "' to the file data '" + name + "' on '" + parentData + "'");
205        }
206    }
207    
208    private void writeExplorerFile(ModifiableRepositoryData parentData, String name, ExplorerFile explorerFile)
209    {
210        // FIXME : handle better way the untouched value
211        String resourceId = explorerFile.getResourceId();
212        if (!"untouched".equals(resourceId))
213        {
214            parentData.setValue(name, resourceId);
215        }
216    }
217
218    private void _writeMultipleBinaryValue(ModifiableRepositoryData parentData, String name, Binary[] value)
219    {
220        ModifiableRepositoryData multipleParentData = null;
221        if (parentData.hasValue(name))
222        {
223            if (parentData.getType(name).equals(RepositoryConstants.MULTIPLE_ITEM_NODETYPE))
224            {
225                multipleParentData = parentData.getRepositoryData(name);
226            }
227            else
228            {
229                // The value was stored as String, remove the old property
230                parentData.removeValue(name);
231            }
232        }
233        
234        if (multipleParentData == null)
235        {
236            multipleParentData = parentData.addRepositoryData(name, RepositoryConstants.MULTIPLE_ITEM_NODETYPE);
237            // Add the type identifier in a property to know what the type of the data even if there is no value
238            multipleParentData.setValue(ComplexRepositoryElementType.TYPE_ID_DATA_NAME, getId(), RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
239        }
240        
241        // Loop on all given values to replace the existing ones
242        for (int i = 0; i < value.length; i++)
243        {
244            if (value[i] != null)
245            {
246                _writeSingleBinaryValue(multipleParentData, String.valueOf(i + 1), value[i]);
247            }
248            else
249            {
250                throw new IllegalArgumentException("Try to set a null value into the multiple " + getId() + " data '" + name + "' on '" + parentData + "'");
251            }
252        }
253        
254        // Remove existing entries from the last one to the last of given values
255        for (int i = multipleParentData.getDataNames().size(); i > value.length; i--)
256        {
257            multipleParentData.removeValue(String.valueOf(i));
258        }
259    }
260    
261    private void _writeSingleBinaryValue(ModifiableRepositoryData parentData, String name, Binary binary)
262    {
263        if (parentData.hasValue(name))
264        {
265            String dataType = parentData.getType(name);
266            if (dataType.equals(RepositoryData.STRING_REPOSITORY_DATA_TYPE))
267            {
268                // In this case, there are a explorer data (type String) in the repo, so delete it before adding the binary data
269                parentData.removeValue(name);
270            }
271        }
272        
273        ResourceElementTypeHelper.writeSingleBinaryValue(parentData, name, binary);
274    }
275    
276    public String getRepositoryDataType()
277    {
278        return RepositoryData.STRING_REPOSITORY_DATA_TYPE;
279    }
280    
281    public boolean isCompatible(RepositoryData parentData, String name) throws UnknownDataException
282    {
283        if (parentData.hasValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL))
284        {
285            return true;
286        }
287        
288        String dataType = parentData.getType(name);
289        switch (dataType)
290        {
291            case RepositoryData.STRING_REPOSITORY_DATA_TYPE:
292            case RepositoryConstants.BINARY_NODETYPE:
293                return true;
294            case RepositoryConstants.MULTIPLE_ITEM_NODETYPE:
295                RepositoryData multipleItemNode = parentData.getRepositoryData(name);
296                return getId().equals(multipleItemNode.getString(ComplexRepositoryElementType.TYPE_ID_DATA_NAME, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL));
297            default:
298                return false;
299        }
300    }
301}