001/*
002 *  Copyright 2023 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.odf.cdmfr;
017
018import static org.ametys.cms.data.type.ModelItemTypeConstants.CONTENT_ELEMENT_TYPE_ID;
019import static org.ametys.cms.data.type.ModelItemTypeConstants.FILE_ELEMENT_TYPE_ID;
020import static org.ametys.cms.data.type.ModelItemTypeConstants.REFERENCE_ELEMENT_TYPE_ID;
021import static org.ametys.cms.data.type.ModelItemTypeConstants.RICH_TEXT_ELEMENT_TYPE_ID;
022import static org.ametys.plugins.repository.data.type.ModelItemTypeConstants.COMPOSITE_TYPE_ID;
023import static org.ametys.plugins.repository.data.type.ModelItemTypeConstants.REPEATER_TYPE_ID;
024import static org.ametys.runtime.model.type.ModelItemTypeConstants.BOOLEAN_TYPE_ID;
025import static org.ametys.runtime.model.type.ModelItemTypeConstants.DATE_TYPE_ID;
026import static org.ametys.runtime.model.type.ModelItemTypeConstants.DOUBLE_TYPE_ID;
027import static org.ametys.runtime.model.type.ModelItemTypeConstants.LONG_TYPE_ID;
028import static org.ametys.runtime.model.type.ModelItemTypeConstants.STRING_TYPE_ID;
029
030import java.time.LocalDate;
031import java.util.HashMap;
032import java.util.Map;
033import java.util.Set;
034
035import org.apache.avalon.framework.configuration.Configurable;
036import org.apache.avalon.framework.configuration.Configuration;
037import org.apache.avalon.framework.configuration.ConfigurationException;
038import org.apache.avalon.framework.service.ServiceException;
039import org.apache.avalon.framework.service.ServiceManager;
040import org.apache.avalon.framework.service.Serviceable;
041import org.apache.cocoon.xml.AttributesImpl;
042import org.apache.cocoon.xml.XMLUtils;
043import org.apache.excalibur.source.SourceResolver;
044import org.xml.sax.ContentHandler;
045import org.xml.sax.SAXException;
046
047import org.ametys.cms.contenttype.ContentTypesHelper;
048import org.ametys.cms.data.ContentValue;
049import org.ametys.cms.data.File;
050import org.ametys.cms.data.Reference;
051import org.ametys.cms.data.RichText;
052import org.ametys.cms.repository.Content;
053import org.ametys.odf.course.Course;
054import org.ametys.odf.enumeration.OdfReferenceTableHelper;
055import org.ametys.odf.orgunit.OrgUnit;
056import org.ametys.odf.person.Person;
057import org.ametys.odf.program.AbstractProgram;
058import org.ametys.odf.program.Container;
059import org.ametys.odf.program.Program;
060import org.ametys.odf.program.ProgramFactory;
061import org.ametys.odf.program.SubProgram;
062import org.ametys.plugins.repository.data.holder.ModelAwareDataHolder;
063import org.ametys.plugins.repository.data.holder.group.ModelAwareComposite;
064import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeater;
065import org.ametys.plugins.repository.data.holder.group.ModelAwareRepeaterEntry;
066import org.ametys.plugins.repository.model.RepositoryDataContext;
067import org.ametys.runtime.i18n.I18nizableText;
068import org.ametys.runtime.model.ElementDefinition;
069import org.ametys.runtime.model.Enumerator;
070import org.ametys.runtime.model.ModelItem;
071import org.ametys.runtime.model.ModelItemGroup;
072import org.ametys.runtime.model.View;
073import org.ametys.runtime.model.type.DataContext;
074import org.ametys.runtime.model.type.ModelItemType;
075
076/**
077 * Simple {@link CDMfrExtension} generating CDM-fr Ametys extension for each configured attribute.<br>
078 * Each attribute should be configured like:<br><br>
079 * <code>&lt;attribute name="attributeName" tag="tagName"></code><br><br>
080 * and will be output with the following syntax:<br><br>
081 * <code>&lt;ametys-cdm:tagName>value&lt;/ametys-cdm:tagName></code><br><br>
082 * The tag name is optional, defaulting to the attribute name.<br>
083 * It the attribute is of type "content", then the "cdm" view is also exported inside the attribute's XML tag if it exists.
084 */
085public class GenericCDMfrExtension extends AbstractCDMfrExtension implements Configurable, Serviceable
086{
087    private SourceResolver _sourceResolver;
088    private OdfReferenceTableHelper _refTableHelper;
089    private ContentTypesHelper _contentTypesHelper;
090    
091    private Map<String, String> _abstractPrograms = new HashMap<>();
092    private Map<String, String> _programs = new HashMap<>();
093    private Map<String, String> _subPrograms = new HashMap<>();
094    private Map<String, String> _containers = new HashMap<>();
095    private Map<String, String> _courses = new HashMap<>();
096    private Map<String, String> _orgUnits = new HashMap<>();
097    private Map<String, String> _persons = new HashMap<>();
098    
099    public void service(ServiceManager manager) throws ServiceException
100    {
101        _sourceResolver = (SourceResolver) manager.lookup(SourceResolver.ROLE);
102        _refTableHelper = (OdfReferenceTableHelper) manager.lookup(OdfReferenceTableHelper.ROLE);
103        _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
104    }
105    
106    public void configure(Configuration configuration) throws ConfigurationException
107    {
108        _abstractPrograms = _configure(configuration, "abstractProgram");
109        _programs = _configure(configuration, "program");
110        _subPrograms = _configure(configuration, "subProgram");
111        _containers = _configure(configuration, "container");
112        _courses = _configure(configuration, "course");
113        _orgUnits = _configure(configuration, "orgUnit");
114        _persons = _configure(configuration, "person");
115    }
116    
117    private Map<String, String> _configure(Configuration configuration, String attributeSet) throws ConfigurationException
118    {
119        Map<String, String> attributeMap = new HashMap<>();
120        
121        for (Configuration conf : configuration.getChild(attributeSet, true).getChildren("attribute"))
122        {
123            String name = conf.getAttribute("name");
124            String tag = conf.getAttribute("tag", name);
125            
126            attributeMap.put(name, tag);
127        }
128        
129        return attributeMap;
130    }
131    
132    public void abstractProgram2CDM(ContentHandler contentHandler, AbstractProgram<? extends ProgramFactory> program, Set<String> persons, Set<String> orgUnits) throws SAXException
133    {
134        _attributes2CDM(contentHandler, program, _abstractPrograms);
135    }
136
137    public void program2CDM(ContentHandler contentHandler, Program program, Set<String> persons, Set<String> orgUnits) throws SAXException
138    {
139        _attributes2CDM(contentHandler, program, _programs);
140    }
141    
142    public void subProgram2CDM(ContentHandler contentHandler, SubProgram subProgram, Set<String> persons, Set<String> orgUnits) throws SAXException
143    {
144        _attributes2CDM(contentHandler, subProgram, _subPrograms);
145    }
146
147    public void course2CDM(ContentHandler contentHandler, Course course, Set<String> persons, Set<String> orgUnits) throws SAXException
148    {
149        _attributes2CDM(contentHandler, course, _courses);
150    }
151
152    public void orgunit2CDM(ContentHandler contentHandler, OrgUnit orgunit) throws SAXException
153    {
154        _attributes2CDM(contentHandler, orgunit, _orgUnits);
155    }
156
157    public void person2CDM(ContentHandler contentHandler, Person person) throws SAXException
158    {
159        _attributes2CDM(contentHandler, person, _persons);
160    }
161
162    public void container2CDM(ContentHandler contentHandler, Container container, Set<String> persons, Set<String> orgUnits) throws SAXException
163    {
164        _attributes2CDM(contentHandler, container, _containers);
165    }
166    
167    private void _attributes2CDM(ContentHandler contentHandler, Content content, Map<String, String> attributes) throws SAXException
168    {
169        for (Map.Entry<String, String> entry : attributes.entrySet())
170        {
171            _attribute2CDM(contentHandler, content, content, null, entry.getKey(), entry.getValue());
172        }
173    }
174
175    private void _attribute2CDM(ContentHandler contentHandler, ModelAwareDataHolder dataHolder, Content initialContent, String path, String attributeName, String tagName) throws SAXException
176    {
177        Object value = dataHolder.getValue(attributeName, false, null);
178
179        if (value == null)
180        {
181            return;
182        }
183        
184        String dataPath = path == null ? attributeName : path + ModelItem.ITEM_PATH_SEPARATOR + attributeName;
185
186        ModelItem modelItem = dataHolder.getDefinition(attributeName);
187        ModelItemType type = modelItem.getType();
188        String typeId = type.getId();
189        
190        if (modelItem instanceof ElementDefinition definition)
191        {
192            Object[] values = definition.isMultiple() ? (Object[]) value : new Object[] {value};
193            
194            switch (typeId)
195            {
196                case BOOLEAN_TYPE_ID:
197                case STRING_TYPE_ID:
198                case DOUBLE_TYPE_ID:
199                case LONG_TYPE_ID:
200                    _simple2CDM(contentHandler, values, tagName, definition);
201                    break;
202                case REFERENCE_ELEMENT_TYPE_ID:
203                    _reference2CDM(contentHandler, values, tagName);
204                    break;
205                case DATE_TYPE_ID:
206                    _date2CDM(contentHandler, values, tagName);
207                    break;
208                case RICH_TEXT_ELEMENT_TYPE_ID:
209                    _richText2CDM(contentHandler, values, tagName, initialContent, dataPath);
210                    break;
211                case CONTENT_ELEMENT_TYPE_ID:
212                    _content2CDM(contentHandler, values, tagName, initialContent);
213                    break;
214                case FILE_ELEMENT_TYPE_ID:
215                    _file2CDM(contentHandler, values, tagName, initialContent, dataPath);
216                    break;
217                default:
218                    // Ignore it
219                    break;
220            }
221        }
222        else if (typeId.equals(COMPOSITE_TYPE_ID))
223        {
224            ModelAwareComposite composite = (ModelAwareComposite) value;
225            ModelItemGroup group = (ModelItemGroup) modelItem;
226            
227            _composite2CDM(contentHandler, composite, group, initialContent, dataPath, tagName);
228        }
229        else if (typeId.equals(REPEATER_TYPE_ID))
230        {
231            ModelAwareRepeater repeater = (ModelAwareRepeater) value;
232            ModelItemGroup group = (ModelItemGroup) modelItem;
233            
234            _repeater2CDM(contentHandler, repeater, group, initialContent, dataPath, tagName);
235        }
236    }
237    
238    private void _simple2CDM(ContentHandler contentHandler, Object[] values, String tagName, ElementDefinition definition) throws SAXException
239    {
240        Enumerator enumerator = definition.getEnumerator();
241        
242        for (Object v : values)
243        {
244            if (enumerator == null)
245            {
246                XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, v.toString());
247            }
248            else
249            {
250                try
251                {
252                    @SuppressWarnings("unchecked")
253                    I18nizableText label = enumerator.getEntry(v);
254                    
255                    AttributesImpl atts = new AttributesImpl();
256                    atts.addCDATAAttribute("value", v.toString());
257                    
258                    XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, atts);
259                    label.toSAX(contentHandler);
260                    XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName);
261                }
262                catch (Exception e)
263                {
264                    throw new SAXException("Cannot retrieve enumerated label for value " + v + " for attribute " + definition.getName(), e);
265                }
266            }
267        }
268    }
269    
270    private void _reference2CDM(ContentHandler contentHandler, Object[] values, String tagName) throws SAXException
271    {
272        for (Object v : values)
273        {
274            Reference ref = (Reference) v;
275
276            AttributesImpl attrs = new AttributesImpl();
277            attrs.addCDATAAttribute("type", ref.getType());
278            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, attrs, ref.getValue());
279        }
280    }
281    
282    private void _date2CDM(ContentHandler contentHandler, Object[] values, String tagName) throws SAXException
283    {
284        for (Object v : values)
285        {
286            CDMHelper.date2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, (LocalDate) v);
287        }
288    }
289    
290    private void _richText2CDM(ContentHandler contentHandler, Object[] values, String tagName, Content content, String dataPath) throws SAXException
291    {
292        for (Object v : values)
293        {
294            DataContext context = RepositoryDataContext.newInstance()
295                                                       .withObject(content)
296                                                       .withDataPath(dataPath);
297            
298            CDMHelper.richText2CDM(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, (RichText) v, context, _sourceResolver);
299        }
300    }
301    
302    private void _content2CDM(ContentHandler contentHandler, Object[] values, String tagName, Content initialContent) throws SAXException
303    {
304        for (Object v : values)
305        {
306            Content content = ((ContentValue) v).getContentIfExists().orElse(null);
307            
308            if (content != null)
309            {
310                if (_refTableHelper.isTableReferenceEntry(content))
311                {
312                    AttributesImpl attrs = new AttributesImpl();
313                    attrs.addCDATAAttribute("id", content.getId());
314                    attrs.addCDATAAttribute("code", _refTableHelper.getItemCDMfrValue(content.getId(), true));
315                    attrs.addCDATAAttribute("title", _refTableHelper.getItemLabel(content.getId(), initialContent.getLanguage()));
316                    
317                    XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, attrs);
318                    _contentView2CDM(contentHandler, content);
319                    XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName);
320                }
321                else
322                {
323                    AttributesImpl attrs = new AttributesImpl();
324                    attrs.addCDATAAttribute("id", content.getId());
325                    attrs.addCDATAAttribute("title", content.getTitle());
326                    
327                    XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, attrs);
328                    _contentView2CDM(contentHandler, content);
329                    XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName);
330                }
331            }
332        }
333    }
334    
335    private void _contentView2CDM(ContentHandler contentHandler, Content content) throws SAXException
336    {
337        DataContext context = RepositoryDataContext.newInstance()
338                                                   .withObject(content);
339
340        View view = _contentTypesHelper.getView("cdm", content);
341        
342        if (view != null)
343        {
344            content.dataToSAX(contentHandler, view, context);
345        }
346    }
347    
348    private void _file2CDM(ContentHandler contentHandler, Object[] values, String tagName, Content content, String dataPath) throws SAXException
349    {
350        for (Object v : values)
351        {
352            File file = (File) v;
353            XMLUtils.createElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName, _getFileAbsoluteUrl(content, file, dataPath));
354        }
355    }
356    
357    private void _composite2CDM(ContentHandler contentHandler, ModelAwareComposite composite, ModelItemGroup group, Content initialContent, String dataPath, String tagName) throws SAXException
358    {
359        XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName);
360        
361        for (ModelItem subAttribute : group.getChildren())
362        {
363            _attribute2CDM(contentHandler, composite, initialContent, dataPath, subAttribute.getName(), subAttribute.getName());
364        }
365        
366        XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName);
367    }
368    
369    private void _repeater2CDM(ContentHandler contentHandler, ModelAwareRepeater repeater, ModelItemGroup group, Content initialContent, String dataPath, String tagName) throws SAXException
370    {
371        
372        for (ModelAwareRepeaterEntry entry : repeater.getEntries())
373        {
374            XMLUtils.startElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName);
375            
376            for (ModelItem subAttribute : group.getChildren())
377            {
378                _attribute2CDM(contentHandler, entry, initialContent, dataPath + "[" + entry.getPosition() + "]", subAttribute.getName(), subAttribute.getName());
379            }
380            
381            XMLUtils.endElement(contentHandler, CDMFRTagsConstants.NAMESPACE_AMETYS_CDM + tagName);
382        }
383    }
384}