001/*
002 *  Copyright 2018 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.time.LocalDate;
020import java.time.ZoneId;
021import java.time.ZonedDateTime;
022import java.util.Arrays;
023import java.util.Calendar;
024import java.util.Optional;
025
026import javax.xml.transform.TransformerException;
027
028import org.apache.cocoon.xml.AttributesImpl;
029import org.apache.cocoon.xml.XMLUtils;
030import org.w3c.dom.Element;
031import org.xml.sax.ContentHandler;
032import org.xml.sax.SAXException;
033
034import org.ametys.core.model.type.AbstractDateElementType;
035import org.ametys.core.util.DateUtils;
036import org.ametys.plugins.repository.RepositoryConstants;
037import org.ametys.plugins.repository.data.UnknownDataException;
038import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
039import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
040import org.ametys.plugins.repository.data.type.RepositoryElementType;
041import org.ametys.runtime.model.ViewItem;
042import org.ametys.runtime.model.exception.BadItemTypeException;
043import org.ametys.runtime.model.type.DataContext;
044
045/**
046 * Class for date type of elements stored in the repository
047 */
048public class DateRepositoryElementType extends AbstractDateElementType implements RepositoryElementType<LocalDate>
049{
050    public Object read(RepositoryData parentData, String name) throws BadItemTypeException
051    {
052        if (!parentData.hasValue(name))
053        {
054            return null;
055        }
056        
057        if (!isCompatible(parentData, name))
058        {
059            throw new BadItemTypeException("Try to get date value from the non date data '" + name + "' on '" + parentData + "'");
060        }
061        
062        if (parentData.isMultiple(name))
063        {
064            return Arrays.stream(parentData.getDates(name))
065                         .map(DateUtils::asLocalDate)
066                         .toArray(LocalDate[]::new);
067        }
068        else
069        {
070            Calendar date = parentData.getDate(name);
071            return DateUtils.asLocalDate(date);
072        }
073    }
074    
075    @Override
076    public boolean hasValue(RepositoryData parentData, String name) throws BadItemTypeException
077    {
078        return RepositoryElementType.super.hasValue(parentData, name) || parentData.hasValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
079    }
080    
081    public boolean hasNonEmptyValue(RepositoryData parentData, String name) throws BadItemTypeException
082    {
083        if (!parentData.hasValue(name))
084        {
085            return false;
086        }
087        
088        if (!isCompatible(parentData, name))
089        {
090            throw new BadItemTypeException("Try to check date value from the non date data '" + name + "' on '" + parentData + "'");
091        }
092        
093        if (parentData.isMultiple(name))
094        {
095            return parentData.getDates(name).length > 0;
096        }
097        else
098        {
099            return true;
100        }
101    }
102    
103    public void write(ModifiableRepositoryData parentData, String name, Object value) throws BadItemTypeException
104    {
105        if (value == null)
106        {
107            if (parentData.hasValue(name) && parentData.isMultiple(name))
108            {
109                parentData.setValues(name, new Calendar[0]);
110            }
111            else
112            {
113                parentData.setValue(name + EMPTY_METADATA_SUFFIX, true, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
114
115                if (parentData.hasValue(name))
116                {
117                    parentData.removeValue(name);
118                }
119            }
120        }
121        else if (value instanceof LocalDate)
122        {
123            Calendar date = DateUtils.asCalendar((LocalDate) value);
124            parentData.setValue(name, date);
125            
126            if (parentData.hasValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL))
127            {
128                parentData.removeValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
129            }
130        }
131        else if (value instanceof LocalDate[])
132        {
133            Calendar[] calendars = Arrays.stream((LocalDate[]) value)
134                                         .map(v -> Optional.ofNullable(v)
135                                                           .orElseThrow(() -> new IllegalArgumentException("Try to set a null value into the multiple " + getId() + " data '" + name + "' on '" + parentData + "'")))
136                                         .map(localDate -> DateUtils.asCalendar(localDate))
137                                         .toArray(Calendar[]::new);
138            
139            parentData.setValues(name, calendars);
140        }
141        else
142        {
143            throw new BadItemTypeException("Try to set the non date value '" + value + "' to the date data '" + name + "' on '" + parentData + "'");
144        }
145    }
146    
147    @Override
148    public void remove(ModifiableRepositoryData parentData, String name) throws UnknownDataException
149    {
150        if (parentData.hasValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL))
151        {
152            parentData.removeValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
153
154            if (parentData.hasValue(name))
155            {
156                // If there is an empty metadata AND a value, delete this value. Should never happen
157                parentData.removeValue(name);
158            }
159        }
160        else
161        {
162            RepositoryElementType.super.remove(parentData, name);
163        }
164    }
165
166    public boolean isCompatible(RepositoryData parentData, String name) throws UnknownDataException
167    {
168        if (parentData.hasValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL))
169        {
170            return true;
171        }
172        
173        if (!parentData.hasValue(name))
174        {
175            throw new UnknownDataException("No data found for '" + name + "' in repository data named '" + parentData.getName() + "'.");
176        }
177        
178        if (getRepositoryDataType().equals(parentData.getType(name)))
179        {
180            if (parentData.isMultiple(name))
181            {
182                Calendar[] calendars = parentData.getDates(name);
183                for (Calendar calendar : calendars)
184                {
185                    if (!_isDate(calendar))
186                    {
187                        // If there is at least one datetime, it could not be a data of type date
188                        return false;
189                    }
190                }
191                
192                // No date is a datetime, so it could be a data of type date
193                return true;
194            }
195            else
196            {
197                return _isDate(parentData.getDate(name));
198            }
199        }
200        else
201        {
202            return false;
203        }
204    }
205    
206    private boolean _isDate(Calendar calendar)
207    {
208        return calendar.get(Calendar.HOUR_OF_DAY) == 0
209            && calendar.get(Calendar.MINUTE) == 0
210            && calendar.get(Calendar.SECOND) == 0
211            && calendar.get(Calendar.MILLISECOND) == 0;
212    }
213    
214    public String getRepositoryDataType()
215    {
216        return RepositoryData.CALENDAR_REPOSITORY_DATA_TYPE;
217    }
218    
219    @Override
220    protected LocalDate _singleValueFromXML(Element element, Optional<Object> additionalData) throws TransformerException, IOException
221    {
222        String value = element.getTextContent();
223        ZonedDateTime zdt = DateUtils.parseZonedDateTime(value);
224        return zdt != null ? zdt.toLocalDate() : null;
225    }
226    
227    @Override
228    protected void _singleValueToSAX(ContentHandler contentHandler, String tagName, LocalDate value, Optional<ViewItem> viewItem, DataContext context, AttributesImpl attributes) throws SAXException, IOException
229    {
230        ZonedDateTime zdt = value.atStartOfDay(ZoneId.systemDefault());
231        
232        // CMS-10434 : Use zoned date times and DateUtils parser for legacy compliance with SAX events generated by the old attributes API 
233        String valueAsString = DateUtils.zonedDateTimeToString(zdt);
234        XMLUtils.createElement(contentHandler, tagName, attributes, valueAsString);
235    }
236}