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;
017
018import java.time.ZoneId;
019import java.time.ZonedDateTime;
020import java.time.format.DateTimeFormatter;
021import java.util.Calendar;
022import java.util.GregorianCalendar;
023import java.util.LinkedHashMap;
024import java.util.Map;
025import java.util.Optional;
026
027import org.apache.cocoon.xml.AttributesImpl;
028import org.apache.cocoon.xml.XMLUtils;
029import org.xml.sax.ContentHandler;
030import org.xml.sax.SAXException;
031
032import org.ametys.cms.data.Binary;
033import org.ametys.cms.data.File;
034import org.ametys.cms.data.Resource;
035import org.ametys.plugins.repository.RepositoryConstants;
036import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
037import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
038import org.ametys.runtime.model.exception.BadItemTypeException;
039
040/**
041 * Helper for resource type of elements stored in the repository
042 */
043public final class ResourceElementTypeHelper
044{
045    /** Prefix to use for resource's metadata */
046    public static final String METADATA_PREFIX = "jcr";
047    
048    /** Mime type metadata identifier */
049    public static final String MIME_TYPE_IDENTIFIER = "mimeType";
050    
051    /** Encoding metadata identifier */
052    public static final String ENCODING_IDENTIFIER = "encoding";
053    
054    /** Last modification date metadata identifier */
055    public static final String LAST_MODIFICATION_DATE_IDENTIFIER = "lastModified";
056    
057    /** Identifier of the data containing the resource's data */
058    public static final String DATA_IDENTIFIER = "data";
059    
060    /** hash metadata identifier */ 
061    public static final String HASH_IDENTIFIER = "hash";
062    
063    /** file name metadata identifier */
064    public static final String FILENAME_IDENTIFIER = "filename";
065    
066    private ResourceElementTypeHelper()
067    {
068        // Empty constructor
069    }
070    
071    /**
072     * Generates SAX events for the given single file
073     * @param contentHandler the {@link ContentHandler} that will receive the SAX events
074     * @param tagName the tag name of the SAX event to generate.
075     * @param file the single file to SAX
076     * @param fileType the type of the file
077     * @throws SAXException if an error occurs during the SAX events generation
078     */
079    public static void singleFileToSAX(ContentHandler contentHandler, String tagName, File file, String fileType) throws SAXException
080    {
081        AttributesImpl attributes = new AttributesImpl();
082        
083        Optional.ofNullable(file.getMimeType()).ifPresent(mimeType -> attributes.addCDATAAttribute("mime-type", mimeType));
084        Optional.ofNullable(file.getLastModificationDate()).ifPresent(lastModificationDate -> attributes.addCDATAAttribute("lastModified", lastModificationDate.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)));
085        
086        attributes.addCDATAAttribute("type", fileType);
087        attributes.addCDATAAttribute("size", String.valueOf(file.getLength()));
088        Optional.ofNullable(file.getPath()).ifPresent(path -> attributes.addCDATAAttribute("path", path));
089        Optional.ofNullable(file.getName()).ifPresent(filename -> attributes.addCDATAAttribute("filename", filename));
090        
091        XMLUtils.createElement(contentHandler, tagName, attributes);
092    }
093    
094    /**
095     * Convert the single file into a JSON object
096     * @param file the file to convert
097     * @param fileType the type of the file
098     * @return The file as JSON
099     */
100    public static Object singleFileToJSON(File file, String fileType)
101    {
102        Map<String, Object> binaryInfos = new LinkedHashMap<>();
103        
104        Optional.ofNullable(file.getMimeType()).ifPresent(mimeType -> binaryInfos.put("mime-type", mimeType));
105        Optional.ofNullable(file.getLastModificationDate()).ifPresent(lastModificationDate -> binaryInfos.put("lastModified", lastModificationDate.format(DateTimeFormatter.ISO_OFFSET_DATE_TIME)));
106
107        binaryInfos.put("size", String.valueOf(file.getLength()));
108        Optional.ofNullable(file.getPath()).ifPresent(path -> binaryInfos.put("path", path));
109        binaryInfos.put("type", fileType);
110        Optional.ofNullable(file.getName()).ifPresent(filename -> binaryInfos.put("filename", filename));
111        
112        return binaryInfos;
113    }
114    
115    /**
116     * Read the binary from the given repository data
117     * @param binaryData the repository data containing the binary's data
118     * @return the read binary
119     */
120    public static Binary readBinaryData(RepositoryData binaryData)
121    {
122        String hash = getStringValue(binaryData, HASH_IDENTIFIER, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
123        Binary binary = new Binary(binaryData, hash);
124        
125        readResourceData(binaryData, binary);
126        Optional.ofNullable(getStringValue(binaryData, FILENAME_IDENTIFIER, RepositoryConstants.NAMESPACE_PREFIX)).ifPresent(binary::setFilename);
127        
128        return binary;
129    }
130    
131    /**
132     * Read the resource from the given repository data
133     * @param resourceData the repository data containing the resource's data
134     * @param resource the resource to read
135     */
136    public static void readResourceData(RepositoryData resourceData, Resource resource)
137    {
138        Optional.ofNullable(getStringValue(resourceData, MIME_TYPE_IDENTIFIER, METADATA_PREFIX)).ifPresent(resource::setMimeType);
139        Optional.ofNullable(getStringValue(resourceData, ENCODING_IDENTIFIER, METADATA_PREFIX)).ifPresent(resource::setEncoding);
140        Optional.ofNullable(getDateValue(resourceData, LAST_MODIFICATION_DATE_IDENTIFIER, METADATA_PREFIX)).ifPresent(resource::setLastModificationDate);
141    }
142    
143    /**
144     * Write the resource in the given repository data
145     * @param parentData the repository data where to write the binary
146     * @param name the name of the element to write
147     * @param value the binary to write
148     */
149    public static void writeSingleBinaryValue(ModifiableRepositoryData parentData, String name, Binary value)
150    {
151        ModifiableRepositoryData binaryData;
152        if (parentData.hasValue(name))
153        {
154            binaryData = getRepositoryData(parentData, RepositoryConstants.BINARY_NODETYPE, name, RepositoryConstants.NAMESPACE_PREFIX);
155        }
156        else
157        {
158            binaryData = parentData.addRepositoryData(name, RepositoryConstants.BINARY_NODETYPE);
159        }
160        
161        if (value != null)
162        {
163            writeResourceData(binaryData, value);
164            Optional.ofNullable(value.getFilename()).ifPresent(filename -> binaryData.setValue(FILENAME_IDENTIFIER, filename, RepositoryConstants.NAMESPACE_PREFIX));
165            
166            if (binaryData.hasValue(HASH_IDENTIFIER, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL))
167            {
168                String currentHash = getStringValue(binaryData, HASH_IDENTIFIER, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
169                if (!currentHash.equals(value.getHash()))
170                {
171                    Optional.ofNullable(value.getHash()).ifPresent(hash -> binaryData.setValue(HASH_IDENTIFIER, hash, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL));
172                    Optional.ofNullable(value.getInputStream()).ifPresent(stream -> binaryData.setValue(DATA_IDENTIFIER, stream, METADATA_PREFIX));
173                }
174            }
175            else
176            {
177                Optional.ofNullable(value.getHash()).ifPresent(hash -> binaryData.setValue(HASH_IDENTIFIER, hash, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL));
178                Optional.ofNullable(value.getInputStream()).ifPresent(stream -> binaryData.setValue(DATA_IDENTIFIER, stream, METADATA_PREFIX));
179            }
180        }
181    }
182
183    /**
184     * Write the resource in the given repository data
185     * @param resourceData the repository data where to write the resource's data
186     * @param value the resource to write
187     */
188    public static void writeResourceData(ModifiableRepositoryData resourceData, Resource value)
189    {
190        Optional.ofNullable(value.getMimeType()).ifPresent(mimeType -> resourceData.setValue(MIME_TYPE_IDENTIFIER, mimeType, METADATA_PREFIX));
191        Optional.ofNullable(value.getEncoding()).ifPresent(encoding -> resourceData.setValue(ENCODING_IDENTIFIER, encoding, METADATA_PREFIX));
192        Optional.ofNullable(value.getLastModificationDate()).ifPresent(lastModificationDate -> resourceData.setValue(LAST_MODIFICATION_DATE_IDENTIFIER, _getCalendarFromZonedDateTime(lastModificationDate), METADATA_PREFIX));
193    }
194    
195    /**
196     * Retrieves the string value from the given repository data
197     * @param repositoryData the repository data containing the data to retrieve
198     * @param name the name of the data to retrieve
199     * @param prefix the prefix of the data to retrieve
200     * @return the string value
201     */
202    public static String getStringValue(RepositoryData repositoryData, String name, String prefix)
203    {
204        if (!repositoryData.hasValue(name, prefix))
205        {
206            return null;
207        }
208        else if (RepositoryData.STRING_REPOSITORY_DATA_TYPE.equals(repositoryData.getType(name, prefix)))
209        {
210            return repositoryData.getString(name, prefix);
211        }
212        else
213        {
214            throw new BadItemTypeException("Try to get string value from the non string data '" + prefix + ":" + name + "' on '" + repositoryData + "'");
215        }
216    }
217    
218    /**
219     * Retrieves the string values from the given repository data
220     * @param repositoryData the repository data containing the data to retrieve
221     * @param name the name of the data to retrieve
222     * @param prefix the prefix of the data to retrieve
223     * @return the string values
224     */
225    public static String[] getStringValues(RepositoryData repositoryData, String name, String prefix)
226    {
227        if (!repositoryData.hasValue(name, prefix))
228        {
229            return null;
230        }
231        else if (RepositoryData.STRING_REPOSITORY_DATA_TYPE.equals(repositoryData.getType(name, prefix)))
232        {
233            return repositoryData.getStrings(name, prefix);
234        }
235        else
236        {
237            throw new BadItemTypeException("Try to get string value from the non string data '" + prefix + ":" + name + "' on '" + repositoryData + "'");
238        }
239    }
240    
241    /**
242     * Retrieves the child repository data from the given repository data
243     * @param <T> Type of the repository data (modifiable or not)
244     * @param parentData the repository data containing the data to retrieve
245     * @param dataTypeName the type of the data to retrieve
246     * @param name the name of the data to retrieve
247     * @param prefix the prefix of the data to retrieve
248     * @return the child repository data
249     */
250    @SuppressWarnings("unchecked")
251    public static <T extends RepositoryData> T getRepositoryData(T parentData, String dataTypeName, String name, String prefix)
252    {
253        if (!parentData.hasValue(name, prefix))
254        {
255            return null;
256        }
257        else if (dataTypeName.equals(parentData.getType(name, prefix)))
258        {
259            return (T) parentData.getRepositoryData(name, prefix);
260        }
261        else
262        {
263            throw new BadItemTypeException("Try to get data of type '" + dataTypeName + "' from the non data '" + prefix + ":" + name + "' on '" + parentData + "'");
264        }
265    }
266    
267    /**
268     * Retrieves the date value from the given repository data
269     * @param repositoryData the repository data containing data to retrieve
270     * @param prefix the prefix of the data to retrieve
271     * @param name the name of the data to retrieve
272     * @return the date value
273     */
274    public static ZonedDateTime getDateValue(RepositoryData repositoryData, String name, String prefix)
275    {
276        if (!repositoryData.hasValue(name, prefix))
277        {
278            return null;
279        }
280        else if (RepositoryData.CALENDAR_REPOSITORY_DATA_TYPE.equals(repositoryData.getType(name, prefix)))
281        {
282            return _getZonedDateTimeFromCalendar(repositoryData.getDate(name, prefix));
283        }
284        else
285        {
286            throw new BadItemTypeException("Try to get date value from the non date data '" + prefix + ":" + name + "' on '" + repositoryData + "'");
287        }
288    }
289    
290    private static ZonedDateTime _getZonedDateTimeFromCalendar(Calendar calendar)
291    {
292        ZonedDateTime zonedDateTime =  ZonedDateTime.of(calendar.get(Calendar.YEAR),
293                                calendar.get(Calendar.MONTH) + 1,
294                                calendar.get(Calendar.DAY_OF_MONTH),
295                                calendar.get(Calendar.HOUR_OF_DAY),
296                                calendar.get(Calendar.MINUTE),
297                                calendar.get(Calendar.SECOND),
298                                calendar.get(Calendar.MILLISECOND),
299                                calendar.getTimeZone().toZoneId());
300        return zonedDateTime.withZoneSameInstant(ZoneId.of("UTC"));
301        
302    }
303    
304    private static Calendar _getCalendarFromZonedDateTime(ZonedDateTime zonedDateTime)
305    {
306        ZonedDateTime dateTimeOnUTC = zonedDateTime.withZoneSameInstant(ZoneId.of("UTC"));
307        return GregorianCalendar.from(dateTimeOnUTC);
308    }
309}