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.time.ZonedDateTime;
019import java.time.format.DateTimeFormatter;
020import java.util.Arrays;
021import java.util.Calendar;
022import java.util.Date;
023import java.util.Optional;
024import java.util.TimeZone;
025
026import org.apache.cocoon.xml.AttributesImpl;
027import org.apache.cocoon.xml.XMLUtils;
028import org.w3c.dom.Element;
029import org.xml.sax.ContentHandler;
030import org.xml.sax.SAXException;
031
032import org.ametys.core.model.type.AbstractDateTimeElementType;
033import org.ametys.core.util.DateUtils;
034import org.ametys.plugins.repository.RepositoryConstants;
035import org.ametys.plugins.repository.data.repositorydata.ModifiableRepositoryData;
036import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
037import org.ametys.plugins.repository.data.type.RepositoryElementType;
038import org.ametys.runtime.model.ViewItem;
039import org.ametys.runtime.model.exception.BadItemTypeException;
040import org.ametys.runtime.model.type.DataContext;
041
042/**
043 * Class for date time type of elements stored in the repository
044 */
045public class DateTimeRepositoryElementType extends AbstractDateTimeElementType implements RepositoryElementType<ZonedDateTime>
046{
047    /**
048     * {@inheritDoc}
049     * The DateTime type retrieves a single or an array of {@link ZonedDateTime}.
050     * To convert a {@link ZonedDateTime} in {@link Date} and keep the {@link TimeZone} information, use the {@link ZonedDateTime#toEpochSecond()} method. 
051     */
052    public Object read(RepositoryData parentData, String name) throws BadItemTypeException
053    {
054        if (!parentData.hasValue(name))
055        {
056            return null;
057        }
058        
059        if (!isCompatible(parentData, name))
060        {
061            throw new BadItemTypeException("Try to get date time value from the non date time data '" + name + "' on '" + parentData + "'");
062        }
063        
064        if (parentData.isMultiple(name))
065        {
066            return Arrays.stream(parentData.getDates(name))
067                         .map(DateUtils::asZonedDateTime)
068                         .toArray(ZonedDateTime[]::new);
069        }
070        else
071        {
072            Calendar date = parentData.getDate(name);
073            return DateUtils.asZonedDateTime(date);
074        }
075    }
076    
077    @Override
078    public boolean hasValue(RepositoryData parentData, String name) throws BadItemTypeException
079    {
080        return RepositoryElementType.super.hasValue(parentData, name) || parentData.hasValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
081    }
082    
083    public boolean hasNonEmptyValue(RepositoryData parentData, String name) throws BadItemTypeException
084    {
085        if (!parentData.hasValue(name))
086        {
087            return false;
088        }
089        
090        if (!isCompatible(parentData, name))
091        {
092            throw new BadItemTypeException("Try to check date time value from the non date time data '" + name + "' on '" + parentData + "'");
093        }
094        
095        if (parentData.isMultiple(name))
096        {
097            return parentData.getDates(name).length > 0;
098        }
099        else
100        {
101            return true;
102        }
103    }
104    
105    public void write(ModifiableRepositoryData parentData, String name, Object value) throws BadItemTypeException
106    {
107        if (value == null)
108        {
109            if (parentData.hasValue(name) && parentData.isMultiple(name))
110            {
111                parentData.setValues(name, new Calendar[0]);
112            }
113            else
114            {
115                parentData.setValue(name + EMPTY_METADATA_SUFFIX, true, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
116
117                if (parentData.hasValue(name))
118                {
119                    parentData.removeValue(name);
120                }
121            }
122        }
123        else if (value instanceof ZonedDateTime)
124        {
125            Calendar date = DateUtils.asCalendar((ZonedDateTime) value);
126            parentData.setValue(name, date);
127            
128            if (parentData.hasValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL))
129            {
130                parentData.removeValue(name + EMPTY_METADATA_SUFFIX, RepositoryConstants.NAMESPACE_PREFIX_INTERNAL);
131            }
132        }
133        else if (value instanceof ZonedDateTime[])
134        {
135            Calendar[] calendars = Arrays.stream((ZonedDateTime[]) value)
136                                         .map(v -> Optional.ofNullable(v)
137                                                           .orElseThrow(() -> new IllegalArgumentException("Try to set a null value into the multiple " + getId() + " data '" + name + "' on '" + parentData + "'")))
138                                         .map(zonedDateTime -> DateUtils.asCalendar(zonedDateTime))
139                                         .toArray(Calendar[]::new);
140
141            parentData.setValues(name, calendars);
142        }
143        else
144        {
145            throw new BadItemTypeException("Try to set the non date time value '" + value + "' to the date time data '" + name + "' on '" + parentData + "'");
146        }
147    }
148    
149    public String getRepositoryDataType()
150    {
151        return RepositoryData.CALENDAR_REPOSITORY_DATA_TYPE;
152    }
153    
154    @Override
155    protected ZonedDateTime _singleValueFromXML(Element element, Optional<Object> additionalData)
156    {
157        String value = element.getTextContent();
158        Optional<DateTimeFormatter> formatter = additionalData.filter(DateTimeFormatter.class::isInstance)
159                                                              .map(DateTimeFormatter.class::cast);
160        
161        return DateUtils.parseZonedDateTime(value, formatter);
162    }
163    
164    @Override
165    protected void _singleValueToSAX(ContentHandler contentHandler, String tagName, ZonedDateTime value, Optional<ViewItem> viewItem, DataContext context, AttributesImpl attributes) throws SAXException
166    {
167        String valueAsString = DateUtils.zonedDateTimeToString(value);
168        XMLUtils.createElement(contentHandler, tagName, attributes, valueAsString);
169    }
170}