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    /** Attribute name for mention type in table refe degree */
184    public static final String DEGREE_MENTION_TYPE = "mentionType";
185    
186    private AmetysObjectResolver _resolver;
187
188    private ContentTypeExtensionPoint _cTypeEP;
189
190    private ContentTypesHelper _cTypeHelper;
191
192    private ODFHelper _odfHelper;
193
194    @Override
195    public void service(ServiceManager manager) throws ServiceException
196    {
197        _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE);
198        _cTypeEP = (ContentTypeExtensionPoint) manager.lookup(ContentTypeExtensionPoint.ROLE);
199        _cTypeHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE);
200        _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE);
201    }
202    
203    /**
204     * Determines if the content type is a ODF table reference
205     * @param cTypeId The id of content type
206     * @return true if the content type is a ODF table reference
207     */
208    public boolean isTableReference(String cTypeId)
209    {
210        return _cTypeHelper.getAncestors(cTypeId).contains(ABSTRACT_TABLE_REF);
211    }
212    
213    /**
214     * Determines if the content is an entry of a ODF table reference
215     * @param content The content
216     * @return <code>true</code> if the content is an entry of a ODF table reference
217     */
218    public boolean isTableReferenceEntry(Content content)
219    {
220        String[] cTypeIds = content.getTypes();
221        for (String cTypeId : cTypeIds)
222        {
223            if (isTableReference(cTypeId))
224            {
225                return true;
226            }
227        }
228        return false;
229    }
230    
231    /**
232     * Get the id of table references
233     * @return The content type's id
234     */
235    public Set<String> getTableReferenceIds()
236    {
237        Set<String> tableRefIds = new HashSet<>();
238        
239        for (String cTypeId : _cTypeEP.getExtensionsIds())
240        {
241            if (_cTypeHelper.getAncestors(cTypeId).contains(ABSTRACT_TABLE_REF))
242            {
243                tableRefIds.add(cTypeId);
244            }
245        }
246        return tableRefIds;
247    }
248    
249    /**
250     * Get the attribute definitions for table references for a given content type
251     * @param cTypeId The id of content type
252     * @return The attribute definitions for table references
253     */
254    public Map<String, ContentAttributeDefinition> getTableRefAttributeDefinitions(String cTypeId)
255    {
256        ContentType cType = _cTypeEP.getExtension(cTypeId);
257        return _getTableRefAttributes(cType);
258    }
259    
260    private Map<String, ContentAttributeDefinition> _getTableRefAttributes(ModelItemContainer modelItemContainer)
261    {
262        Map<String, ContentAttributeDefinition> tableRefAttributes = new LinkedHashMap<>();
263        
264        for (ModelItem modelItem : modelItemContainer.getModelItems())
265        {
266            if (modelItem instanceof ContentAttributeDefinition)
267            {
268                ContentAttributeDefinition attributeDefinition = (ContentAttributeDefinition) modelItem;
269                if (isTableReference(attributeDefinition.getContentTypeId()))
270                {
271                    tableRefAttributes.put(attributeDefinition.getPath(), attributeDefinition);
272                }
273            }
274            else if (modelItem instanceof ModelItemContainer && !(modelItem instanceof RepeaterDefinition))
275            {
276                // enumerated attributes in repeaters are not supported
277                tableRefAttributes.putAll(_getTableRefAttributes((ModelItemContainer) modelItem));
278            }
279        }
280        
281        return tableRefAttributes;
282    }
283    
284    /**
285     * Get the attribute definitions for table references for a given content type and a view
286     * @param cTypeId The id of content type
287     * @param viewName the name of the view. Cannot be null.
288     * @return The attributes definitions for table references
289     */
290    public Map<String, ContentAttributeDefinition> getTableRefAttributeDefinitions(String cTypeId, String viewName)
291    {
292        ContentType cType = _cTypeEP.getExtension(cTypeId);
293        
294        View view = cType.getView(viewName);
295        return _getTableRefAttributes(view);
296    }
297    
298    private Map<String, ContentAttributeDefinition> _getTableRefAttributes(ViewItemContainer viewItemContainer)
299    {
300        Map<String, ContentAttributeDefinition> tableRefAttributes = new LinkedHashMap<>();
301        
302        for (ViewItem viewItem : viewItemContainer.getViewItems())
303        {
304            if (viewItem instanceof ViewElement)
305            {
306                ElementDefinition definition = ((ViewElement) viewItem).getDefinition();
307                if (definition instanceof ContentAttributeDefinition)
308                {
309                    tableRefAttributes.put(definition.getPath(), (ContentAttributeDefinition) definition);
310                }
311            }
312            else if (viewItem instanceof ViewItemContainer)
313            {
314                tableRefAttributes.putAll(_getTableRefAttributes((ViewItemContainer) viewItem));
315            }
316        }
317        
318        return tableRefAttributes;
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        for (String degreeId : degreeIds)
331        {
332            mentionTypes.put(degreeId, getMentionForDegree(degreeId));
333        }
334        return mentionTypes;
335    }
336    
337    /**
338     * Get the available BUT training paths for a BUT mention
339     * @param mentionId the id of BUT mention
340     * @return the BUT training paths
341     */
342    public ContentValue[] getBUTParcoursForMention(String mentionId)
343    {
344        Content content = _resolver.resolveById(mentionId);
345        if (_cTypeHelper.isInstanceOf(content, MENTION_BUT))
346        {
347            return content.getValue(MENTION_BUT_ATTRIBUTE_PARCOURS, false, new ContentValue[0]);
348        }
349        return new ContentValue[0];
350    }
351    
352    /**
353     * Get the available BUT training paths for a content
354     * @param contentId the content's id
355     * @param mentionId the id of mention. Can be null or empty
356     * @return the available BUT training paths
357     */
358    @Callable
359    public List<Map<String, String>> getBUTParcoursItems(String contentId, String mentionId)
360    {
361        ContentValue[] values = null;
362        if (StringUtils.isNotEmpty(mentionId))
363        {
364            // Get items from mention
365            values = getBUTParcoursForMention(mentionId);
366        }
367        else
368        {
369            // Get items from parent program
370            values = getBUTParcoursItems(contentId);
371        }
372        
373        if (values != null)
374        {
375            return Arrays.stream(values)
376                .map(ContentValue::getContent)
377                .map(c -> Map.of("id", c.getId(), "title", c.getTitle()))
378                .collect(Collectors.toList());
379        }
380        else
381        {
382            return List.of();
383        }
384    }
385    /**
386     * Get the available BUT training paths for a {@link ProgramItem}
387     * @param programItemId the id of program item
388     * @return the BUT training paths
389     */
390    public ContentValue[] getBUTParcoursItems(String programItemId)
391    {
392        Content content = _resolver.resolveById(programItemId);
393        
394        // Get first parent program
395        Set<Program> parentPrograms = _odfHelper.getParentPrograms((AbstractProgram) content);
396        if (!parentPrograms.isEmpty())
397        {
398            Program program = parentPrograms.iterator().next();
399            
400            if (isBUTDiploma(program))
401            {
402                return Optional.ofNullable(program.<ContentValue>getValue(AbstractProgram.MENTION))
403                    .flatMap(ContentValue::getContentIfExists)
404                    .map(c -> c.<ContentValue[]>getValue(MENTION_BUT_ATTRIBUTE_PARCOURS))
405                    .orElse(new ContentValue[0]);
406                
407            }
408        }
409        
410        // No BUT training paths
411        return new ContentValue[0];
412    }
413    
414    /**
415     * Determines if the program is a BUT diploma
416     * @param program the program
417     * @return true if the program has a BUT
418     */
419    public boolean isBUTDiploma(Program program)
420    {
421        String mentionType = getMentionType(program);
422        return mentionType != null && MENTION_BUT.equals(mentionType);
423    }
424    
425    /**
426     * Get the type of mention for a program
427     * @param program the program
428     * @return the type of mention or null if there is no mention for this program
429     */
430    public String getMentionType(Program program)
431    {
432        return Optional.ofNullable(program.<ContentValue>getValue(AbstractProgram.DEGREE))
433                .flatMap(ContentValue::getContentIfExists)
434                .map(c -> c.<String>getValue(DEGREE_MENTION_TYPE))
435                .orElse(null);
436    }
437    
438    /**
439     * Get the mention for a given degree.
440     * @param degreeId The degree ID
441     * @return The associated mention reference table, null if there isn't
442     */
443    public String getMentionForDegree(String degreeId)
444    {
445        try
446        {
447            Content content = _resolver.resolveById(degreeId);
448            return content.getValue(DEGREE_MENTION_TYPE);
449        }
450        catch (UnknownAmetysObjectException e)
451        {
452            // Nothing to do
453        }
454        return null;
455    }
456    
457    /**
458     * Get the CDM-fr value associated with the given code
459     * @param tableRefId The id of content type
460     * @param code The code
461     * @return The CDM-fr value or empty string if not found
462     */
463    public String getCDMfrValue (String tableRefId, String code)
464    {
465        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefId);
466        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.CODE, Operator.EQ, code);
467        
468        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
469        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
470        AmetysObjectIterator<Content> it = contents.iterator();
471        
472        if (it.hasNext())
473        {
474            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(it.next());
475            return entry.getCdmValue();
476        }
477        
478        return "";
479    }
480    
481    /**
482     * Get all items of an enumeration and their label
483     * @param tableRefId The id of content type
484     * @return items of enumeration
485     */
486    public List<OdfReferenceTableEntry> getItems (String tableRefId)
487    {
488        return getItems(tableRefId, new SortField[0]);
489    }
490    
491    /**
492     * Get all items of an enumeration and their label, sorted by given fields
493     * @param tableRefId The id of content type
494     * @param sortFields The sort fields to order results
495     * @return items of enumeration
496     */
497    public List<OdfReferenceTableEntry> getItems (String tableRefId, SortField... sortFields)
498    {
499        return getItems(tableRefId, true, sortFields);
500    }
501    
502    /**
503     * Get all items of an enumeration and their label, sorted by given fields
504     * @param tableRefId The id of content type
505     * @param includeArchived <code>true</code> to include archived entries
506     * @param sortFields The sort fields to order results
507     * @return items of enumeration
508     */
509    public List<OdfReferenceTableEntry> getItems (String tableRefId, boolean includeArchived, SortField... sortFields)
510    {
511        Expression expr = new ContentTypeExpression(Operator.EQ, tableRefId);
512        if (!includeArchived)
513        {
514            Expression notArchivedExpr = new NotExpression(new BooleanExpression(OdfReferenceTableEntry.ARCHIVED, true));
515            expr = new AndExpression(expr, notArchivedExpr);
516        }
517        
518        SortCriteria sortCriteria = new SortCriteria();
519        for (SortField sortField : sortFields)
520        {
521            sortCriteria.addCriterion(sortField.getName(), sortField.getAscending(), sortField.getNormalize());
522        }
523        
524        String xpathQuery = ContentQueryHelper.getContentXPathQuery(expr, sortCriteria);
525        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
526        
527        return contents.stream().map(OdfReferenceTableEntry::new).collect(Collectors.toList());
528    }
529    
530    /**
531     * Returns the label of an reference table entry
532     * @param contentId The content id
533     * @param lang The requested language of label
534     * @return the item label or <code>null</code> if not found
535     */
536    public String getItemLabel(String contentId, String lang)
537    {
538        try
539        {
540            Content content = _resolver.resolveById(contentId);
541            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
542            return entry.getLabel(lang);
543        }
544        catch (UnknownAmetysObjectException e)
545        {
546            return null;
547        }
548    }
549    
550    /**
551     * Returns the label of an reference table entry
552     * @param tableRefId The id of content type (useless)
553     * @param contentId The content id
554     * @param lang The requested language of label
555     * @return the item label or <code>null</code> if not found
556     * @deprecated Use {@link #getItemLabel(String, String)} instead
557     */
558    @Deprecated
559    public String getItemLabel (String tableRefId, String contentId, String lang)
560    {
561        return getItemLabel(contentId, lang);
562    }
563
564    /**
565     * Returns the CMD value of an reference table entry
566     * @param contentId The content id
567     * @param returnCodeIfEmpty <code>true</code> to return the code if CDM-fr value is empty
568     * @return the CDM-fr value or empty value if not found
569     */
570    public String getItemCDMfrValue(String contentId, boolean returnCodeIfEmpty)
571    {
572        if (StringUtils.isEmpty(contentId))
573        {
574            return "";
575        }
576        
577        Content content = _resolver.resolveById(contentId);
578        OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
579        
580        String cdmValue = entry.getCdmValue();
581        
582        if (StringUtils.isEmpty(cdmValue) && returnCodeIfEmpty)
583        {
584            return entry.getCode();
585        }
586        return cdmValue;
587    }
588    
589    /**
590     * Returns the CMD value of an reference table entry
591     * @param tableRefId The id of content type (useless)
592     * @param contentId The content id
593     * @param returnCodeIfEmpty <code>true</code> to return the code if CDM-fr value is empty
594     * @return the CDM-fr value or empty value if not found
595     * @deprecated Use {@link #getItemCDMfrValue(String, boolean)} instead
596     */
597    @Deprecated
598    public String getItemCDMfrValue (String tableRefId, String contentId, boolean returnCodeIfEmpty)
599    {
600        return getItemCDMfrValue(contentId, returnCodeIfEmpty);
601    }
602    
603    /**
604     * Returns the code of an reference table entry from its CDM value
605     * @param contentId The id of content
606     * @return the code or empty value if not found
607     */
608    public String getItemCode(String contentId)
609    {
610        if (StringUtils.isEmpty(contentId))
611        {
612            return "";
613        }
614        
615        try
616        {
617            Content content = _resolver.resolveById(contentId);
618            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
619            return entry.getCode();
620        }
621        catch (UnknownAmetysObjectException e)
622        {
623            return "";
624        }
625    }
626    
627    /**
628     * Returns the code of an reference table entry from its CDM value
629     * @param tableRefId The id of content type (useless)
630     * @param contentId The id of content
631     * @return the code or empty value if not found
632     * @deprecated Use {@link #getItemCode(String)} instead
633     */
634    @Deprecated
635    public String getItemCode (String tableRefId, String contentId)
636    {
637        return getItemCode(contentId);
638    }
639    
640    /**
641     * Returns the code of an reference table entry from its CDM value
642     * @param tableRefId The id of content type
643     * @param cdmValue The CDM-fr value
644     * @return the code or <code>null</code> if not found
645     */
646    public String getItemCodeFromCDM (String tableRefId, String cdmValue)
647    {
648        ContentTypeExpression cTypeExpr = _getContentTypeExpression(tableRefId);
649        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.CDM_VALUE, Operator.EQ, cdmValue);
650        
651        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
652        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
653        AmetysObjectIterator<Content> it = contents.iterator();
654        
655        if (it.hasNext())
656        {
657            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(it.next());
658            return entry.getCode();
659        }
660        return null;
661    }
662
663    /**
664     * Returns the entry of an reference table entry from its cdmValue
665     * @param tableRefId The id of content type
666     * @param cdmValue The CDM-fr value
667     * @return the entry or <code>null</code> if not found
668     */
669    public OdfReferenceTableEntry getItemFromCDM(String tableRefId, String cdmValue)
670    {
671        ContentTypeExpression cTypeExpr = _getContentTypeExpression(tableRefId);
672        StringExpression cdmExpr = new StringExpression(OdfReferenceTableEntry.CDM_VALUE, Operator.EQ, cdmValue);
673        
674        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, cdmExpr));
675
676        return _resolver.<Content>query(xpathQuery).stream()
677            .findFirst()
678            .map(OdfReferenceTableEntry::new)
679            .orElse(null);
680    }
681    
682    /**
683     * Returns the entry of an reference table entry from its code
684     * @param tableRefId The id of content type
685     * @param code The code
686     * @return the entry or <code>null</code> if not found
687     */
688    public OdfReferenceTableEntry getItemFromCode(String tableRefId, String code)
689    {
690        ContentTypeExpression cTypeExpr = _getContentTypeExpression(tableRefId);
691        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.CODE, Operator.EQ, code);
692        
693        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
694        
695        return _resolver.<Content>query(xpathQuery).stream()
696            .findFirst()
697            .map(OdfReferenceTableEntry::new)
698            .orElse(null);
699    }
700    
701    /**
702     * Returns the entry of a reference table entry from its code for the connector (Apogée, Pégase...)
703     * @param tableRefId The id of content type
704     * @param connectorCode The code
705     * @param codeFieldName The field name containing the connector code
706     * @return the entry or <code>null</code> if not found
707     */
708    public OdfReferenceTableEntry getItemFromConnector(String tableRefId, String connectorCode, String codeFieldName)
709    {
710        ContentTypeExpression cTypeExpr = _getContentTypeExpression(tableRefId);
711        StringExpression cdmExpr = new StringExpression(codeFieldName, Operator.EQ, connectorCode);
712        
713        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, cdmExpr));
714
715        return _resolver.<Content>query(xpathQuery).stream()
716            .findFirst()
717            .map(OdfReferenceTableEntry::new)
718            .orElse(null);
719    }
720    
721    private ContentTypeExpression _getContentTypeExpression(String tableRefId)
722    {
723        Set<String> tableRefids;
724        if (tableRefId.equals(OdfReferenceTableHelper.ABSTRACT_MENTION))
725        {
726            tableRefids = _cTypeEP.getSubTypes(tableRefId);
727        }
728        else
729        {
730            tableRefids = Collections.singleton(tableRefId);
731        }
732        
733        return new ContentTypeExpression(Operator.EQ, tableRefids.toArray(new String[tableRefids.size()]));
734    }
735
736    /**
737     * Returns the reference table entry from its CDM value
738     * @param contentId The id of content
739     * @return the item as an {@link OdfReferenceTableEntry} or null if not found
740     */
741    public OdfReferenceTableEntry getItem(String contentId)
742    {
743        try
744        {
745            Content content = _resolver.resolveById(contentId);
746            return new OdfReferenceTableEntry(content);
747        }
748        catch (UnknownAmetysObjectException e)
749        {
750            // Can be an empty ID or an invalid ID (workspace or simply deleted element)
751            return null;
752        }
753    }
754
755    /**
756     * SAX items of a reference table
757     * @param contentHandler The content handler to sax into
758     * @param tableRefId The id of reference table
759     * @throws SAXException if an error occurred while saxing
760     */
761    public void saxItems (ContentHandler contentHandler, String tableRefId) throws SAXException
762    {
763        saxItems(contentHandler, tableRefId, null);
764    }
765    
766    /**
767     * SAX items of a reference table
768     * @param contentHandler the content handler to sax into
769     * @param attributeDefinition the metadata definition
770     * @throws SAXException if an error occurs while saxing
771     */
772    public void saxItems (ContentHandler contentHandler, ContentAttributeDefinition attributeDefinition) throws SAXException
773    {
774        saxItems(contentHandler, attributeDefinition, null);
775    }
776    
777    /**
778     * SAX items of a reference table
779     * @param contentHandler The content handler to sax into
780     * @param tableRefId The id of reference table
781     * @param lang the language to use to display items, can be <code>null</code> then odf language is used
782     * @throws SAXException if an error occurred while saxing
783     */
784    public void saxItems (ContentHandler contentHandler, String tableRefId, String lang) throws SAXException
785    {
786        _saxItems(contentHandler, new AttributesImpl(), tableRefId, lang);
787    }
788    
789    /**
790     * SAX items of a reference table
791     * @param contentHandler the content handler to sax into
792     * @param attributeDefinition the metadata definition
793     * @param lang the language to use to display items, can be <code>null</code> then odf language is used
794     * @throws SAXException if an error occurs while saxing
795     */
796    public void saxItems(ContentHandler contentHandler, ContentAttributeDefinition attributeDefinition, String lang) throws SAXException
797    {
798        String cTypeId = attributeDefinition.getContentTypeId();
799        if (cTypeId.startsWith("odf-enumeration."))
800        {
801            AttributesImpl attrs = new AttributesImpl();
802            attrs.addCDATAAttribute("metadataName", attributeDefinition.getName());
803            attrs.addCDATAAttribute("metadataPath", attributeDefinition.getPath());
804            
805            _saxItems(contentHandler, attrs, cTypeId, lang);
806        }
807    }
808    
809    private void _saxItems(ContentHandler contentHandler, AttributesImpl rootAttrs, String tableRefId, String lang) throws SAXException
810    {
811        String langToUse = lang != null ? lang : Config.getInstance().getValue("odf.programs.lang");
812        boolean hasShortLabel = _cTypeEP.getExtension(tableRefId).hasModelItem("shortLabel");
813        
814        rootAttrs.addCDATAAttribute("contentTypeId", tableRefId);
815        XMLUtils.startElement(contentHandler, "items", rootAttrs);
816        
817        List<OdfReferenceTableEntry> entries = getItems(tableRefId);
818        for (OdfReferenceTableEntry entry : entries)
819        {
820            AttributesImpl valueAttrs = new AttributesImpl();
821            valueAttrs.addCDATAAttribute("id", entry.getId());
822            valueAttrs.addCDATAAttribute("order", entry.getOrder().toString());
823            valueAttrs.addCDATAAttribute("code", entry.getCode());
824            valueAttrs.addCDATAAttribute("cdmValue", entry.getCdmValue());
825            valueAttrs.addCDATAAttribute("archived", entry.isArchived().toString());
826            if (hasShortLabel)
827            {
828                valueAttrs.addCDATAAttribute("shortLabel", entry.getContent().getValue("shortLabel", false, StringUtils.EMPTY));
829            }
830            
831            XMLUtils.createElement(contentHandler, "item", valueAttrs, entry.getLabel(langToUse));
832        }
833        
834        XMLUtils.endElement(contentHandler, "items");
835    }
836    
837    /**
838     * This class represents a sort field for reference table.
839     *
840     */
841    public static final class SortField
842    {
843        private String _name;
844        private boolean _ascending;
845        private boolean _normalize;
846
847        /**
848         * Create a sort field
849         * @param name the name of field to sort on
850         * @param ascending <code>true</code> to sort in ascending order, <code>false</code> otherwise
851         */
852        public SortField(String name, boolean ascending)
853        {
854            this(name, ascending, false);
855        }
856        
857        /**
858         * Create a sort field
859         * @param name the name of field to sort on
860         * @param ascending <code>true</code> to sort in ascending order, <code>false</code> otherwise
861         * @param normalize <code>true</code> to normalize string properties (remove accents and lower case)
862         */
863        public SortField(String name, boolean ascending, boolean normalize)
864        {
865            _name = name;
866            _ascending = ascending;
867            _normalize = normalize;
868        }
869
870        /**
871         * Get the name of the sort field
872         * @return the name of the sort field
873         */
874        public String getName()
875        {
876            return _name;
877        }
878
879        /**
880         * Get the order for sorting results
881         * @return <code>true</code> to sort results in ascending order, <code>false</code> otherwise
882         */
883        public boolean getAscending()
884        {
885            return _ascending;
886        }
887        
888        /**
889         * Return the normalize status for this sort field
890         * @return <code>true</code> if string properties should be normalized (remove accents and lower case)
891         */
892        public boolean getNormalize()
893        {
894            return _normalize;
895        }
896
897    }
898}