001/*
002 *  Copyright 2011 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.enumeration;
017
018import java.util.HashMap;
019import java.util.HashSet;
020import java.util.LinkedHashMap;
021import java.util.List;
022import java.util.Map;
023import java.util.Set;
024import java.util.stream.Collectors;
025
026import org.apache.avalon.framework.component.Component;
027import org.apache.avalon.framework.logger.AbstractLogEnabled;
028import org.apache.avalon.framework.service.ServiceException;
029import org.apache.avalon.framework.service.ServiceManager;
030import org.apache.avalon.framework.service.Serviceable;
031import org.apache.cocoon.xml.AttributesImpl;
032import org.apache.cocoon.xml.XMLUtils;
033import org.apache.commons.lang3.StringUtils;
034import org.xml.sax.ContentHandler;
035import org.xml.sax.SAXException;
036
037import org.ametys.cms.contenttype.AbstractMetadataSetElement;
038import org.ametys.cms.contenttype.ContentConstants;
039import org.ametys.cms.contenttype.ContentType;
040import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
041import org.ametys.cms.contenttype.ContentTypesHelper;
042import org.ametys.cms.contenttype.MetadataDefinition;
043import org.ametys.cms.contenttype.MetadataDefinitionHolder;
044import org.ametys.cms.contenttype.MetadataDefinitionReference;
045import org.ametys.cms.contenttype.MetadataSet;
046import org.ametys.cms.contenttype.MetadataType;
047import org.ametys.cms.contenttype.RepeaterDefinition;
048import org.ametys.cms.repository.Content;
049import org.ametys.cms.repository.ContentQueryHelper;
050import org.ametys.cms.repository.ContentTypeExpression;
051import org.ametys.core.ui.Callable;
052import org.ametys.plugins.repository.AmetysObjectIterable;
053import org.ametys.plugins.repository.AmetysObjectIterator;
054import org.ametys.plugins.repository.AmetysObjectResolver;
055import org.ametys.plugins.repository.UnknownAmetysObjectException;
056import org.ametys.plugins.repository.query.expression.AndExpression;
057import org.ametys.plugins.repository.query.expression.Expression.Operator;
058import org.ametys.plugins.repository.query.expression.StringExpression;
059import org.ametys.runtime.config.Config;
060
061/**
062 * This component handles ODF reference tables 
063 *
064 */
065public class OdfReferenceTableHelper extends AbstractLogEnabled implements Component, Serviceable
066{
067    /** Avalon Role */
068    public static final String ROLE = OdfReferenceTableHelper.class.getName();
069 
070    /** Abstract table ref */
071    public static final String ABSTRACT_TABLE_REF = "odf-enumeration.AbstractTableRef";
072    /** Domain */
073    public static final String DOMAIN = "odf-enumeration.Domain";
074    /** Degree */
075    public static final String DEGREE = "odf-enumeration.Degree";
076    /** Level */
077    public static final String LEVEL = "odf-enumeration.Level";
078    /** Program type. */
079    public static final String PROGRAM_TYPE = "odf-enumeration.ProgramType";
080    /** Form of teaching */
081    public static final String FORMOFTEACHING_METHOD = "odf-enumeration.FormofteachingMethod";
082    /** Orgnization of teaching */
083    public static final String FORMOFTEACHING_ORG = "odf-enumeration.FormofteachingOrg";
084    /** Teaching method */
085    public static final String TEACHING_ACTIVITY = "odf-enumeration.TeachingActivity";
086    /** Type of training course */
087    public static final String INTERNSHIP = "odf-enumeration.Internship";
088    /** Distance learning modalities */
089    public static final String DISTANCE_LEARNING_MODALITIES = "odf-enumeration.DistanceLearningModalities";
090    /** Place */
091    public static final String PLACE = "odf-enumeration.Place";
092    /** Teaching term. */
093    public static final String TEACHING_TERM = "odf-enumeration.TeachingTerm";
094    /** Time slot */
095    public static final String TIME_SLOT = "odf-enumeration.TimeSlot";
096    /** Code ROME */
097    public static final String CODE_ROME = "odf-enumeration.CodeRome";
098    /** Code ERASMUS */
099    public static final String CODE_ERASMUS = "odf-enumeration.CodeErasmus";
100    /** Code DGESIP */
101    public static final String CODE_DGESIP = "odf-enumeration.CodeDgesip";
102    /** Code SISE */
103    public static final String CODE_SISE = "odf-enumeration.CodeSise";
104    /** Code Cite97 */
105    public static final String CODE_CITE97 = "odf-enumeration.CodeCite97";
106    /** Code FAP */
107    public static final String CODE_FAP = "odf-enumeration.CodeFap";
108    /** Code NSF */
109    public static final String CODE_NSF = "odf-enumeration.CodeNsf";
110    /** RNCP level */
111    public static final String RNCP_LEVEL = "odf-enumeration.RncpLevel";
112    /** Join orgunit*/
113    public static final String JOIN_ORGUNIT = "odf-enumeration.JoinOrgunit";
114    /** Mention licence */
115    public static final String ABSTRACT_MENTION = "odf-enumeration.Mention";
116    /** Mention licence */
117    public static final String MENTION_LICENCE = "odf-enumeration.MentionLicence";
118    /** Mention licence pro */
119    public static final String MENTION_LICENCEPRO = "odf-enumeration.MentionLicencepro";
120    /** Mention master */
121    public static final String MENTION_MASTER = "odf-enumeration.MentionMaster";
122    /** Abstract table ref for category */
123    public static final String ABSTRACT_TABLE_REF_CATEGORY = "odf-enumeration.AbstractTableRefCategory";
124    /** Apprenticeship contract */
125    public static final String APPRENTICESHIP_CONTRACT = "odf-enumeration.ApprenticeshipContract";
126    /** Available certification */
127    public static final String AVAILABLE_CERTIFICATION = "odf-enumeration.AvailableCertification";
128    /** Campus */
129    public static final String CAMPUS = "odf-enumeration.Campus";
130    /** Category for code Erasmus */
131    public static final String CODE_ERASMUS_CATEGORY = "odf-enumeration.CodeErasmusCategory";
132    /** Category for code FAP */
133    public static final String CODE_FAP_CATEGORY = "odf-enumeration.CodeFapCategory";
134    /** Nature of container */
135    public static final String CONTAINER_NATURE = "odf-enumeration.ContainerNature";
136    /** Nature of course */
137    public static final String COURSE_NATURE = "odf-enumeration.CourseNature";
138    /** Discipline */
139    public static final String DISCIPLINE = "odf-enumeration.Discipline";
140    /** Duration */
141    public static final String DURATION = "odf-enumeration.Duration";
142    /** ECTS */
143    public static final String ECTS = "odf-enumeration.Ects";
144    /** Foreign place */
145    public static final String FOREIGN_PLACE = "odf-enumeration.ForeignPlace";
146    /** International education */
147    public static final String INTERNATIONAL_EDUCATION = "odf-enumeration.InternationalEducation";
148    /** Language */
149    public static final String LANGUAGE = "odf-enumeration.Language";
150    /** OrgUnit type */
151    public static final String ORGUNIT_TYPE = "odf-enumeration.OrgUnitType";
152    /** Period */
153    public static final String PERIOD = "odf-enumeration.Period";
154    /** Person role */
155    public static final String PERSON_ROLE = "odf-enumeration.PersonRole";
156    /** Program field */
157    public static final String PROGRAM_FIELD = "odf-enumeration.ProgramField";
158    /** Sectors */
159    public static final String SECTORS = "odf-enumeration.Sectors";
160    
161    private AmetysObjectResolver _resolver;
162
163    private ContentTypeExtensionPoint _cTypeEP;
164
165    private ContentTypesHelper _cTypeHelper;
166
167    @Override
168    public void service(ServiceManager manager) throws ServiceException
169    {
170        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
171        _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
172        _cTypeHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
173    } 
174    
175    /**
176     * Determines if the content type is a ODF table reference
177     * @param cTypeId The id of content type
178     * @return true if the content type is a ODF table reference
179     */
180    public boolean isTableReference(String cTypeId)
181    {
182        return _cTypeHelper.getAncestors(cTypeId).contains(ABSTRACT_TABLE_REF);
183    }
184    
185    /**
186     * Determines if the content is an entry of a ODF table reference
187     * @param content The content 
188     * @return <code>true</code> if the content is an entry of a ODF table reference
189     */
190    public boolean isTableReferenceEntry(Content content)
191    {
192        String[] cTypeIds = content.getTypes();
193        for (String cTypeId : cTypeIds)
194        {
195            if (isTableReference(cTypeId))
196            {
197                return true;
198            }
199        }
200        return false;
201    }
202    
203    /**
204     * Get the id of table references
205     * @return The content type's id
206     */
207    public Set<String> getTableReferenceIds()
208    {
209        Set<String> tableRefIds = new HashSet<>();
210        
211        for (String cTypeId : _cTypeEP.getExtensionsIds())
212        {
213            if (_cTypeHelper.getAncestors(cTypeId).contains(ABSTRACT_TABLE_REF))
214            {
215                tableRefIds.add(cTypeId);
216            }
217        }
218        return tableRefIds;
219    }
220    
221    /**
222     * Get the metadata definitions for table references for a given content type
223     * @param cTypeId The id of content type
224     * @return The metadata definitions for table references
225     */
226    public Map<String, MetadataDefinition> getTableRefMetadataDefinition(String cTypeId)
227    {
228        ContentType cType = _cTypeEP.getExtension(cTypeId);
229        return _getTableRefMetadata(cType, "");
230    }
231    
232    /**
233     * Get the metadata definitions for table references for a given content type and a metadata set
234     * @param cTypeId The id of content type
235     * @param medatataSetName the name of the metadata set. Cannot be null.
236     * @param isEditionMetadataSet true if the metadata set is for edition.
237     * @return The metadata definitions for table references
238     */
239    public Map<String, MetadataDefinition> getTableRefMetadataDefinition(String cTypeId, String medatataSetName, boolean isEditionMetadataSet)
240    {
241        ContentType cType = _cTypeEP.getExtension(cTypeId);
242        
243        MetadataSet metadataSet = isEditionMetadataSet ? cType.getMetadataSetForEdition(medatataSetName) : cType.getMetadataSetForView(medatataSetName);
244        return _getTableRefMetadata(metadataSet, cType, "");
245    }
246    
247    private Map<String, MetadataDefinition> _getTableRefMetadata(MetadataDefinitionHolder parentMetaDef, String prefix)
248    {
249        Map<String, MetadataDefinition> tableRefMetadata = new LinkedHashMap<>();
250        
251        Set<String> metadataNames = parentMetaDef.getMetadataNames();
252        for (String metadataName : metadataNames)
253        {
254            MetadataDefinition metaDef = parentMetaDef.getMetadataDefinition(metadataName);
255            MetadataType type = metaDef.getType();
256            
257            switch (type)
258            {
259                case COMPOSITE:
260                    if (!(metaDef instanceof RepeaterDefinition))
261                    {
262                        // enumerated metadata in repeaters are not supported
263                        tableRefMetadata.putAll(_getTableRefMetadata(metaDef, prefix + metadataName + ContentConstants.METADATA_PATH_SEPARATOR)); 
264                    }
265                    break;
266
267                case CONTENT:
268                    if (isTableReference(metaDef.getContentType()))
269                    {
270                        tableRefMetadata.put(prefix + metadataName, metaDef);
271                    }
272                    break;
273                    
274                default:
275                    break;
276            }
277        }
278        
279        return tableRefMetadata;
280    }
281    
282    private Map<String, MetadataDefinition> _getTableRefMetadata (AbstractMetadataSetElement element, MetadataDefinitionHolder parentMetaDef, String prefix)
283    {
284        Map<String, MetadataDefinition> tableRefMetadata = new LinkedHashMap<>();
285        
286        for (AbstractMetadataSetElement subElement : element.getElements())
287        {
288            if (subElement instanceof MetadataDefinitionReference)
289            {
290                MetadataDefinitionReference metadataDefRef = (MetadataDefinitionReference) subElement;
291                String metadataName = metadataDefRef.getMetadataName();
292                MetadataDefinition metaDef = parentMetaDef.getMetadataDefinition(metadataName);
293           
294                MetadataType type = metaDef.getType();
295                
296                switch (type)
297                {
298                    case COMPOSITE:
299                        if (!(metaDef instanceof RepeaterDefinition))
300                        {
301                            // table ref metadata in repeaters are not supported
302                            tableRefMetadata.putAll(_getTableRefMetadata(subElement, metaDef, prefix + metadataName + ContentConstants.METADATA_PATH_SEPARATOR)); 
303                        }
304                        break;
305
306                    case CONTENT:
307                        if (isTableReference(metaDef.getContentType()))
308                        {
309                            tableRefMetadata.put(prefix + metadataName, metaDef);
310                        }
311                        break;
312                        
313                    default:
314                        break;
315                }
316            }
317        }
318        return tableRefMetadata;
319    }
320    
321    /**
322     * Get the content type for mention for this degree
323     * @param degreeIds The ids of degrees
324     * @return A map with the id of content type or null if there is no mention for this degree
325     */
326    @Callable
327    public Map<String, String> getMentionContentTypes(List<String> degreeIds)
328    {
329        Map<String, String> mentionTypes = new HashMap<>();
330        
331        for (String degreeId : degreeIds)
332        {
333            try
334            {
335                Content content = _resolver.resolveById(degreeId);
336                OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
337                
338                String degreeCode = entry.getCode();
339                
340                if (degreeCode.equals(Config.getInstance().getValueAsString("odf.programs.degree.license")))
341                {
342                    mentionTypes.put(degreeId, OdfReferenceTableHelper.MENTION_LICENCE);
343                }
344                else if (degreeCode.equals(Config.getInstance().getValueAsString("odf.programs.degree.licensepro")))
345                {
346                    mentionTypes.put(degreeId, OdfReferenceTableHelper.MENTION_LICENCEPRO);
347                }
348                else if (degreeCode.equals(Config.getInstance().getValueAsString("odf.programs.degree.master")))
349                {
350                    mentionTypes.put(degreeId, OdfReferenceTableHelper.MENTION_MASTER);
351                }
352            }
353            catch (UnknownAmetysObjectException e)
354            {
355                mentionTypes.put(degreeId, null);
356            }
357        }
358        return mentionTypes;
359    }
360    
361    /**
362     * Get the CDM-fr value associated with the given code
363     * @param tableRefIf The id of content type
364     * @param code The code
365     * @return The CDM-fr value or empty string if not found
366     */
367    public String getCDMfrValue (String tableRefIf, String code)
368    {
369        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefIf);
370        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.METADATA_CODE, Operator.EQ, code);
371        
372        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
373        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
374        AmetysObjectIterator<Content> it = contents.iterator();
375        
376        if (it.hasNext())
377        {
378            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(it.next());
379            return entry.getCdmValue();
380        }
381        
382        return "";
383    }
384    
385    /**
386     * Get all items of an enumeration and their label
387     * @param tableRefId The id of content type
388     * @return items of enumeration
389     */
390    public List<OdfReferenceTableEntry> getItems (String tableRefId)
391    {
392        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefId);
393        
394        String xpathQuery = ContentQueryHelper.getContentXPathQuery(cTypeExpr);
395        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
396        
397        return contents.stream().map(c -> new OdfReferenceTableEntry(c)).collect(Collectors.toList());
398    }
399    
400    /**
401     * Returns the label of an reference table entry
402     * @param tableRefId The id of content type
403     * @param contentId The content id
404     * @param lang The requested language of label
405     * @return the item label or <code>null</code> if not found
406     */
407    public String getItemLabel (String tableRefId, String contentId, String lang)
408    {
409        try
410        {
411            Content content = _resolver.resolveById(contentId);
412            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
413            return entry.getLabel(lang);
414        }
415        catch (UnknownAmetysObjectException e)
416        {
417            return null;
418        }
419    }
420    
421    /**
422     * Returns the CMD value of an reference table entry
423     * @param tableRefId The id of content type
424     * @param contentId The content id
425     * @param returnCodeIfEmpty <code>true</code> to return the code if CDM-fr value is empty
426     * @return the CDM-fr value or empty value if not found
427     */
428    public String getItemCDMfrValue (String tableRefId, String contentId, boolean returnCodeIfEmpty)
429    {
430        if (StringUtils.isEmpty(contentId))
431        {
432            return "";
433        }
434        
435        Content content = _resolver.resolveById(contentId);
436        OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
437        
438        String cdmValue = entry.getCdmValue();
439        
440        if (StringUtils.isEmpty(cdmValue) && returnCodeIfEmpty)
441        {
442            return entry.getCode();
443        }
444        return cdmValue;
445    }
446    
447    /**
448     * Returns the code of an reference table entry from its CDM value
449     * @param tableRefId The id of content type
450     * @param contentId The id of content
451     * @return the code or empty value if not found
452     */
453    public String getItemCode (String tableRefId, String contentId)
454    {
455        if (StringUtils.isEmpty(contentId))
456        {
457            return "";
458        }
459        
460        try
461        {
462            Content content = _resolver.resolveById(contentId);
463            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
464            return entry.getCode();
465        }
466        catch (UnknownAmetysObjectException e)
467        {
468            return "";
469        }
470    }
471    
472    /**
473     * Returns the code of an reference table entry from its CDM value
474     * @param tableRefId The id of content type
475     * @param cdmValue The CDM-fr value
476     * @return the code or <code>null</code> if not found
477     */
478    public String getItemCodeFromCDM (String tableRefId, String cdmValue)
479    {
480        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefId);
481        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.METADATA_CDM_VALUE, Operator.EQ, cdmValue);
482        
483        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
484        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
485        AmetysObjectIterator<Content> it = contents.iterator();
486        
487        if (it.hasNext())
488        {
489            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(it.next());
490            return entry.getCode();
491        }
492        return null;
493    }
494
495    /**
496     * Returns the entry of an reference table entry from its cdmValue
497     * @param tableRefId The id of content type
498     * @param cdmValue The CDM-fr value
499     * @return the entry or <code>null</code> if not found
500     */
501    public OdfReferenceTableEntry getItemFromCDM(String tableRefId, String cdmValue)
502    {
503        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefId);
504        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.METADATA_CDM_VALUE, Operator.EQ, cdmValue);
505        
506        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
507
508        return _resolver.<Content>query(xpathQuery).stream()
509            .findFirst()
510            .map(OdfReferenceTableEntry::new)
511            .orElse(null);
512    }
513    
514    /**
515     * Returns the entry of an reference table entry from its code
516     * @param tableRefId The id of content type
517     * @param code The code
518     * @return the entry or <code>null</code> if not found
519     */
520    public OdfReferenceTableEntry getItemFromCode(String tableRefId, String code)
521    {
522        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefId);
523        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.METADATA_CODE, Operator.EQ, code);
524        
525        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
526        
527        return _resolver.<Content>query(xpathQuery).stream()
528            .findFirst()
529            .map(OdfReferenceTableEntry::new)
530            .orElse(null);
531    }
532    
533    /**
534     * SAX items of a reference table
535     * @param contentHandler The content handler to sax into
536     * @param tableRefId The id of reference table
537     * @throws SAXException if an error occurred while saxing
538     */
539    public void saxItems (ContentHandler contentHandler, String tableRefId) throws SAXException
540    {
541        _saxItems(contentHandler, new AttributesImpl(), tableRefId);
542    }
543    
544    /**
545     * SAX items of a reference table
546     * @param contentHandler the content handler to sax into
547     * @param metaDef the metadata definition
548     * @throws SAXException if an error occurs while saxing
549     */
550    public void saxItems (ContentHandler contentHandler, MetadataDefinition metaDef) throws SAXException
551    {
552        if (metaDef.getType() == MetadataType.CONTENT)
553        {
554            String cTypeId = metaDef.getContentType();
555            if (cTypeId.startsWith("odf-enumeration."))
556            {
557                AttributesImpl attrs = new AttributesImpl();
558                attrs.addCDATAAttribute("metadataName", metaDef.getName());
559                attrs.addCDATAAttribute("metadataPath", metaDef.getId());
560                
561                _saxItems(contentHandler, attrs, cTypeId);
562            }
563        }
564    }
565    
566    private void _saxItems (ContentHandler contentHandler, AttributesImpl rootAttrs, String tableRefId) throws SAXException
567    {
568        rootAttrs.addCDATAAttribute("contentTypeId", tableRefId);
569        XMLUtils.startElement(contentHandler, "items", rootAttrs);
570        
571        List<OdfReferenceTableEntry> entries = getItems(tableRefId);
572        for (OdfReferenceTableEntry entry : entries)
573        {
574            AttributesImpl valueAttrs = new AttributesImpl();
575            valueAttrs.addCDATAAttribute("id", entry.getId());
576            valueAttrs.addCDATAAttribute("code", entry.getCode());
577            valueAttrs.addCDATAAttribute("cdmValue", entry.getCdmValue());
578            
579            String lang = Config.getInstance().getValueAsString("odf.programs.lang");
580            XMLUtils.createElement(contentHandler, "item", valueAttrs, entry.getLabel(lang));
581        }
582        
583        XMLUtils.endElement(contentHandler, "items");
584    }
585}