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