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            return !_isStringDataEmpty(parentData, name);
129        }
130        else
131        {
132            return !_isBinaryDataEmpty(parentData, name);
133        }
134    }
135
136    private boolean _isStringDataEmpty(RepositoryData parentData, String name)
137    {
138        if (parentData.isMultiple(name))
139        {
140            return  parentData.getStrings(name).length == 0;
141        }
142        else
143        {
144            String value = parentData.getString(name);
145            return StringUtils.isEmpty(value);
146        }
147    }
148
149    private boolean _isBinaryDataEmpty(RepositoryData parentData, String name)
150    {
151        if (parentData.isMultiple(name))
152        {
153            RepositoryData multipleParentData = parentData.getRepositoryData(name);
154            return multipleParentData.getDataNames().isEmpty();
155        }
156        else
157        {
158            RepositoryData data = parentData.getRepositoryData(name);
159            return ResourceElementTypeHelper.isResourceDataEmpty(data);
160        }
161    }
162
163    public void write(ModifiableRepositoryData parentData, String name, Object value) throws BadItemTypeException
164    {
165        if (value == null)
166        {
167            if (parentData.hasValue(name) && parentData.isMultiple(name))
168            {
169                parentData.setValues(name, new String[0]);
170            }
171            else
172            {
173                parentData.setValue(name, StringUtils.EMPTY);
174            }
175        }
176        else if (value instanceof String)
177        {
178            parentData.setValue(name, (String) value);
179        }
180        else if (value instanceof String[])
181        {
182            String[] values = Arrays.stream((String[]) value)
183                                    .map(v -> Optional.ofNullable(v)
184                                                      .orElseThrow(() -> new IllegalArgumentException("Try to set a null value into the multiple " + getId() + " data '" + name + "' on '" + parentData + "'")))
185                                    .collect(Collectors.toList())
186                                    .toArray(new String[((String[]) value).length]);
187            
188            parentData.setValues(name, values);
189        }
190        else if (value instanceof ExplorerFile)
191        {
192            writeExplorerFile(parentData, name, (ExplorerFile) value);
193        }
194        else if (value instanceof ExplorerFile[])
195        {
196            // FIXME : maybe handle the untouched value
197            String[] values = Arrays.stream((ExplorerFile[]) value)
198                                    .map(file -> Optional.ofNullable(file)
199                                                         .map(ExplorerFile::getResourceId)
200                                                         .orElseThrow(() -> new IllegalArgumentException("Try to set a null value into the multiple " + getId() + " data '" + name + "' on '" + parentData + "'")))
201                                    .collect(Collectors.toList())
202                                    .toArray(new String[((ExplorerFile[]) value).length]);
203            
204            parentData.setValues(name, values);
205        }
206        else if (value instanceof Binary)
207        {
208            _writeSingleBinaryValue(parentData, name, (Binary) value);
209        }
210        else if (value instanceof Binary[])
211        {
212            _writeMultipleBinaryValue(parentData, name, (Binary[]) value);
213        }
214        else
215        {
216            throw new BadItemTypeException("Try to set the non file value '" + value + "' to the file data '" + name + "' on '" + parentData + "'");
217        }
218    }
219    
220    private void writeExplorerFile(ModifiableRepositoryData parentData, String name, ExplorerFile explorerFile)
221    {
222        // FIXME : handle better way the untouched value
223        String resourceId = explorerFile.getResourceId();
224        if (!"untouched".equals(resourceId))
225        {
226            parentData.setValue(name, resourceId);
227        }
228    }
229
230    private void _writeMultipleBinaryValue(ModifiableRepositoryData parentData, String name, Binary[] value)
231    {
232        ModifiableRepositoryData multipleParentData;
233        
234        if (parentData.hasValue(name))
235        {
236            multipleParentData = parentData.getRepositoryData(name);
237        }
238        else
239        {
240            multipleParentData = parentData.addRepositoryData(name, RepositoryConstants.MULTIPLE_ITEM_NODETYPE);
241            // Add the type identifier in a property to know what the type of the data even if there is no value
242            multipleParentData.setValue(ComplexRepositoryElementType.TYPE_ID_DATA_NAME, getId(), RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
243        }
244        
245        for (int i = 0; i < value.length; i++)
246        {
247            if (value[i] != null)
248            {
249                _writeSingleBinaryValue(multipleParentData, String.valueOf(i + 1), value[i]);
250            }
251            else
252            {
253                throw new IllegalArgumentException("Try to set a null value into the multiple " + getId() + " data '" + name + "' on '" + parentData + "'");
254            }
255        }
256    }
257    
258    private void _writeSingleBinaryValue(ModifiableRepositoryData parentData, String name, Binary binary)
259    {
260        if (parentData.hasValue(name))
261        {
262            String dataType = parentData.getType(name);
263            if (dataType.equals(RepositoryData.STRING_REPOSITORY_DATA_TYPE))
264            {
265                // In this case, there are a explorer data (type String) in the repo, so delete it before adding the binary data
266                parentData.removeValue(name);
267            }
268        }
269        
270        ResourceElementTypeHelper.writeSingleBinaryValue(parentData, name, binary);
271    }
272    
273    public String getRepositoryDataType()
274    {
275        return RepositoryData.STRING_REPOSITORY_DATA_TYPE;
276    }
277    
278    public boolean isCompatible(RepositoryData parentData, String name) throws UnknownDataException
279    {
280        if (parentData.hasValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL))
281        {
282            return true;
283        }
284        
285        String dataType = parentData.getType(name);
286        switch (dataType)
287        {
288            case RepositoryData.STRING_REPOSITORY_DATA_TYPE:
289            case RepositoryConstants.BINARY_NODETYPE:
290                return true;
291            case RepositoryConstants.MULTIPLE_ITEM_NODETYPE:
292                RepositoryData multipleItemNode = parentData.getRepositoryData(name);
293                return getId().equals(multipleItemNode.getString(ComplexRepositoryElementType.TYPE_ID_DATA_NAME, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL));
294            default:
295                return false;
296        }
297    }
298}