001/*
002 *  Copyright 2012 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.plugins.odfweb.repository;
017
018import java.util.Arrays;
019import java.util.HashMap;
020import java.util.HashSet;
021import java.util.Iterator;
022import java.util.List;
023import java.util.Map;
024import java.util.Set;
025import java.util.stream.Collectors;
026
027import javax.jcr.Node;
028import javax.jcr.RepositoryException;
029import javax.jcr.Value;
030
031import org.apache.avalon.framework.activity.Initializable;
032import org.apache.avalon.framework.component.Component;
033import org.apache.avalon.framework.logger.AbstractLogEnabled;
034import org.apache.avalon.framework.service.ServiceException;
035import org.apache.avalon.framework.service.ServiceManager;
036import org.apache.avalon.framework.service.Serviceable;
037
038import org.ametys.cms.FilterNameHelper;
039import org.ametys.cms.content.ContentHelper;
040import org.ametys.cms.contenttype.ContentTypeExtensionPoint;
041import org.ametys.cms.contenttype.ContentTypesHelper;
042import org.ametys.cms.contenttype.MetadataDefinition;
043import org.ametys.core.ui.Callable;
044import org.ametys.core.util.I18nUtils;
045import org.ametys.odf.ProgramItem;
046import org.ametys.odf.catalog.CatalogsManager;
047import org.ametys.odf.course.Course;
048import org.ametys.odf.enumeration.OdfReferenceTableHelper;
049import org.ametys.odf.orgunit.RootOrgUnitProvider;
050import org.ametys.odf.program.AbstractProgram;
051import org.ametys.odf.program.Program;
052import org.ametys.odf.tree.OdfClassificationHandler;
053import org.ametys.plugins.odfweb.restrictions.OdfProgramRestriction;
054import org.ametys.plugins.odfweb.restrictions.OdfProgramRestrictionManager;
055import org.ametys.plugins.repository.AmetysObjectIterable;
056import org.ametys.plugins.repository.AmetysObjectResolver;
057import org.ametys.plugins.repository.AmetysRepositoryException;
058import org.ametys.plugins.repository.jcr.JCRAmetysObject;
059import org.ametys.plugins.repository.provider.WorkspaceSelector;
060import org.ametys.plugins.repository.query.expression.Expression;
061import org.ametys.plugins.repository.query.expression.VirtualFactoryExpression;
062import org.ametys.runtime.i18n.I18nizableText;
063import org.ametys.web.repository.page.Page;
064import org.ametys.web.repository.page.PageQueryHelper;
065import org.ametys.web.repository.site.Site;
066import org.ametys.web.repository.sitemap.Sitemap;
067
068import com.google.common.collect.ImmutableList;
069
070/**
071 * Component providing methods to retrieve ODF virtual pages, such as the ODF root,
072 * level 1 and 2 metadata names, and so on.
073 */
074public class OdfPageHandler extends AbstractLogEnabled implements Component, Initializable, Serviceable
075{
076    /** The avalon role. */
077    public static final String ROLE = OdfPageHandler.class.getName();
078    
079    /** First level metadata name. */
080    public static final String LEVEL1_METADATA_NAME = "firstLevel";
081    
082    /** Second level metadata name. */
083    public static final String LEVEL2_METADATA_NAME = "secondLevel";
084    
085    /** Catalog metadata name. */
086    public static final String CATALOG_METADATA_NAME = "odf-root-catalog";
087    
088    /** The default level 1 metadata. */
089    protected static final String DEFAULT_LEVEL1_METADATA = "degree";
090    
091    /** The default level 2 metadata. */
092    protected static final String DEFAULT_LEVEL2_METADATA = "domain";
093    
094    /** Content types that are not eligible for first and second level */
095    // See ODF-1115 Exclude the mentions enumerator from the list : 
096    protected static final List<String> NON_ELIGIBLE_CTYPES_FOR_LEVEL = Arrays.asList("org.ametys.plugins.odf.Content.programItem", "odf-enumeration.Mention");
097    
098    /** The ametys object resolver. */
099    protected AmetysObjectResolver _resolver;
100    
101    /** The i18n utils. */
102    protected I18nUtils _i18nUtils;
103    
104    /** The content type extension point. */
105    protected ContentTypeExtensionPoint _cTypeEP;
106    
107    /** The ODF Catalog enumeration */
108    protected CatalogsManager _catalogsManager;
109    
110    /** The workspace selector. */
111    protected WorkspaceSelector _workspaceSelector;
112    
113    /** The ODF root pages, indexed by workspace, site and sitemap name. */
114    protected Map<String, Map<String, Map<String, Set<String>>>> _odfRootPages;
115    
116    /** For each site, indicate if an ODF root is present. */
117    protected Map<String, Map<String, Boolean>> _hasOdfRoot;
118    
119    /** Avalon service manager */
120    protected ServiceManager _manager;
121    
122    /** Restriction manager */
123    protected OdfProgramRestrictionManager _odfRestrictionsManager;
124    
125    /** Content types helper */
126    protected ContentTypesHelper _contentTypesHelper;
127    
128    /** Content helper */
129    protected ContentHelper _contentHelper;
130    
131    /** Odf reference table helper */
132    protected OdfReferenceTableHelper _odfReferenceTableHelper;
133    
134    /** Root orgunit provider */
135    protected RootOrgUnitProvider _orgUnitProvider;
136    
137    /** Root orgunit provider */
138    protected OdfClassificationHandler _odfClassificationHandler;
139    
140    @Override
141    public void service(ServiceManager serviceManager) throws ServiceException
142    {
143        _manager = serviceManager;
144        _resolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
145        _i18nUtils = (I18nUtils) serviceManager.lookup(I18nUtils.ROLE);
146        _cTypeEP = (ContentTypeExtensionPoint) serviceManager.lookup(ContentTypeExtensionPoint.ROLE);
147        _workspaceSelector = (WorkspaceSelector) serviceManager.lookup(WorkspaceSelector.ROLE);
148        _catalogsManager = (CatalogsManager) serviceManager.lookup(CatalogsManager.ROLE);
149        _odfRestrictionsManager = (OdfProgramRestrictionManager) serviceManager.lookup(OdfProgramRestrictionManager.ROLE);
150        _contentTypesHelper = (ContentTypesHelper) serviceManager.lookup(ContentTypesHelper.ROLE);
151        _contentHelper = (ContentHelper) serviceManager.lookup(ContentHelper.ROLE);
152        _odfReferenceTableHelper = (OdfReferenceTableHelper) serviceManager.lookup(OdfReferenceTableHelper.ROLE);
153        _orgUnitProvider = (RootOrgUnitProvider) serviceManager.lookup(RootOrgUnitProvider.ROLE);
154        _odfClassificationHandler = (OdfClassificationHandler) serviceManager.lookup(OdfClassificationHandler.ROLE);
155    }
156    
157    @Override
158    public void initialize() throws Exception
159    {
160        _odfRootPages = new HashMap<>();
161        _hasOdfRoot = new HashMap<>();
162    }
163    
164    /**
165     * Get the first ODF root page.
166     * @param siteName The site name
167     * @param sitemapName The sitemap's name
168     * @return a ODF root page or null if not found.
169     * @throws AmetysRepositoryException if an error occurs
170     */
171    public Page getOdfRootPage(String siteName, String sitemapName) throws AmetysRepositoryException
172    {
173        Set<Page> rootPages = getOdfRootPages(siteName, sitemapName);
174        return rootPages.isEmpty() ? null : rootPages.iterator().next();
175    }
176    
177    /**
178     * Get ODF root page of a specific catalog.
179     * @param siteName The site name
180     * @param sitemapName The sitemap name 
181     * @param catalogName The catalog name
182     * @return the ODF root page or null if not found.
183     * @throws AmetysRepositoryException if an error occurs
184     */
185    public Page getOdfRootPage(String siteName, String sitemapName, String catalogName) throws AmetysRepositoryException
186    {
187        String catalogToCompare = catalogName != null ? catalogName : "";
188        
189        for (Page odfRootPage : getOdfRootPages(siteName, sitemapName))
190        {
191            if (catalogToCompare.equals(getCatalog(odfRootPage)))
192            {
193                return odfRootPage;
194            }
195        }
196        
197        return null;
198    }
199    
200    /**
201     * Get the id of ODF root pages
202     * @param siteName The site name
203     * @param sitemapName The sitemap name
204     * @return The ids of ODF root pages
205     * @throws AmetysRepositoryException if an error occurs.
206     */
207    @Callable
208    public List<String> getOdfRootPageIds(String siteName, String sitemapName) throws AmetysRepositoryException
209    {
210        Set<Page> pages = getOdfRootPages(siteName, sitemapName);
211        return pages.stream().map(p -> p.getId()).collect(Collectors.toList());
212    }
213    
214    /**
215     * Get the ODF root pages.
216     * @param siteName the current site.
217     * @param sitemapName the current sitemap/language.
218     * @return the ODF root pages
219     * @throws AmetysRepositoryException if an error occurs.
220     */
221    public Set<Page> getOdfRootPages(String siteName, String sitemapName) throws AmetysRepositoryException
222    {
223        Set<Page> rootPages = new HashSet<>();
224        
225        String workspace = _workspaceSelector.getWorkspace();
226        
227        Map<String, Map<String, Set<String>>> wspOdfRootPages = null;
228        if (_odfRootPages.containsKey(workspace))
229        {
230            wspOdfRootPages = _odfRootPages.get(workspace);
231        }
232        else
233        {
234            wspOdfRootPages = new HashMap<>();
235            _odfRootPages.put(workspace, wspOdfRootPages);
236        }
237        
238        Map<String, Set<String>> siteOdfRootPages = null;
239        
240        if (wspOdfRootPages.containsKey(siteName))
241        {
242            siteOdfRootPages = wspOdfRootPages.get(siteName);
243        }
244        else
245        {
246            siteOdfRootPages = new HashMap<>();
247            wspOdfRootPages.put(siteName, siteOdfRootPages);
248        }
249        
250        if (siteOdfRootPages.containsKey(sitemapName))
251        {
252            Set<String> odfRootPageIds = siteOdfRootPages.get(sitemapName);
253            if (odfRootPageIds != null)
254            {
255                for (String pageId : odfRootPageIds)
256                {
257                    rootPages.add((Page) _resolver.resolveById(pageId));
258                }
259            }
260        }
261        else
262        {
263            rootPages = _getOdfRootPages(siteName, sitemapName);
264            Set<String> odfRootPageIds = new HashSet<>();
265            for (Page rootPage : rootPages)
266            {
267                odfRootPageIds.add(rootPage.getId());
268            }
269            siteOdfRootPages.put(sitemapName, odfRootPageIds);
270        }
271        
272        return rootPages;
273    }
274    
275    /**
276     * Test if the given site has at least one sitemap with an odf root page.
277     * @param site the site to test.
278     * @return true if the site has at least one sitemap with an odf root page, false otherwise.
279     */
280    public boolean hasOdfRootPage(Site site)
281    {
282        boolean hasOneRoot = false;
283        
284        String workspace = _workspaceSelector.getWorkspace();
285        String siteName = site.getName();
286        
287        Map<String, Boolean> wspHasOneRoot = null;
288        if (_hasOdfRoot.containsKey(workspace))
289        {
290            wspHasOneRoot = _hasOdfRoot.get(workspace);
291        }
292        else
293        {
294            wspHasOneRoot = new HashMap<>();
295            _hasOdfRoot.put(workspace, wspHasOneRoot);
296        }
297
298        
299        if (wspHasOneRoot.containsKey(siteName))
300        {
301            hasOneRoot = wspHasOneRoot.get(siteName);
302        }
303        else
304        {
305            Iterator<Sitemap> sitemaps = site.getSitemaps().iterator();
306            
307            while (sitemaps.hasNext() && !hasOneRoot)
308            {
309                String sitemapName = sitemaps.next().getName();
310                
311                if (!getOdfRootPages(site.getName(), sitemapName).isEmpty())
312                {
313                    hasOneRoot = true;
314                }
315            }
316            
317            wspHasOneRoot.put(siteName, hasOneRoot);
318        }
319        
320        return hasOneRoot;
321    }
322    
323    /**
324     * Determines if the program is part of the site restrictions
325     * @param rootPage The ODF root page
326     * @param program The program
327     * @return <code>true</code> the program is part of the site restrictions
328     */
329    public boolean isValidRestriction(Page rootPage, Program program)
330    {
331        // Check catalog
332        if (!program.getCatalog().equals(getCatalog(rootPage)))
333        {
334            return false;
335        }
336        
337        // Check language
338        if (!program.getLanguage().equals(rootPage.getSitemapName()))
339        {
340            return false;
341        }
342        
343        // Check site restrictions
344        OdfProgramRestriction restriction = _odfRestrictionsManager.getRestriction(rootPage);
345        if (restriction != null)
346        {
347            return restriction.contains(program);
348        }
349        
350        return true;
351    }
352    
353    /**
354     * Clear the ODF root page cache.
355     */
356    public void clearRootCache()
357    {
358        _odfRootPages = new HashMap<>();
359        _hasOdfRoot = new HashMap<>();
360    }
361    
362    /**
363     * Clear the ODF root page cache for a given site and language.
364     * @param siteName the current site.
365     * @param sitemapName the current sitemap/language.
366     */
367    public void clearRootCache(String siteName, String sitemapName)
368    {
369        if (_odfRootPages.containsKey(siteName))
370        {
371            _odfRootPages.get(siteName).remove(sitemapName);
372        }
373        
374        _hasOdfRoot.remove(siteName);
375    }
376    
377    /**
378     * Determines if the page is a ODF root page
379     * @param page The page to test
380     * @return true if the page is a ODF root page
381     */
382    public boolean isODFRootPage (Page page)
383    {
384        if (page instanceof JCRAmetysObject)
385        {
386            try
387            {
388                JCRAmetysObject jcrPage = (JCRAmetysObject) page;
389                Node node = jcrPage.getNode();
390                
391                if (node.hasProperty(AmetysObjectResolver.VIRTUAL_PROPERTY))
392                {
393                    Value[] values = node.getProperty(AmetysObjectResolver.VIRTUAL_PROPERTY).getValues();
394                    
395                    boolean hasValue = false;
396                    for (int i = 0; i < values.length && !hasValue; i++)
397                    {
398                        hasValue = FirstLevelPageFactory.class.getName().equals(values[i].getString());
399                    }
400                    
401                    return hasValue;
402                }
403                else
404                {
405                    return false;
406                }
407            }
408            catch (RepositoryException e)
409            {
410                return false;
411            }
412        }
413        
414        return false;
415        
416    }
417    
418    /**
419     * Get the ODF root page.
420     * @param siteName the current site.
421     * @param sitemapName the current sitemap/language.
422     * @return the ODF root page or null if not found.
423     * @throws AmetysRepositoryException if an error occurs.
424     */
425    protected Page _getOdfRootPage(String siteName, String sitemapName) throws AmetysRepositoryException
426    {
427        Expression expression = new VirtualFactoryExpression(FirstLevelPageFactory.class.getName());
428        String query = PageQueryHelper.getPageXPathQuery(siteName, sitemapName, null, expression, null);
429        
430        AmetysObjectIterable<Page> pages = _resolver.query(query);
431        Page page = pages.stream().findFirst().orElse(null);
432        
433        return page;
434    }
435    
436    /**
437     * Get the ODF root page.
438     * @param siteName the current site.
439     * @param sitemapName the current sitemap/language.
440     * @return the ODF root page or null if not found.
441     * @throws AmetysRepositoryException if an error occurs.
442     */
443    protected Set<Page> _getOdfRootPages(String siteName, String sitemapName) throws AmetysRepositoryException
444    {
445        Expression expression = new VirtualFactoryExpression(FirstLevelPageFactory.class.getName());
446        String query = PageQueryHelper.getPageXPathQuery(siteName, sitemapName, null, expression, null);
447        
448        return _resolver.<Page>query(query).stream().collect(Collectors.toSet());
449    }
450    
451    /**
452     * Get the catalog value of the ODF root page
453     * @param rootPage The ODF root page
454     * @return the catalog value
455     */
456    public String getCatalog (Page rootPage)
457    {
458        return rootPage.getMetadataHolder().getString(CATALOG_METADATA_NAME, "");
459    }
460    
461    /**
462     * Get the first level metadata name.
463     * @param siteName the site name.
464     * @param sitemapName the sitemap name.
465     * @param catalog the current selected catalog.
466     * @return the first level metadata name.
467     */
468    public String getLevel1Metadata(String siteName, String sitemapName, String catalog)
469    {
470        Page rootPage = getOdfRootPage(siteName, sitemapName, catalog);
471        
472        return getLevel1Metadata(rootPage);
473    }
474    
475    /**
476     * Get the first level metadata name.
477     * @param rootPage the ODF root page.
478     * @return the first level metadata name.
479     */
480    public String getLevel1Metadata(Page rootPage)
481    {
482        return rootPage.getMetadataHolder().getString(LEVEL1_METADATA_NAME, DEFAULT_LEVEL1_METADATA);
483    }
484    
485    /**
486     * Get the second level metadata name.
487     * @param siteName the site name.
488     * @param sitemapName the sitemap name.
489     * @param catalog the current selected catalog.
490     * @return the second level metadata name.
491     */
492    public String getLevel2Metadata(String siteName, String sitemapName, String catalog)
493    {
494        Page rootPage = getOdfRootPage(siteName, sitemapName, catalog);
495        
496        return getLevel2Metadata(rootPage);
497    }
498    
499    /**
500     * Get the second level metadata name.
501     * @param rootPage the ODF root page.
502     * @return the second level metadata name.
503     */
504    public String getLevel2Metadata(Page rootPage)
505    {
506        return rootPage.getMetadataHolder().getString(LEVEL2_METADATA_NAME, DEFAULT_LEVEL2_METADATA);
507    }
508    
509    /**
510     * Get the first level metadata values (with translated label).
511     * @param siteName the site name.
512     * @param sitemapName the sitemap name.
513     * @param catalog the current selected catalog.
514     * @return the first level metadata values.
515     */
516    public Map<String, String> getLevel1Values(String siteName, String sitemapName, String catalog)
517    {
518        Page rootPage = getOdfRootPage(siteName, sitemapName, catalog);
519        
520        return getLevel1Values(rootPage);
521    }
522    
523    /**
524     * Get the level value of a program by extracting and transforming the raw program value at the desired metadata path
525     * @param program The program
526     * @param levelMetaPath The desired metadata path that represent a level
527     * @return The final level value
528     */
529    public String getProgramLevelValue(Program program, String levelMetaPath)
530    {
531        List<String> programLevelValue = _odfClassificationHandler.getProgramLevelValues(program, levelMetaPath);
532        return programLevelValue.isEmpty() ? null : programLevelValue.get(0);
533    }
534    
535    /**
536     * Get the first level value of a program by extracting and transforming the raw program value
537     * @param rootPage The root page
538     * @param program The program
539     * @return The final level value or <code>null</code> if not found
540     */
541    public String getProgramLevel1Value(Page rootPage, Program program)
542    {
543        List<String> programLevelValue = _odfClassificationHandler.getProgramLevelValues(program, getLevel1Metadata(rootPage));
544        return programLevelValue.isEmpty() ? null : programLevelValue.get(0);
545    }
546    
547    /**
548     * Get the second level value of a program by extracting and transforming the raw program value
549     * @param rootPage The root page
550     * @param program The program
551     * @return The final level value or <code>null</code> if not found
552     */
553    public String getProgramLevel2Value(Page rootPage, Program program)
554    {
555        List<String> programLevelValue = _odfClassificationHandler.getProgramLevelValues(program, getLevel2Metadata(rootPage));
556        return programLevelValue.isEmpty() ? null : programLevelValue.get(0);
557    }
558    
559    /**
560     * Get the name of the page of first level for a given program
561     * @param rootPage The ODF root page
562     * @param program The program
563     * @return The page's name 
564     */
565    public String getLevel1PageName(Page rootPage, Program program)
566    {
567        String value = getProgramLevel1Value(rootPage, program);
568        return value != null ? FilterNameHelper.filterName(value) + "-" + encodeLevelValue(value) : "";
569    }
570    
571    /**
572     * Get the name of the page of second level for a given program
573     * @param rootPage The ODF root page
574     * @param program The program
575     * @return The page's name 
576     */
577    public String getLevel2PageName(Page rootPage, Program program)
578    {
579        String value = getProgramLevel2Value(rootPage, program);
580        return value != null ? FilterNameHelper.filterName(value) + "-" + encodeLevelValue(value) : "";
581    }
582    
583    /**
584     * Get the orgunit identifier given an uai code
585     * @param rootPage Odf root page
586     * @param uaiCode The uai code
587     * @return The orgunit id or null if not found
588     */
589    public String getOrgunitIdFromUaiCode(Page rootPage, String uaiCode)
590    {
591        return _odfClassificationHandler.getOrgunitIdFromUaiCode(rootPage.getSitemapName(), uaiCode);
592    }
593    
594    /**
595     * Get the programs available for a ODF root page, taking account the site's restrictions
596     * @param rootPage The ODF root page
597     * @param level1 filters results with a level1 value. Can be null.
598     * @param level2 filters results with a level2 value. Can be null.
599     * 
600     * @param programName expected program name. Can be null.
601     * @return an iterator over resulting programs
602     */
603    public AmetysObjectIterable<Program> getProgramsWithRestrictions(Page rootPage, String level1, String level2, String programName)
604    {
605        return getProgramsWithRestrictions(rootPage, getLevel1Metadata(rootPage), level1, getLevel2Metadata(rootPage), level2, programName);
606    }
607
608    /**
609     * Get the programs available for a ODF root page, taking account the site's restrictions
610     * @param rootPage The ODF root page
611     * @param level1Metadata metadata name for first level
612     * @param level1 filters results with a level1 value. Can be null.
613     * @param level2Metadata metadata name for second level
614     * @param level2 filters results with a level2 value. Can be null.
615     * 
616     * @param programName expected program name. Can be null.
617     * @return an iterator over resulting programs
618     */
619    public AmetysObjectIterable<Program> getProgramsWithRestrictions(Page rootPage, String level1Metadata, String level1, String level2Metadata, String level2, String programName)
620    {
621        OdfProgramRestriction restriction = _odfRestrictionsManager.getRestriction(rootPage);
622        return _odfClassificationHandler.getPrograms(getCatalog(rootPage), rootPage.getSitemapName(), level1Metadata, level1, level2Metadata, level2, programName, restriction == null ? null : ImmutableList.of(restriction.getExpression()));
623    }
624    
625    /**
626     * Get the first level metadata values (with translated label).
627     * @param rootPage the ODF root page.
628     * @return the first level metadata values.
629     */
630    public Map<String, String> getLevel1Values(Page rootPage)
631    {
632        return _odfClassificationHandler.getLevelValues(getLevel1Metadata(rootPage), rootPage.getSitemapName());
633    }
634    
635    /**
636     * Get the second level metadata values (with translated label).
637     * @param siteName the site name.
638     * @param sitemapName the sitemap name.
639     * @param catalog the current selected catalog.
640     * @return the second level metadata values.
641     */
642    public Map<String, String> getLevel2Values(String siteName, String sitemapName, String catalog)
643    {
644        Page rootPage = getOdfRootPage(siteName, sitemapName, catalog);
645        
646        return getLevel2Values(rootPage);
647    }
648    
649    /**
650     * Get the second level metadata values (with translated label).
651     * @param rootPage the ODF root page.
652     * @return the second level metadata values.
653     */
654    public Map<String, String> getLevel2Values(Page rootPage)
655    {
656        return _odfClassificationHandler.getLevelValues(getLevel2Metadata(rootPage), rootPage.getSitemapName());
657    }
658    
659    /**
660     * Encode level value to be use into a URI.
661     * Double-encode characters ':', '-' and '/'.
662     * @param value The raw value
663     * @return the encoded value
664     */
665    public String encodeLevelValue (String value)
666    {
667        return _odfClassificationHandler.encodeLevelValue(value);
668    }
669    
670    /**
671     * Decode level value used in a URI
672     * @param value The encoded value
673     * @return the decoded value
674     */
675    public String decodeLevelValue (String value)
676    {
677        return _odfClassificationHandler.decodeLevelValue(value);
678    }
679    
680    /**
681     * Returns the page's name of a {@link ProgramItem}.
682     * Only {@link AbstractProgram} and {@link Course} can have a page.
683     * @param item The program item
684     * @return The page's name
685     * @throws IllegalArgumentException if the program item is not a {@link AbstractProgram} nor a {@link Course}.
686     */
687    public String getPageName (ProgramItem item)
688    {
689        if (item instanceof AbstractProgram)
690        {
691            // E.g: licence-lea-anglais-allemand-program-fruai3182988abcde07
692            return FilterNameHelper.filterName(((AbstractProgram) item).getTitle()) + "-" + item.getName();
693        }
694        else if (item instanceof Course)
695        {
696         // E.g: langue-anglaise-1-fruai3182988acoh6yp1p98
697            return FilterNameHelper.filterName(((Course) item).getTitle()) + "-" + item.getCode();
698        }
699        else 
700        {
701            throw new IllegalArgumentException("Illegal program item : no page can be associated for a program item of type " + item.getClass().getName());
702        }
703    }
704
705    /**
706     * Get the eligible enumerated metadata for ODF page level
707     * @return the eligible metadata definition
708     */
709    public Map<String, MetadataDefinition> getEligibleMetadataForLevel()
710    {
711        return _odfClassificationHandler.getEligibleMetadataForLevel();
712    }
713
714    /**
715     * Get the ODF catalogs
716     * @return the ODF catalogs
717     */
718    public Map<String, I18nizableText> getCatalogs()
719    {
720        return _odfClassificationHandler.getCatalogs();
721    }
722
723    /**
724     * Get the enumerated metadata definition for the given content type.
725     * Metadata with enumerator or content metadata are considered as enumrated
726     * @param programContentTypeId The content type's id 
727     * @param allowMultiple <code>true</code> true to allow multiple metadata
728     * @return The definition of enumerated metadata
729     */
730    public Map<String, MetadataDefinition> getEnumeratedMetadata(String programContentTypeId, boolean allowMultiple)
731    {
732        return _odfClassificationHandler.getEnumeratedMetadata(programContentTypeId, allowMultiple);
733    }
734}