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