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.Arrays;
019import java.util.Collections;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.LinkedHashMap;
023import java.util.List;
024import java.util.Map;
025import java.util.Optional;
026import java.util.Set;
027import java.util.stream.Collectors;
028
029import org.apache.avalon.framework.component.Component;
030import org.apache.avalon.framework.service.ServiceException;
031import org.apache.avalon.framework.service.ServiceManager;
032import org.apache.avalon.framework.service.Serviceable;
033import org.apache.cocoon.xml.AttributesImpl;
034import org.apache.cocoon.xml.XMLUtils;
035import org.apache.commons.lang3.StringUtils;
036import org.xml.sax.ContentHandler;
037import org.xml.sax.SAXException;
038
039import org.ametys.cms.contenttype.ContentAttributeDefinition;
040import org.ametys.cms.contenttype.ContentType;
041import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
042import org.ametys.cms.contenttype.ContentTypesHelper;
043import org.ametys.cms.data.ContentValue;
044import org.ametys.cms.repository.Content;
045import org.ametys.cms.repository.ContentQueryHelper;
046import org.ametys.cms.repository.ContentTypeExpression;
047import org.ametys.core.ui.Callable;
048import org.ametys.odf.ODFHelper;
049import org.ametys.odf.ProgramItem;
050import org.ametys.odf.program.AbstractProgram;
051import org.ametys.odf.program.Program;
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.model.RepeaterDefinition;
057import org.ametys.plugins.repository.query.SortCriteria;
058import org.ametys.plugins.repository.query.expression.AndExpression;
059import org.ametys.plugins.repository.query.expression.BooleanExpression;
060import org.ametys.plugins.repository.query.expression.Expression;
061import org.ametys.plugins.repository.query.expression.Expression.Operator;
062import org.ametys.plugins.repository.query.expression.NotExpression;
063import org.ametys.plugins.repository.query.expression.StringExpression;
064import org.ametys.runtime.config.Config;
065import org.ametys.runtime.model.ElementDefinition;
066import org.ametys.runtime.model.ModelItem;
067import org.ametys.runtime.model.ModelItemContainer;
068import org.ametys.runtime.model.View;
069import org.ametys.runtime.model.ViewElement;
070import org.ametys.runtime.model.ViewItem;
071import org.ametys.runtime.model.ViewItemContainer;
072import org.ametys.runtime.plugin.component.AbstractLogEnabled;
073
074/**
075 * This component handles ODF reference tables 
076 *
077 */
078public class OdfReferenceTableHelper extends AbstractLogEnabled implements Component, Serviceable
079{
080    /** Avalon Role */
081    public static final String ROLE = OdfReferenceTableHelper.class.getName();
082 
083    /** Abstract table ref */
084    public static final String ABSTRACT_TABLE_REF = "odf-enumeration.AbstractTableRef";
085    /** Domain */
086    public static final String DOMAIN = "odf-enumeration.Domain";
087    /** Degree */
088    public static final String DEGREE = "odf-enumeration.Degree";
089    /** Level */
090    public static final String LEVEL = "odf-enumeration.Level";
091    /** Program type. */
092    public static final String PROGRAM_TYPE = "odf-enumeration.ProgramType";
093    /** Form of teaching */
094    public static final String FORMOFTEACHING_METHOD = "odf-enumeration.FormofteachingMethod";
095    /** Orgnization of teaching */
096    public static final String FORMOFTEACHING_ORG = "odf-enumeration.FormofteachingOrg";
097    /** Teaching method */
098    public static final String TEACHING_ACTIVITY = "odf-enumeration.TeachingActivity";
099    /** Type of training course */
100    public static final String INTERNSHIP = "odf-enumeration.Internship";
101    /** Distance learning modalities */
102    public static final String DISTANCE_LEARNING_MODALITIES = "odf-enumeration.DistanceLearningModalities";
103    /** Place */
104    public static final String PLACE = "odf-enumeration.Place";
105    /** Teaching term. */
106    public static final String TEACHING_TERM = "odf-enumeration.TeachingTerm";
107    /** Time slot */
108    public static final String TIME_SLOT = "odf-enumeration.TimeSlot";
109    /** Code ROME */
110    public static final String CODE_ROME = "odf-enumeration.CodeRome";
111    /** Code ERASMUS */
112    public static final String CODE_ERASMUS = "odf-enumeration.CodeErasmus";
113    /** Code DGESIP */
114    public static final String CODE_DGESIP = "odf-enumeration.CodeDgesip";
115    /** Code SISE */
116    public static final String CODE_SISE = "odf-enumeration.CodeSise";
117    /** Code Cite97 */
118    public static final String CODE_CITE97 = "odf-enumeration.CodeCite97";
119    /** Code FAP */
120    public static final String CODE_FAP = "odf-enumeration.CodeFap";
121    /** Code NSF */
122    public static final String CODE_NSF = "odf-enumeration.CodeNsf";
123    /** RNCP level */
124    public static final String RNCP_LEVEL = "odf-enumeration.RncpLevel";
125    /** Join orgunit*/
126    public static final String JOIN_ORGUNIT = "odf-enumeration.JoinOrgunit";
127    /** Mention licence */
128    public static final String ABSTRACT_MENTION = "odf-enumeration.Mention";
129    /** Mention BUT */
130    public static final String MENTION_BUT = "odf-enumeration.MentionBUT";
131    /** Mention BUT */
132    public static final String MENTION_BUT_ATTRIBUTE_PARCOURS = "parcours";
133    /** Mention licence */
134    public static final String MENTION_LICENCE = "odf-enumeration.MentionLicence";
135    /** Mention licence pro */
136    public static final String MENTION_LICENCEPRO = "odf-enumeration.MentionLicencepro";
137    /** Mention master */
138    public static final String MENTION_MASTER = "odf-enumeration.MentionMaster";
139    /** Abstract table ref for category */
140    public static final String ABSTRACT_TABLE_REF_CATEGORY = "odf-enumeration.AbstractTableRefCategory";
141    /** Apprenticeship contract */
142    public static final String APPRENTICESHIP_CONTRACT = "odf-enumeration.ApprenticeshipContract";
143    /** Available certification */
144    public static final String AVAILABLE_CERTIFICATION = "odf-enumeration.AvailableCertification";
145    /** Campus */
146    public static final String CAMPUS = "odf-enumeration.Campus";
147    /** Category for code Erasmus */
148    public static final String CODE_ERASMUS_CATEGORY = "odf-enumeration.CodeErasmusCategory";
149    /** Category for code FAP */
150    public static final String CODE_FAP_CATEGORY = "odf-enumeration.CodeFapCategory";
151    /** Nature of container */
152    public static final String CONTAINER_NATURE = "odf-enumeration.ContainerNature";
153    /** Nature of course */
154    public static final String COURSE_NATURE = "odf-enumeration.CourseNature";
155    /** Discipline */
156    public static final String DISCIPLINE = "odf-enumeration.Discipline";
157    /** Duration */
158    public static final String DURATION = "odf-enumeration.Duration";
159    /** ECTS */
160    public static final String ECTS = "odf-enumeration.Ects";
161    /** Foreign place */
162    public static final String FOREIGN_PLACE = "odf-enumeration.ForeignPlace";
163    /** International education */
164    public static final String INTERNATIONAL_EDUCATION = "odf-enumeration.InternationalEducation";
165    /** Language */
166    public static final String LANGUAGE = "odf-enumeration.Language";
167    /** OrgUnit type */
168    public static final String ORGUNIT_TYPE = "odf-enumeration.OrgUnitType";
169    /** Period */
170    public static final String PERIOD = "odf-enumeration.Period";
171    /** Period type */
172    public static final String PERIOD_TYPE = "odf-enumeration.PeriodType";
173    /** Person role */
174    public static final String PERSON_ROLE = "odf-enumeration.PersonRole";
175    /** Program field */
176    public static final String PROGRAM_FIELD = "odf-enumeration.ProgramField";
177    /** Sectors */
178    public static final String SECTORS = "odf-enumeration.Sectors";
179    /** Nature of course part */
180    public static final String ENSEIGNEMENT_NATURE = "odf-enumeration.EnseignementNature";
181    /** Category of nature of course part */
182    public static final String ENSEIGNEMENT_NATURE_CATEGORY = "odf-enumeration.EnseignementNatureCategory";
183    /** Skill */
184    public static final String SKILL = "odf-enumeration.Skill";
185    /** Skill set */
186    public static final String SKILL_SET = "odf-enumeration.SkillSet";
187    /** Attribute name for mention type in table refe degree */
188    public static final String DEGREE_MENTION_TYPE = "mentionType";
189    
190    private AmetysObjectResolver _resolver;
191
192    private ContentTypeExtensionPoint _cTypeEP;
193
194    private ContentTypesHelper _cTypeHelper;
195
196    private ODFHelper _odfHelper;
197
198    @Override
199    public void service(ServiceManager manager) throws ServiceException
200    {
201        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
202        _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
203        _cTypeHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
204        _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE);
205    } 
206    
207    /**
208     * Determines if the content type is a ODF table reference
209     * @param cTypeId The id of content type
210     * @return true if the content type is a ODF table reference
211     */
212    public boolean isTableReference(String cTypeId)
213    {
214        return _cTypeHelper.getAncestors(cTypeId).contains(ABSTRACT_TABLE_REF);
215    }
216    
217    /**
218     * Determines if the content is an entry of a ODF table reference
219     * @param content The content 
220     * @return <code>true</code> if the content is an entry of a ODF table reference
221     */
222    public boolean isTableReferenceEntry(Content content)
223    {
224        String[] cTypeIds = content.getTypes();
225        for (String cTypeId : cTypeIds)
226        {
227            if (isTableReference(cTypeId))
228            {
229                return true;
230            }
231        }
232        return false;
233    }
234    
235    /**
236     * Get the id of table references
237     * @return The content type's id
238     */
239    public Set<String> getTableReferenceIds()
240    {
241        Set<String> tableRefIds = new HashSet<>();
242        
243        for (String cTypeId : _cTypeEP.getExtensionsIds())
244        {
245            if (_cTypeHelper.getAncestors(cTypeId).contains(ABSTRACT_TABLE_REF))
246            {
247                tableRefIds.add(cTypeId);
248            }
249        }
250        return tableRefIds;
251    }
252    
253    /**
254     * Get the attribute definitions for table references for a given content type
255     * @param cTypeId The id of content type
256     * @return The attribute definitions for table references
257     */
258    public Map<String, ContentAttributeDefinition> getTableRefAttributeDefinitions(String cTypeId)
259    {
260        ContentType cType = _cTypeEP.getExtension(cTypeId);
261        return _getTableRefAttributes(cType);
262    }
263    
264    private Map<String, ContentAttributeDefinition> _getTableRefAttributes(ModelItemContainer modelItemContainer)
265    {
266        Map<String, ContentAttributeDefinition> tableRefAttributes = new LinkedHashMap<>();
267        
268        for (ModelItem modelItem : modelItemContainer.getModelItems())
269        {
270            if (modelItem instanceof ContentAttributeDefinition)
271            {
272                ContentAttributeDefinition attributeDefinition = (ContentAttributeDefinition) modelItem;
273                if (isTableReference(attributeDefinition.getContentTypeId()))
274                {
275                    tableRefAttributes.put(attributeDefinition.getPath(), attributeDefinition);
276                }
277            }
278            else if (modelItem instanceof ModelItemContainer && !(modelItem instanceof RepeaterDefinition))
279            {
280                // enumerated attributes in repeaters are not supported
281                tableRefAttributes.putAll(_getTableRefAttributes((ModelItemContainer) modelItem));
282            }
283        }
284        
285        return tableRefAttributes;
286    }
287    
288    /**
289     * Get the attribute definitions for table references for a given content type and a view
290     * @param cTypeId The id of content type
291     * @param viewName the name of the view. Cannot be null.
292     * @return The attributes definitions for table references
293     */
294    public Map<String, ContentAttributeDefinition> getTableRefAttributeDefinitions(String cTypeId, String viewName)
295    {
296        ContentType cType = _cTypeEP.getExtension(cTypeId);
297        
298        View view = cType.getView(viewName);
299        return _getTableRefAttributes(view);
300    }
301    
302    private Map<String, ContentAttributeDefinition> _getTableRefAttributes(ViewItemContainer viewItemContainer)
303    {
304        Map<String, ContentAttributeDefinition> tableRefAttributes = new LinkedHashMap<>();
305        
306        for (ViewItem viewItem : viewItemContainer.getViewItems())
307        {
308            if (viewItem instanceof ViewElement)
309            {
310                ElementDefinition definition = ((ViewElement) viewItem).getDefinition();
311                if (definition instanceof ContentAttributeDefinition)
312                {
313                    tableRefAttributes.put(definition.getPath(), (ContentAttributeDefinition) definition);
314                }
315            }
316            else if (viewItem instanceof ViewItemContainer)
317            {
318                tableRefAttributes.putAll(_getTableRefAttributes((ViewItemContainer) viewItem));
319            }
320        }
321        
322        return tableRefAttributes;
323    }
324    
325    /**
326     * Get the content type for mention for this degree
327     * @param degreeIds The ids of degrees
328     * @return A map with the id of content type or null if there is no mention for this degree
329     */
330    @Callable
331    public Map<String, String> getMentionContentTypes(List<String> degreeIds)
332    {
333        Map<String, String> mentionTypes = new HashMap<>();
334        for (String degreeId : degreeIds)
335        {
336            mentionTypes.put(degreeId, getMentionForDegree(degreeId));
337        }
338        return mentionTypes;
339    }
340    
341    /**
342     * Get the available BUT training paths for a BUT mention
343     * @param mentionId the id of BUT mention
344     * @return the BUT training paths
345     */
346    public ContentValue[] getBUTParcoursForMention(String mentionId)
347    {
348        Content content = _resolver.resolveById(mentionId);
349        if (_cTypeHelper.isInstanceOf(content, MENTION_BUT))
350        {
351            return content.getValue(MENTION_BUT_ATTRIBUTE_PARCOURS, false, new ContentValue[0]);
352        }
353        return new ContentValue[0];
354    }
355    
356    /**
357     * Get the available BUT training paths for a content
358     * @param contentId the content's id
359     * @param mentionId the id of mention. Can be null or empty
360     * @return the available BUT training paths
361     */
362    @Callable
363    public List<Map<String, String>> getBUTParcoursItems(String contentId, String mentionId)
364    {
365        ContentValue[] values = null;
366        if (StringUtils.isNotEmpty(mentionId))
367        {
368            // Get items from mention
369            values = getBUTParcoursForMention(mentionId);
370        }
371        else
372        {
373            // Get items from parent program
374            values = getBUTParcoursItems(contentId);
375        }
376        
377        if (values != null)
378        {
379            return Arrays.stream(values)
380                .map(ContentValue::getContent)
381                .map(c -> Map.of("id", c.getId(), "title", c.getTitle()))
382                .collect(Collectors.toList());
383        }
384        else
385        {
386            return List.of();
387        }
388    }
389    /**
390     * Get the available BUT training paths for a {@link ProgramItem}
391     * @param programItemId the id of program item
392     * @return the BUT training paths
393     */
394    public ContentValue[] getBUTParcoursItems(String programItemId)
395    {
396        Content content = _resolver.resolveById(programItemId);
397        
398        // Get first parent program
399        Set<Program> parentPrograms = _odfHelper.getParentPrograms((AbstractProgram) content);
400        if (!parentPrograms.isEmpty())
401        {
402            Program program = parentPrograms.iterator().next();
403            
404            if (isBUTDiploma(program))
405            {
406                return Optional.ofNullable(program.<ContentValue>getValue(AbstractProgram.MENTION))
407                    .map(ContentValue::getContentIfExists)
408                    .filter(Optional::isPresent)
409                    .map(Optional::get)
410                    .map(c -> c.<ContentValue[]>getValue(MENTION_BUT_ATTRIBUTE_PARCOURS))
411                    .orElse(new ContentValue[0]);
412                
413            }
414        }
415        
416        // No BUT training paths
417        return new ContentValue[0];
418    }
419    
420    /**
421     * Determines if the program is a BUT diploma
422     * @param program the program
423     * @return true if the program has a BUT
424     */
425    public boolean isBUTDiploma(Program program)
426    {
427        String mentionType = getMentionType(program);
428        return mentionType != null && MENTION_BUT.equals(mentionType);
429    }
430    
431    /**
432     * Get the type of mention for a program
433     * @param program the program
434     * @return the type of mention or null if there is no mention for this program
435     */
436    public String getMentionType(Program program)
437    {
438        return Optional.ofNullable(program.<ContentValue>getValue(AbstractProgram.DEGREE))
439                .map(ContentValue::getContentIfExists)
440                .filter(Optional::isPresent)
441                .map(Optional::get)
442                .map(c -> c.<String>getValue(DEGREE_MENTION_TYPE))
443                .orElse(null);
444    }
445    
446    /**
447     * Get the mention for a given degree.
448     * @param degreeId The degree ID
449     * @return The associated mention reference table, null if there isn't
450     */
451    public String getMentionForDegree(String degreeId)
452    {
453        try
454        {
455            Content content = _resolver.resolveById(degreeId);
456            return content.getValue(DEGREE_MENTION_TYPE);
457        }
458        catch (UnknownAmetysObjectException e)
459        {
460            // Nothing to do
461        }
462        return null;
463    }
464    
465    /**
466     * Get the CDM-fr value associated with the given code
467     * @param tableRefId The id of content type
468     * @param code The code
469     * @return The CDM-fr value or empty string if not found
470     */
471    public String getCDMfrValue (String tableRefId, String code)
472    {
473        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefId);
474        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.CODE, Operator.EQ, code);
475        
476        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
477        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
478        AmetysObjectIterator<Content> it = contents.iterator();
479        
480        if (it.hasNext())
481        {
482            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(it.next());
483            return entry.getCdmValue();
484        }
485        
486        return "";
487    }
488    
489    /**
490     * Get all items of an enumeration and their label
491     * @param tableRefId The id of content type
492     * @return items of enumeration
493     */
494    public List<OdfReferenceTableEntry> getItems (String tableRefId)
495    {
496        return getItems(tableRefId, new SortField[0]);
497    }
498    
499    /**
500     * Get all items of an enumeration and their label, sorted by given fields
501     * @param tableRefId The id of content type
502     * @param sortFields The sort fields to order results
503     * @return items of enumeration
504     */
505    public List<OdfReferenceTableEntry> getItems (String tableRefId, SortField... sortFields)
506    {
507        return getItems(tableRefId, true, sortFields);
508    }
509    
510    /**
511     * Get all items of an enumeration and their label, sorted by given fields
512     * @param tableRefId The id of content type
513     * @param includeArchived <code>true</code> to include archived entries
514     * @param sortFields The sort fields to order results
515     * @return items of enumeration
516     */
517    public List<OdfReferenceTableEntry> getItems (String tableRefId, boolean includeArchived, SortField... sortFields)
518    {
519        Expression expr = new ContentTypeExpression(Operator.EQ, tableRefId);
520        if (!includeArchived)
521        {
522            Expression notArchivedExpr = new NotExpression(new BooleanExpression(OdfReferenceTableEntry.ARCHIVED, true));
523            expr = new AndExpression(expr, notArchivedExpr);
524        }
525        
526        SortCriteria sortCriteria = new SortCriteria();
527        for (SortField sortField : sortFields)
528        {
529            sortCriteria.addCriterion(sortField.getName(), sortField.getAscending(), sortField.getNormalize());
530        }
531        
532        String xpathQuery = ContentQueryHelper.getContentXPathQuery(expr, sortCriteria);
533        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
534        
535        return contents.stream().map(c -> new OdfReferenceTableEntry(c)).collect(Collectors.toList());
536    }
537    
538    /**
539     * Returns the label of an reference table entry
540     * @param contentId The content id
541     * @param lang The requested language of label
542     * @return the item label or <code>null</code> if not found
543     */
544    public String getItemLabel(String contentId, String lang)
545    {
546        try
547        {
548            Content content = _resolver.resolveById(contentId);
549            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
550            return entry.getLabel(lang);
551        }
552        catch (UnknownAmetysObjectException e)
553        {
554            return null;
555        }
556    }
557    
558    /**
559     * Returns the label of an reference table entry
560     * @param tableRefId The id of content type (useless)
561     * @param contentId The content id
562     * @param lang The requested language of label
563     * @return the item label or <code>null</code> if not found
564     * @deprecated Use {@link #getItemLabel(String, String)} instead
565     */
566    @Deprecated
567    public String getItemLabel (String tableRefId, String contentId, String lang)
568    {
569        return getItemLabel(contentId, lang);
570    }
571
572    /**
573     * Returns the CMD value of an reference table entry
574     * @param contentId The content id
575     * @param returnCodeIfEmpty <code>true</code> to return the code if CDM-fr value is empty
576     * @return the CDM-fr value or empty value if not found
577     */
578    public String getItemCDMfrValue(String contentId, boolean returnCodeIfEmpty)
579    {
580        if (StringUtils.isEmpty(contentId))
581        {
582            return "";
583        }
584        
585        Content content = _resolver.resolveById(contentId);
586        OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
587        
588        String cdmValue = entry.getCdmValue();
589        
590        if (StringUtils.isEmpty(cdmValue) && returnCodeIfEmpty)
591        {
592            return entry.getCode();
593        }
594        return cdmValue;
595    }
596    
597    /**
598     * Returns the CMD value of an reference table entry
599     * @param tableRefId The id of content type (useless)
600     * @param contentId The content id
601     * @param returnCodeIfEmpty <code>true</code> to return the code if CDM-fr value is empty
602     * @return the CDM-fr value or empty value if not found
603     * @deprecated Use {@link #getItemCDMfrValue(String, boolean)} instead
604     */
605    @Deprecated
606    public String getItemCDMfrValue (String tableRefId, String contentId, boolean returnCodeIfEmpty)
607    {
608        return getItemCDMfrValue(contentId, returnCodeIfEmpty);
609    }
610    
611    /**
612     * Returns the code of an reference table entry from its CDM value
613     * @param contentId The id of content
614     * @return the code or empty value if not found
615     */
616    public String getItemCode(String contentId)
617    {
618        if (StringUtils.isEmpty(contentId))
619        {
620            return "";
621        }
622        
623        try
624        {
625            Content content = _resolver.resolveById(contentId);
626            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
627            return entry.getCode();
628        }
629        catch (UnknownAmetysObjectException e)
630        {
631            return "";
632        }
633    }
634    
635    /**
636     * Returns the code of an reference table entry from its CDM value
637     * @param tableRefId The id of content type (useless)
638     * @param contentId The id of content
639     * @return the code or empty value if not found
640     * @deprecated Use {@link #getItemCode(String)} instead
641     */
642    @Deprecated
643    public String getItemCode (String tableRefId, String contentId)
644    {
645        return getItemCode(contentId);
646    }
647    
648    /**
649     * Returns the code of an reference table entry from its CDM value
650     * @param tableRefId The id of content type
651     * @param cdmValue The CDM-fr value
652     * @return the code or <code>null</code> if not found
653     */
654    public String getItemCodeFromCDM (String tableRefId, String cdmValue)
655    {
656        ContentTypeExpression cTypeExpr = _getContentTypeExpression(tableRefId);
657        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.CDM_VALUE, Operator.EQ, cdmValue);
658        
659        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
660        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
661        AmetysObjectIterator<Content> it = contents.iterator();
662        
663        if (it.hasNext())
664        {
665            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(it.next());
666            return entry.getCode();
667        }
668        return null;
669    }
670
671    /**
672     * Returns the entry of an reference table entry from its cdmValue
673     * @param tableRefId The id of content type
674     * @param cdmValue The CDM-fr value
675     * @return the entry or <code>null</code> if not found
676     */
677    public OdfReferenceTableEntry getItemFromCDM(String tableRefId, String cdmValue)
678    {
679        ContentTypeExpression cTypeExpr = _getContentTypeExpression(tableRefId);
680        StringExpression cdmExpr = new StringExpression(OdfReferenceTableEntry.CDM_VALUE, Operator.EQ, cdmValue);
681        
682        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, cdmExpr));
683
684        return _resolver.<Content>query(xpathQuery).stream()
685            .findFirst()
686            .map(OdfReferenceTableEntry::new)
687            .orElse(null);
688    }
689    
690    /**
691     * Returns the entry of an reference table entry from its code
692     * @param tableRefId The id of content type
693     * @param code The code
694     * @return the entry or <code>null</code> if not found
695     */
696    public OdfReferenceTableEntry getItemFromCode(String tableRefId, String code)
697    {
698        ContentTypeExpression cTypeExpr = _getContentTypeExpression(tableRefId);
699        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.CODE, Operator.EQ, code);
700        
701        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
702        
703        return _resolver.<Content>query(xpathQuery).stream()
704            .findFirst()
705            .map(OdfReferenceTableEntry::new)
706            .orElse(null);
707    }
708    
709    /**
710     * Returns the entry of a reference table entry from its code for the connector (Apogée, Pégase...)
711     * @param tableRefId The id of content type
712     * @param connectorCode The code
713     * @param codeFieldName The field name containing the connector code
714     * @return the entry or <code>null</code> if not found
715     */
716    public OdfReferenceTableEntry getItemFromConnector(String tableRefId, String connectorCode, String codeFieldName)
717    {
718        ContentTypeExpression cTypeExpr = _getContentTypeExpression(tableRefId);
719        StringExpression cdmExpr = new StringExpression(codeFieldName, Operator.EQ, connectorCode);
720        
721        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, cdmExpr));
722
723        return _resolver.<Content>query(xpathQuery).stream()
724            .findFirst()
725            .map(OdfReferenceTableEntry::new)
726            .orElse(null);
727    }
728    
729    private ContentTypeExpression _getContentTypeExpression(String tableRefId)
730    {
731        Set<String> tableRefids;
732        if (tableRefId.equals(OdfReferenceTableHelper.ABSTRACT_MENTION))
733        {
734            tableRefids = _cTypeEP.getSubTypes(tableRefId);
735        }
736        else
737        {
738            tableRefids = Collections.singleton(tableRefId);
739        }
740        
741        return new ContentTypeExpression(Operator.EQ, tableRefids.toArray(new String[tableRefids.size()]));
742    }
743
744    /**
745     * Returns the reference table entry from its CDM value
746     * @param contentId The id of content
747     * @return the item as an {@link OdfReferenceTableEntry} or null if not found
748     */
749    public OdfReferenceTableEntry getItem(String contentId)
750    {
751        try
752        {
753            Content content = _resolver.resolveById(contentId);
754            return new OdfReferenceTableEntry(content);
755        }
756        catch (UnknownAmetysObjectException e)
757        {
758            // Can be an empty ID or an invalid ID (workspace or simply deleted element)
759            return null;
760        }
761    }
762
763    /**
764     * SAX items of a reference table
765     * @param contentHandler The content handler to sax into
766     * @param tableRefId The id of reference table
767     * @throws SAXException if an error occurred while saxing
768     */
769    public void saxItems (ContentHandler contentHandler, String tableRefId) throws SAXException
770    {
771        saxItems(contentHandler, tableRefId, null);
772    }
773    
774    /**
775     * SAX items of a reference table
776     * @param contentHandler the content handler to sax into
777     * @param attributeDefinition the metadata definition
778     * @throws SAXException if an error occurs while saxing
779     */
780    public void saxItems (ContentHandler contentHandler, ContentAttributeDefinition attributeDefinition) throws SAXException
781    {
782        saxItems(contentHandler, attributeDefinition, null);
783    }
784    
785    /**
786     * SAX items of a reference table
787     * @param contentHandler The content handler to sax into
788     * @param tableRefId The id of reference table
789     * @param lang the language to use to display items, can be <code>null</code> then odf language is used
790     * @throws SAXException if an error occurred while saxing
791     */
792    public void saxItems (ContentHandler contentHandler, String tableRefId, String lang) throws SAXException
793    {
794        _saxItems(contentHandler, new AttributesImpl(), tableRefId, lang);
795    }
796    
797    /**
798     * SAX items of a reference table
799     * @param contentHandler the content handler to sax into
800     * @param attributeDefinition the metadata definition
801     * @param lang the language to use to display items, can be <code>null</code> then odf language is used
802     * @throws SAXException if an error occurs while saxing
803     */
804    public void saxItems(ContentHandler contentHandler, ContentAttributeDefinition attributeDefinition, String lang) throws SAXException
805    {
806        String cTypeId = attributeDefinition.getContentTypeId();
807        if (cTypeId.startsWith("odf-enumeration."))
808        {
809            AttributesImpl attrs = new AttributesImpl();
810            attrs.addCDATAAttribute("metadataName", attributeDefinition.getName());
811            attrs.addCDATAAttribute("metadataPath", attributeDefinition.getPath());
812            
813            _saxItems(contentHandler, attrs, cTypeId, lang);
814        }
815    }
816    
817    private void _saxItems(ContentHandler contentHandler, AttributesImpl rootAttrs, String tableRefId, String lang) throws SAXException
818    {
819        String langToUse = lang != null ? lang : Config.getInstance().getValue("odf.programs.lang");
820        boolean hasShortLabel = _cTypeEP.getExtension(tableRefId).hasModelItem("shortLabel");
821        
822        rootAttrs.addCDATAAttribute("contentTypeId", tableRefId);
823        XMLUtils.startElement(contentHandler, "items", rootAttrs);
824        
825        List<OdfReferenceTableEntry> entries = getItems(tableRefId);
826        for (OdfReferenceTableEntry entry : entries)
827        {
828            AttributesImpl valueAttrs = new AttributesImpl();
829            valueAttrs.addCDATAAttribute("id", entry.getId());
830            valueAttrs.addCDATAAttribute("order", entry.getOrder().toString());
831            valueAttrs.addCDATAAttribute("code", entry.getCode());
832            valueAttrs.addCDATAAttribute("cdmValue", entry.getCdmValue());
833            valueAttrs.addCDATAAttribute("archived", entry.isArchived().toString());
834            if (hasShortLabel)
835            {
836                valueAttrs.addCDATAAttribute("shortLabel", entry.getContent().getValue("shortLabel", false, StringUtils.EMPTY));
837            }
838            
839            XMLUtils.createElement(contentHandler, "item", valueAttrs, entry.getLabel(langToUse));
840        }
841        
842        XMLUtils.endElement(contentHandler, "items");
843    }
844    
845    /**
846     * This class represents a sort field for reference table.
847     *
848     */
849    public static final class SortField
850    {
851        private String _name;
852        private boolean _ascending;
853        private boolean _normalize;
854
855        /**
856         * Create a sort field
857         * @param name the name of field to sort on
858         * @param ascending <code>true</code> to sort in ascending order, <code>false</code> otherwise
859         */
860        public SortField(String name, boolean ascending)
861        {
862            this(name, ascending, false);
863        }
864        
865        /**
866         * Create a sort field
867         * @param name the name of field to sort on
868         * @param ascending <code>true</code> to sort in ascending order, <code>false</code> otherwise
869         * @param normalize <code>true</code> to normalize string properties (remove accents and lower case)
870         */
871        public SortField(String name, boolean ascending, boolean normalize)
872        {
873            _name = name;
874            _ascending = ascending;
875            _normalize = normalize;
876        }
877
878        /**
879         * Get the name of the sort field
880         * @return the name of the sort field
881         */
882        public String getName()
883        {
884            return _name;
885        }
886
887        /**
888         * Get the order for sorting results
889         * @return <code>true</code> to sort results in ascending order, <code>false</code> otherwise
890         */
891        public boolean getAscending()
892        {
893            return _ascending;
894        }
895        
896        /**
897         * Return the normalize status for this sort field
898         * @return <code>true</code> if string properties should be normalized (remove accents and lower case)
899         */
900        public boolean getNormalize()
901        {
902            return _normalize;
903        }
904
905    }
906}