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.cms.rights.ContentRightAssignmentContext;
048import org.ametys.core.ui.Callable;
049import org.ametys.odf.ODFHelper;
050import org.ametys.odf.ProgramItem;
051import org.ametys.odf.program.AbstractProgram;
052import org.ametys.odf.program.Program;
053import org.ametys.plugins.repository.AmetysObjectIterable;
054import org.ametys.plugins.repository.AmetysObjectIterator;
055import org.ametys.plugins.repository.AmetysObjectResolver;
056import org.ametys.plugins.repository.UnknownAmetysObjectException;
057import org.ametys.plugins.repository.model.RepeaterDefinition;
058import org.ametys.plugins.repository.query.SortCriteria;
059import org.ametys.plugins.repository.query.expression.AndExpression;
060import org.ametys.plugins.repository.query.expression.BooleanExpression;
061import org.ametys.plugins.repository.query.expression.Expression;
062import org.ametys.plugins.repository.query.expression.Expression.Operator;
063import org.ametys.plugins.repository.query.expression.NotExpression;
064import org.ametys.plugins.repository.query.expression.StringExpression;
065import org.ametys.runtime.config.Config;
066import org.ametys.runtime.model.ElementDefinition;
067import org.ametys.runtime.model.ModelItem;
068import org.ametys.runtime.model.ModelItemContainer;
069import org.ametys.runtime.model.View;
070import org.ametys.runtime.model.ViewElement;
071import org.ametys.runtime.model.ViewItem;
072import org.ametys.runtime.model.ViewItemContainer;
073import org.ametys.runtime.plugin.component.AbstractLogEnabled;
074
075/**
076 * This component handles ODF reference tables
077 *
078 */
079public class OdfReferenceTableHelper extends AbstractLogEnabled implements Component, Serviceable
080{
081    /** Avalon Role */
082    public static final String ROLE = OdfReferenceTableHelper.class.getName();
083 
084    /** Abstract table ref */
085    public static final String ABSTRACT_TABLE_REF = "odf-enumeration.AbstractTableRef";
086    /** Domain */
087    public static final String DOMAIN = "odf-enumeration.Domain";
088    /** Degree */
089    public static final String DEGREE = "odf-enumeration.Degree";
090    /** Level */
091    public static final String LEVEL = "odf-enumeration.Level";
092    /** Program type. */
093    public static final String PROGRAM_TYPE = "odf-enumeration.ProgramType";
094    /** Form of teaching */
095    public static final String FORMOFTEACHING_METHOD = "odf-enumeration.FormofteachingMethod";
096    /** Orgnization of teaching */
097    public static final String FORMOFTEACHING_ORG = "odf-enumeration.FormofteachingOrg";
098    /** Teaching method */
099    public static final String TEACHING_ACTIVITY = "odf-enumeration.TeachingActivity";
100    /** Type of training course */
101    public static final String INTERNSHIP = "odf-enumeration.Internship";
102    /** Distance learning modalities */
103    public static final String DISTANCE_LEARNING_MODALITIES = "odf-enumeration.DistanceLearningModalities";
104    /** Place */
105    public static final String PLACE = "odf-enumeration.Place";
106    /** Teaching term. */
107    public static final String TEACHING_TERM = "odf-enumeration.TeachingTerm";
108    /** Time slot */
109    public static final String TIME_SLOT = "odf-enumeration.TimeSlot";
110    /** Code ROME */
111    public static final String CODE_ROME = "odf-enumeration.CodeRome";
112    /** Code ERASMUS */
113    public static final String CODE_ERASMUS = "odf-enumeration.CodeErasmus";
114    /** Code DGESIP */
115    public static final String CODE_DGESIP = "odf-enumeration.CodeDgesip";
116    /** Code SISE */
117    public static final String CODE_SISE = "odf-enumeration.CodeSise";
118    /** Code Cite97 */
119    public static final String CODE_CITE97 = "odf-enumeration.CodeCite97";
120    /** Code FAP */
121    public static final String CODE_FAP = "odf-enumeration.CodeFap";
122    /** Code NSF */
123    public static final String CODE_NSF = "odf-enumeration.CodeNsf";
124    /** RNCP level */
125    public static final String RNCP_LEVEL = "odf-enumeration.RncpLevel";
126    /** Join orgunit*/
127    public static final String JOIN_ORGUNIT = "odf-enumeration.JoinOrgunit";
128    /** Mention licence */
129    public static final String ABSTRACT_MENTION = "odf-enumeration.Mention";
130    /** Mention BUT */
131    public static final String MENTION_BUT = "odf-enumeration.MentionBUT";
132    /** Mention BUT */
133    public static final String MENTION_BUT_ATTRIBUTE_PARCOURS = "parcours";
134    /** Mention licence */
135    public static final String MENTION_LICENCE = "odf-enumeration.MentionLicence";
136    /** Mention licence pro */
137    public static final String MENTION_LICENCEPRO = "odf-enumeration.MentionLicencepro";
138    /** Mention master */
139    public static final String MENTION_MASTER = "odf-enumeration.MentionMaster";
140    /** Abstract table ref for category */
141    public static final String ABSTRACT_TABLE_REF_CATEGORY = "odf-enumeration.AbstractTableRefCategory";
142    /** Apprenticeship contract */
143    public static final String APPRENTICESHIP_CONTRACT = "odf-enumeration.ApprenticeshipContract";
144    /** Available certification */
145    public static final String AVAILABLE_CERTIFICATION = "odf-enumeration.AvailableCertification";
146    /** Campus */
147    public static final String CAMPUS = "odf-enumeration.Campus";
148    /** Category for code Erasmus */
149    public static final String CODE_ERASMUS_CATEGORY = "odf-enumeration.CodeErasmusCategory";
150    /** Category for code FAP */
151    public static final String CODE_FAP_CATEGORY = "odf-enumeration.CodeFapCategory";
152    /** Nature of container */
153    public static final String CONTAINER_NATURE = "odf-enumeration.ContainerNature";
154    /** Nature of course */
155    public static final String COURSE_NATURE = "odf-enumeration.CourseNature";
156    /** Discipline */
157    public static final String DISCIPLINE = "odf-enumeration.Discipline";
158    /** Duration */
159    public static final String DURATION = "odf-enumeration.Duration";
160    /** ECTS */
161    public static final String ECTS = "odf-enumeration.Ects";
162    /** Foreign place */
163    public static final String FOREIGN_PLACE = "odf-enumeration.ForeignPlace";
164    /** International education */
165    public static final String INTERNATIONAL_EDUCATION = "odf-enumeration.InternationalEducation";
166    /** Language */
167    public static final String LANGUAGE = "odf-enumeration.Language";
168    /** OrgUnit type */
169    public static final String ORGUNIT_TYPE = "odf-enumeration.OrgUnitType";
170    /** Period */
171    public static final String PERIOD = "odf-enumeration.Period";
172    /** Period type */
173    public static final String PERIOD_TYPE = "odf-enumeration.PeriodType";
174    /** Person role */
175    public static final String PERSON_ROLE = "odf-enumeration.PersonRole";
176    /** Program field */
177    public static final String PROGRAM_FIELD = "odf-enumeration.ProgramField";
178    /** Sectors */
179    public static final String SECTORS = "odf-enumeration.Sectors";
180    /** Nature of course part */
181    public static final String ENSEIGNEMENT_NATURE = "odf-enumeration.EnseignementNature";
182    /** Category of nature of course part */
183    public static final String ENSEIGNEMENT_NATURE_CATEGORY = "odf-enumeration.EnseignementNatureCategory";
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 (rights = Callable.NO_CHECK_REQUIRED) // Used by mention widget. No sensitive information.
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 (rights = Callable.READ_ACCESS, paramIndex = 0, rightContext = ContentRightAssignmentContext.ID)
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                    .flatMap(ContentValue::getContentIfExists)
405                    .map(c -> c.<ContentValue[]>getValue(MENTION_BUT_ATTRIBUTE_PARCOURS))
406                    .orElse(new ContentValue[0]);
407                
408            }
409        }
410        
411        // No BUT training paths
412        return new ContentValue[0];
413    }
414    
415    /**
416     * Determines if the program is a BUT diploma
417     * @param program the program
418     * @return true if the program has a BUT
419     */
420    public boolean isBUTDiploma(Program program)
421    {
422        String mentionType = getMentionType(program);
423        return mentionType != null && MENTION_BUT.equals(mentionType);
424    }
425    
426    /**
427     * Get the type of mention for a program
428     * @param program the program
429     * @return the type of mention or null if there is no mention for this program
430     */
431    public String getMentionType(Program program)
432    {
433        return Optional.ofNullable(program.<ContentValue>getValue(AbstractProgram.DEGREE))
434                .flatMap(ContentValue::getContentIfExists)
435                .map(c -> c.<String>getValue(DEGREE_MENTION_TYPE))
436                .orElse(null);
437    }
438    
439    /**
440     * Get the mention for a given degree.
441     * @param degreeId The degree ID
442     * @return The associated mention reference table, null if there isn't
443     */
444    public String getMentionForDegree(String degreeId)
445    {
446        try
447        {
448            Content content = _resolver.resolveById(degreeId);
449            return content.getValue(DEGREE_MENTION_TYPE);
450        }
451        catch (UnknownAmetysObjectException e)
452        {
453            // Nothing to do
454        }
455        return null;
456    }
457    
458    /**
459     * Get the CDM-fr value associated with the given code
460     * @param tableRefId The id of content type
461     * @param code The code
462     * @return The CDM-fr value or empty string if not found
463     */
464    public String getCDMfrValue (String tableRefId, String code)
465    {
466        ContentTypeExpression cTypeExpr = new ContentTypeExpression(Operator.EQ, tableRefId);
467        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.CODE, Operator.EQ, code);
468        
469        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
470        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
471        AmetysObjectIterator<Content> it = contents.iterator();
472        
473        if (it.hasNext())
474        {
475            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(it.next());
476            return entry.getCdmValue();
477        }
478        
479        return "";
480    }
481    
482    /**
483     * Get all items of an enumeration and their label
484     * @param tableRefId The id of content type
485     * @return items of enumeration
486     */
487    public List<OdfReferenceTableEntry> getItems (String tableRefId)
488    {
489        return getItems(tableRefId, new SortField[0]);
490    }
491    
492    /**
493     * Get all items of an enumeration and their label, sorted by given fields
494     * @param tableRefId The id of content type
495     * @param sortFields The sort fields to order results
496     * @return items of enumeration
497     */
498    public List<OdfReferenceTableEntry> getItems (String tableRefId, SortField... sortFields)
499    {
500        return getItems(tableRefId, true, sortFields);
501    }
502    
503    /**
504     * Get all items of an enumeration and their label, sorted by given fields
505     * @param tableRefId The id of content type
506     * @param includeArchived <code>true</code> to include archived entries
507     * @param sortFields The sort fields to order results
508     * @return items of enumeration
509     */
510    public List<OdfReferenceTableEntry> getItems (String tableRefId, boolean includeArchived, SortField... sortFields)
511    {
512        Expression expr = new ContentTypeExpression(Operator.EQ, tableRefId);
513        if (!includeArchived)
514        {
515            Expression notArchivedExpr = new NotExpression(new BooleanExpression(OdfReferenceTableEntry.ARCHIVED, true));
516            expr = new AndExpression(expr, notArchivedExpr);
517        }
518        
519        SortCriteria sortCriteria = new SortCriteria();
520        for (SortField sortField : sortFields)
521        {
522            sortCriteria.addCriterion(sortField.getName(), sortField.getAscending(), sortField.getNormalize());
523        }
524        
525        String xpathQuery = ContentQueryHelper.getContentXPathQuery(expr, sortCriteria);
526        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
527        
528        return contents.stream().map(OdfReferenceTableEntry::new).collect(Collectors.toList());
529    }
530    
531    /**
532     * Returns the label of an reference table entry
533     * @param contentId The content id
534     * @param lang The requested language of label
535     * @return the item label or <code>null</code> if not found
536     */
537    public String getItemLabel(String contentId, String lang)
538    {
539        try
540        {
541            Content content = _resolver.resolveById(contentId);
542            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
543            return entry.getLabel(lang);
544        }
545        catch (UnknownAmetysObjectException e)
546        {
547            return null;
548        }
549    }
550    
551    /**
552     * Returns the label of an reference table entry
553     * @param tableRefId The id of content type (useless)
554     * @param contentId The content id
555     * @param lang The requested language of label
556     * @return the item label or <code>null</code> if not found
557     * @deprecated Use {@link #getItemLabel(String, String)} instead
558     */
559    @Deprecated
560    public String getItemLabel (String tableRefId, String contentId, String lang)
561    {
562        return getItemLabel(contentId, lang);
563    }
564
565    /**
566     * Returns the CMD value of an reference table entry
567     * @param contentId The content id
568     * @param returnCodeIfEmpty <code>true</code> to return the code if CDM-fr value is empty
569     * @return the CDM-fr value or empty value if not found
570     */
571    public String getItemCDMfrValue(String contentId, boolean returnCodeIfEmpty)
572    {
573        if (StringUtils.isEmpty(contentId))
574        {
575            return "";
576        }
577        
578        Content content = _resolver.resolveById(contentId);
579        OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
580        
581        String cdmValue = entry.getCdmValue();
582        
583        if (StringUtils.isEmpty(cdmValue) && returnCodeIfEmpty)
584        {
585            return entry.getCode();
586        }
587        return cdmValue;
588    }
589    
590    /**
591     * Returns the CMD value of an reference table entry
592     * @param tableRefId The id of content type (useless)
593     * @param contentId The content id
594     * @param returnCodeIfEmpty <code>true</code> to return the code if CDM-fr value is empty
595     * @return the CDM-fr value or empty value if not found
596     * @deprecated Use {@link #getItemCDMfrValue(String, boolean)} instead
597     */
598    @Deprecated
599    public String getItemCDMfrValue (String tableRefId, String contentId, boolean returnCodeIfEmpty)
600    {
601        return getItemCDMfrValue(contentId, returnCodeIfEmpty);
602    }
603    
604    /**
605     * Returns the code of an reference table entry from its CDM value
606     * @param contentId The id of content
607     * @return the code or empty value if not found
608     */
609    public String getItemCode(String contentId)
610    {
611        if (StringUtils.isEmpty(contentId))
612        {
613            return "";
614        }
615        
616        try
617        {
618            Content content = _resolver.resolveById(contentId);
619            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(content);
620            return entry.getCode();
621        }
622        catch (UnknownAmetysObjectException e)
623        {
624            return "";
625        }
626    }
627    
628    /**
629     * Returns the code of an reference table entry from its CDM value
630     * @param tableRefId The id of content type (useless)
631     * @param contentId The id of content
632     * @return the code or empty value if not found
633     * @deprecated Use {@link #getItemCode(String)} instead
634     */
635    @Deprecated
636    public String getItemCode (String tableRefId, String contentId)
637    {
638        return getItemCode(contentId);
639    }
640    
641    /**
642     * Returns the code of an reference table entry from its CDM value
643     * @param tableRefId The id of content type
644     * @param cdmValue The CDM-fr value
645     * @return the code or <code>null</code> if not found
646     */
647    public String getItemCodeFromCDM (String tableRefId, String cdmValue)
648    {
649        ContentTypeExpression cTypeExpr = _getContentTypeExpression(tableRefId);
650        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.CDM_VALUE, Operator.EQ, cdmValue);
651        
652        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
653        AmetysObjectIterable<Content> contents = _resolver.query(xpathQuery);
654        AmetysObjectIterator<Content> it = contents.iterator();
655        
656        if (it.hasNext())
657        {
658            OdfReferenceTableEntry entry = new OdfReferenceTableEntry(it.next());
659            return entry.getCode();
660        }
661        return null;
662    }
663
664    /**
665     * Returns the entry of an reference table entry from its cdmValue
666     * @param tableRefId The id of content type
667     * @param cdmValue The CDM-fr value
668     * @return the entry or <code>null</code> if not found
669     */
670    public OdfReferenceTableEntry getItemFromCDM(String tableRefId, String cdmValue)
671    {
672        ContentTypeExpression cTypeExpr = _getContentTypeExpression(tableRefId);
673        StringExpression cdmExpr = new StringExpression(OdfReferenceTableEntry.CDM_VALUE, Operator.EQ, cdmValue);
674        
675        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, cdmExpr));
676
677        return _resolver.<Content>query(xpathQuery).stream()
678            .findFirst()
679            .map(OdfReferenceTableEntry::new)
680            .orElse(null);
681    }
682    
683    /**
684     * Returns the entry of an reference table entry from its code
685     * @param tableRefId The id of content type
686     * @param code The code
687     * @return the entry or <code>null</code> if not found
688     */
689    public OdfReferenceTableEntry getItemFromCode(String tableRefId, String code)
690    {
691        ContentTypeExpression cTypeExpr = _getContentTypeExpression(tableRefId);
692        StringExpression codeExpr = new StringExpression(OdfReferenceTableEntry.CODE, Operator.EQ, code);
693        
694        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, codeExpr));
695        
696        return _resolver.<Content>query(xpathQuery).stream()
697            .findFirst()
698            .map(OdfReferenceTableEntry::new)
699            .orElse(null);
700    }
701    
702    /**
703     * Returns the entry of a reference table entry from its code for the connector (Apogée, Pégase...)
704     * @param tableRefId The id of content type
705     * @param connectorCode The code
706     * @param codeFieldName The field name containing the connector code
707     * @return the entry or <code>null</code> if not found
708     */
709    public OdfReferenceTableEntry getItemFromConnector(String tableRefId, String connectorCode, String codeFieldName)
710    {
711        ContentTypeExpression cTypeExpr = _getContentTypeExpression(tableRefId);
712        StringExpression cdmExpr = new StringExpression(codeFieldName, Operator.EQ, connectorCode);
713        
714        String xpathQuery = ContentQueryHelper.getContentXPathQuery(new AndExpression(cTypeExpr, cdmExpr));
715
716        return _resolver.<Content>query(xpathQuery).stream()
717            .findFirst()
718            .map(OdfReferenceTableEntry::new)
719            .orElse(null);
720    }
721    
722    private ContentTypeExpression _getContentTypeExpression(String tableRefId)
723    {
724        Set<String> tableRefids;
725        if (tableRefId.equals(OdfReferenceTableHelper.ABSTRACT_MENTION))
726        {
727            tableRefids = _cTypeEP.getSubTypes(tableRefId);
728        }
729        else
730        {
731            tableRefids = Collections.singleton(tableRefId);
732        }
733        
734        return new ContentTypeExpression(Operator.EQ, tableRefids.toArray(new String[tableRefids.size()]));
735    }
736
737    /**
738     * Returns the reference table entry from its CDM value
739     * @param contentId The id of content
740     * @return the item as an {@link OdfReferenceTableEntry} or null if not found
741     */
742    public OdfReferenceTableEntry getItem(String contentId)
743    {
744        try
745        {
746            Content content = _resolver.resolveById(contentId);
747            return new OdfReferenceTableEntry(content);
748        }
749        catch (UnknownAmetysObjectException e)
750        {
751            // Can be an empty ID or an invalid ID (workspace or simply deleted element)
752            return null;
753        }
754    }
755
756    /**
757     * SAX items of a reference table
758     * @param contentHandler The content handler to sax into
759     * @param tableRefId The id of reference table
760     * @throws SAXException if an error occurred while saxing
761     */
762    public void saxItems (ContentHandler contentHandler, String tableRefId) throws SAXException
763    {
764        saxItems(contentHandler, tableRefId, null);
765    }
766    
767    /**
768     * SAX items of a reference table
769     * @param contentHandler the content handler to sax into
770     * @param attributeDefinition the metadata definition
771     * @throws SAXException if an error occurs while saxing
772     */
773    public void saxItems (ContentHandler contentHandler, ContentAttributeDefinition attributeDefinition) throws SAXException
774    {
775        saxItems(contentHandler, attributeDefinition, null);
776    }
777    
778    /**
779     * SAX items of a reference table
780     * @param contentHandler The content handler to sax into
781     * @param tableRefId The id of reference table
782     * @param lang the language to use to display items, can be <code>null</code> then odf language is used
783     * @throws SAXException if an error occurred while saxing
784     */
785    public void saxItems (ContentHandler contentHandler, String tableRefId, String lang) throws SAXException
786    {
787        _saxItems(contentHandler, new AttributesImpl(), tableRefId, lang);
788    }
789    
790    /**
791     * SAX items of a reference table
792     * @param contentHandler the content handler to sax into
793     * @param attributeDefinition the metadata definition
794     * @param lang the language to use to display items, can be <code>null</code> then odf language is used
795     * @throws SAXException if an error occurs while saxing
796     */
797    public void saxItems(ContentHandler contentHandler, ContentAttributeDefinition attributeDefinition, String lang) throws SAXException
798    {
799        String cTypeId = attributeDefinition.getContentTypeId();
800        if (cTypeId.startsWith("odf-enumeration."))
801        {
802            AttributesImpl attrs = new AttributesImpl();
803            attrs.addCDATAAttribute("metadataName", attributeDefinition.getName());
804            attrs.addCDATAAttribute("metadataPath", attributeDefinition.getPath());
805            
806            _saxItems(contentHandler, attrs, cTypeId, lang);
807        }
808    }
809    
810    private void _saxItems(ContentHandler contentHandler, AttributesImpl rootAttrs, String tableRefId, String lang) throws SAXException
811    {
812        String langToUse = lang != null ? lang : Config.getInstance().getValue("odf.programs.lang");
813        boolean hasShortLabel = _cTypeEP.getExtension(tableRefId).hasModelItem("shortLabel");
814        
815        rootAttrs.addCDATAAttribute("contentTypeId", tableRefId);
816        XMLUtils.startElement(contentHandler, "items", rootAttrs);
817        
818        List<OdfReferenceTableEntry> entries = getItems(tableRefId);
819        for (OdfReferenceTableEntry entry : entries)
820        {
821            AttributesImpl valueAttrs = new AttributesImpl();
822            valueAttrs.addCDATAAttribute("id", entry.getId());
823            valueAttrs.addCDATAAttribute("order", entry.getOrder().toString());
824            valueAttrs.addCDATAAttribute("code", entry.getCode());
825            valueAttrs.addCDATAAttribute("cdmValue", entry.getCdmValue());
826            valueAttrs.addCDATAAttribute("archived", entry.isArchived().toString());
827            if (hasShortLabel)
828            {
829                valueAttrs.addCDATAAttribute("shortLabel", entry.getContent().getValue("shortLabel", false, StringUtils.EMPTY));
830            }
831            
832            XMLUtils.createElement(contentHandler, "item", valueAttrs, entry.getLabel(langToUse));
833        }
834        
835        XMLUtils.endElement(contentHandler, "items");
836    }
837    
838    /**
839     * This class represents a sort field for reference table.
840     *
841     */
842    public static final class SortField
843    {
844        private String _name;
845        private boolean _ascending;
846        private boolean _normalize;
847
848        /**
849         * Create a sort field
850         * @param name the name of field to sort on
851         * @param ascending <code>true</code> to sort in ascending order, <code>false</code> otherwise
852         */
853        public SortField(String name, boolean ascending)
854        {
855            this(name, ascending, false);
856        }
857        
858        /**
859         * Create a sort field
860         * @param name the name of field to sort on
861         * @param ascending <code>true</code> to sort in ascending order, <code>false</code> otherwise
862         * @param normalize <code>true</code> to normalize string properties (remove accents and lower case)
863         */
864        public SortField(String name, boolean ascending, boolean normalize)
865        {
866            _name = name;
867            _ascending = ascending;
868            _normalize = normalize;
869        }
870
871        /**
872         * Get the name of the sort field
873         * @return the name of the sort field
874         */
875        public String getName()
876        {
877            return _name;
878        }
879
880        /**
881         * Get the order for sorting results
882         * @return <code>true</code> to sort results in ascending order, <code>false</code> otherwise
883         */
884        public boolean getAscending()
885        {
886            return _ascending;
887        }
888        
889        /**
890         * Return the normalize status for this sort field
891         * @return <code>true</code> if string properties should be normalized (remove accents and lower case)
892         */
893        public boolean getNormalize()
894        {
895            return _normalize;
896        }
897
898    }
899}