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.Collection;
019import java.util.Optional;
020
021import org.apache.cocoon.xml.AttributesImpl;
022import org.apache.cocoon.xml.XMLUtils;
023import org.apache.commons.lang3.math.NumberUtils;
024import org.xml.sax.Attributes;
025import org.xml.sax.ContentHandler;
026import org.xml.sax.SAXException;
027
028import org.ametys.cms.data.holder.impl.IndexableDataHolderHelper;
029import org.ametys.core.model.type.AbstractModelItemType;
030import org.ametys.core.model.type.ModelItemTypeHelper;
031import org.ametys.plugins.repository.RepositoryConstants;
032import org.ametys.plugins.repository.data.UnknownDataException;
033import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeater;
034import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeaterEntry;
035import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
036import org.ametys.plugins.repository.data.type.RepositoryModelItemGroupType;
037import org.ametys.plugins.repository.model.RepeaterDefinition;
038import org.ametys.runtime.model.ModelItemContainer;
039import org.ametys.runtime.model.ModelViewItemGroup;
040import org.ametys.runtime.model.ViewItem;
041import org.ametys.runtime.model.ViewItemAccessor;
042import org.ametys.runtime.model.type.DataContext;
043
044/**
045 * Class for repeater type
046 */
047public class RepeaterRepositoryModelItemType extends AbstractModelItemType implements RepositoryModelItemGroupType
048{
049    public void valueToSAXForEdition(ContentHandler contentHandler, String tagName, Object value, Optional<ViewItem> viewItem, DataContext context) throws SAXException
050    {
051        _valueToSAX(contentHandler, tagName, value, viewItem, context, true);
052    }
053    
054    public void valueToSAX(ContentHandler contentHandler, String tagName, Object value, Optional<ViewItem> viewItem, DataContext context) throws SAXException
055    {
056        _valueToSAX(contentHandler, tagName, value, viewItem, context, false);
057    }
058    
059    private void _valueToSAX(ContentHandler contentHandler, String tagName, Object value, Optional<ViewItem> viewItem, DataContext context, boolean isEdition) throws SAXException
060    {
061        AttributesImpl attributes = ModelItemTypeHelper.getXMLAttributesFromDataContext(context);
062        
063        if (value == null)
064        {
065            XMLUtils.createElement(contentHandler, tagName, attributes);
066        }
067        else if (value instanceof ModelAwareRepeater repeater)
068        {
069            attributes.addCDATAAttribute("entryCount", String.valueOf(repeater.getSize()));
070            
071            ViewItemAccessor viewItemAccessor = viewItem.filter(ViewItemAccessor.class::isInstance)
072                    .map(ViewItemAccessor.class::cast)
073                    .orElse(ModelViewItemGroup.of(repeater.getModel()));
074
075            XMLUtils.startElement(contentHandler, tagName, attributes);
076            for (ModelAwareRepeaterEntry entry : repeater.getEntries())
077            {
078                _repeaterEntryToSAX(contentHandler, entry, viewItemAccessor, context, isEdition);
079            }
080            XMLUtils.endElement(contentHandler, tagName);
081        }
082        else if (value instanceof ModelAwareRepeaterEntry entry)
083        {
084            ViewItemAccessor viewItemAccessor = viewItem.filter(ViewItemAccessor.class::isInstance)
085                    .map(ViewItemAccessor.class::cast)
086                    .orElse(ModelViewItemGroup.of(_getRepeaterEntryDefinition(entry)));
087            
088            _repeaterEntryToSAX(contentHandler, entry, viewItemAccessor, context, isEdition);
089        }
090        else
091        {
092            throw new IllegalArgumentException("Try to sax the non repeater value '" + value + "' in tag name '" + tagName + "'");
093        }
094    }
095    
096    private RepeaterDefinition _getRepeaterEntryDefinition(ModelAwareRepeaterEntry entry)
097    {
098        Collection<? extends ModelItemContainer> modelItemContainers = entry.getModel();
099        assert modelItemContainers.size() == 1;
100        
101        ModelItemContainer modelItemContainer = modelItemContainers.iterator().next();
102        assert modelItemContainer instanceof RepeaterDefinition;
103        
104        return (RepeaterDefinition) modelItemContainer;
105    }
106
107    private void _repeaterEntryToSAX(ContentHandler contentHandler, ModelAwareRepeaterEntry entry, ViewItemAccessor viewItemAccessor, DataContext context, boolean isEdition) throws SAXException
108    {
109        DataContext newContext = context.cloneContext()
110                                        .addSuffixToLastSegment("[" + entry.getPosition() + "]");
111        
112        // Don't sax model path on entry, to avoid the same value as the repeater level
113        XMLUtils.startElement(contentHandler, "entry", _getEntryAttributes(entry, newContext));
114        IndexableDataHolderHelper.dataToSAX(entry, contentHandler, viewItemAccessor, newContext, isEdition);
115        XMLUtils.endElement(contentHandler, "entry");
116    }
117    
118    private Attributes _getEntryAttributes(ModelAwareRepeaterEntry entry, DataContext context)
119    {
120        AttributesImpl entryAttrs = ModelItemTypeHelper.getXMLAttributesFromDataContext(context, false);
121        String entryName = Integer.toString(entry.getPosition());
122        entryAttrs.addCDATAAttribute("name", entryName);
123        return entryAttrs;
124    }
125    
126    public Object valueToJSONForClient(Object value, Optional<ViewItem> viewItem, DataContext context)
127    {
128        return _valueToJSON(value, viewItem, context, false);
129    }
130    
131    public Object valueToJSONForEdition(Object value, Optional<ViewItem> viewItem, DataContext context)
132    {
133        return _valueToJSON(value, viewItem, context, true);
134    }
135    
136    private Object _valueToJSON(Object value, Optional<ViewItem> viewItem, DataContext context, boolean isEdition)
137    {
138        if (value == null)
139        {
140            return null;
141        }
142        else if (value instanceof ModelAwareRepeater repeater)
143        {
144            return viewItem.filter(ViewItemAccessor.class::isInstance)
145                           .map(ViewItemAccessor.class::cast)
146                           .map(viewItemAccessor -> repeater.dataToJSON(viewItemAccessor, context))
147                           .orElseGet(() -> repeater.dataToJSON(context));
148        }
149        else if (value instanceof ModelAwareRepeaterEntry entry)
150        {
151            DataContext entryContext = context.cloneContext().addSuffixToLastSegment("[" + entry.getPosition() + "]");
152            Optional<ViewItemAccessor> optViewItemAccessor = viewItem.filter(ViewItemAccessor.class::isInstance)
153                    .map(ViewItemAccessor.class::cast);
154            
155            return optViewItemAccessor.isEmpty() ? entry.dataToJSON(entryContext)
156                    : isEdition ? entry.dataToJSONForEdition(optViewItemAccessor.get(), entryContext)
157                            : entry.dataToJSON(optViewItemAccessor.get(), entryContext);
158        }
159        else
160        {
161            throw new IllegalArgumentException("Try to convert the non repeater value '" + value + "'");
162        }
163    }
164
165    public boolean isCompatible(RepositoryData parentData, String name) throws UnknownDataException
166    {
167        if (RepositoryModelItemGroupType.super.isCompatible(parentData, name))
168        {
169            return true;
170        }
171        // TODO NEWATTRIBUTEAPI: This part of the test is here just to be compatible with the old compositMetadata type. When all repeaters are migrated, remove this part of the test
172        else if (RepositoryConstants.COMPOSITE_METADTA_NODETYPE.equals(parentData.getType(name)))
173        {
174            RepositoryData possibleRepeater = parentData.getRepositoryData(name);
175            for (String dataName : possibleRepeater.getDataNames())
176            {
177                if (!NumberUtils.isParsable(dataName))
178                {
179                    // If there is at least one data that is not a number (entry position), this data can not be a repeater
180                    return false;
181                }
182            }
183            
184            // All data are numbers and could be entry positions, the data could be a repeater
185            return true;
186        }
187        else
188        {
189            return false;
190        }
191    }
192    
193    public String getRepositoryDataType()
194    {
195        return RepositoryConstants.REPEATER_NODETYPE;
196    }
197}