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.plugins.odfweb.repository;
017
018import java.util.ArrayList;
019import java.util.Collections;
020import java.util.HashSet;
021import java.util.List;
022import java.util.Set;
023
024import org.apache.avalon.framework.component.Component;
025import org.apache.avalon.framework.logger.AbstractLogEnabled;
026import org.apache.avalon.framework.service.ServiceException;
027import org.apache.avalon.framework.service.ServiceManager;
028import org.apache.avalon.framework.service.Serviceable;
029import org.apache.commons.lang.StringUtils;
030
031import org.ametys.cms.repository.Content;
032import org.ametys.odf.ODFHelper;
033import org.ametys.odf.ProgramItem;
034import org.ametys.odf.course.Course;
035import org.ametys.odf.courselist.CourseList;
036import org.ametys.odf.program.AbstractProgram;
037import org.ametys.odf.program.Program;
038import org.ametys.odf.program.ProgramPart;
039import org.ametys.odf.program.SubProgram;
040import org.ametys.plugins.repository.AmetysObjectResolver;
041import org.ametys.plugins.repository.UnknownAmetysObjectException;
042import org.ametys.runtime.config.Config;
043import org.ametys.web.repository.page.Page;
044
045/**
046 * Resolves an ODF page path from the associated ODF content.
047 */
048public class OdfPageResolver extends AbstractLogEnabled implements Component, Serviceable
049{
050    /** The avalon role. */
051    public static final String ROLE = OdfPageResolver.class.getName();
052    
053    /** The ametys object resolver. */
054    protected AmetysObjectResolver _ametysResolver;
055    /** The odf page handler */
056    protected OdfPageHandler _odfPageHandler;
057    /** ODF helper */
058    protected ODFHelper _odfHelper;
059    
060    @Override
061    public void service(ServiceManager serviceManager) throws ServiceException
062    {
063        _ametysResolver = (AmetysObjectResolver) serviceManager.lookup(AmetysObjectResolver.ROLE);
064        _odfPageHandler = (OdfPageHandler) serviceManager.lookup(OdfPageHandler.ROLE);
065        _odfHelper = (ODFHelper) serviceManager.lookup(ODFHelper.ROLE);
066    }
067    
068    /**
069     * Get all referencing pages for this program item, in all sites and all sitemaps
070     * @param programItem The program item
071     * @return the referencing pages
072     */
073    public Set<Page> getReferencingPages(ProgramItem programItem)
074    {
075        return getReferencingPages(programItem, null, ((Content) programItem).getLanguage());
076    }
077    
078    /**
079     * Get all referencing pages for this program item
080     * @param programItem The program item
081     * @param siteName The site name. Can be null to search on all sites
082     * @param lang The sitemap language. Can be null to search on all sitemaps
083     * @return the referencing pages
084     */
085    public Set<Page> getReferencingPages(ProgramItem programItem, String siteName, String lang)
086    {
087        Set<Page> refPages = new HashSet<>();
088        
089        Set<Page> odfRootPages = _odfPageHandler.getOdfRootPages(siteName, lang);
090        
091        for (Page rootPage : odfRootPages)
092        {
093            if (programItem instanceof Program)
094            {
095                ProgramPage programPage = getProgramPage(rootPage, (Program) programItem);
096                if (programPage != null)
097                {
098                    refPages.add(programPage);
099                }
100            }
101            else if (programItem instanceof SubProgram)
102            {
103                Set<Program> parentPrograms = programItem.getRootPrograms();
104                for (Program parentProgram : parentPrograms)
105                {
106                    ProgramPage subProgramPage = getSubProgramPage(rootPage, (SubProgram) programItem, parentProgram);
107                    if (subProgramPage != null)
108                    {
109                        refPages.add(subProgramPage);
110                    }
111                }
112            }
113            else if (programItem instanceof Course)
114            {
115                List<CourseList> parentCourseLists = ((Course) programItem).getParentCourseLists();
116                for (CourseList courseList : parentCourseLists)
117                {
118                    List<Course> parentCourses = courseList.getParentCourses();
119                    for (Course parentCourse : parentCourses)
120                    {
121                        CoursePage coursePage = getCoursePage(rootPage, (Course) programItem, parentCourse);
122                        if (coursePage != null)
123                        {
124                            refPages.add(coursePage);
125                        }
126                    }
127                    
128                    List<AbstractProgram> parentAbstractPrograms = getNearestAncestorAbstractPrograms(courseList);
129                    for (AbstractProgram parentAbstractProgram : parentAbstractPrograms)
130                    {
131                        CoursePage coursePage = getCoursePage(rootPage, (Course) programItem, parentAbstractProgram);
132                        if (coursePage != null)
133                        {
134                            refPages.add(coursePage);
135                        }
136                    }
137                }
138            }
139        }
140        
141        return refPages;
142    }
143    
144    /**
145     * Return the program page
146     * @param program the program
147     * @return the page program or null
148     */
149    public ProgramPage getProgramPage(Program program)
150    {
151        return getProgramPage(program, null);
152    }
153    
154    /**
155     * Return the program page
156     * @param program the program
157     * @param siteName The current site name. If the no ODF root page is present in this site, the default ODF site will be used instead.
158     * @return the page program or null
159     */
160    public ProgramPage getProgramPage(Program program, String siteName)
161    {
162        Page odfRootPage = getOdfRootPage(siteName, program.getLanguage(), program.getCatalog());
163        
164        if (odfRootPage == null)
165        {
166            return null;
167        }
168        
169        return getProgramPage(odfRootPage, program);
170    }
171    
172    /**
173     * Return the program page
174     * @param odfRootPage the odf root page
175     * @param program the program
176     * @return the page program or null
177     */
178    public ProgramPage getProgramPage (Page odfRootPage, Program program)
179    {
180        // E.g: program://_root?rootId=xxxx&programId=xxxx
181        String pageId =  "program://_root?rootId=" + odfRootPage.getId() + "&programId=" + program.getId();
182        try
183        {
184            return _ametysResolver.resolveById(pageId);
185        }
186        catch (UnknownAmetysObjectException e)
187        {
188            return null;
189        }
190    }
191    
192    /**
193     * Return the subprogram page
194     * @param subProgram the subprogram
195     * @param parentProgram The parent program
196     * @param siteName The current site name. If the no ODF root page is present in this site, the default ODF site will be used instead.
197     * @return the subprogram page or null
198     */
199    public ProgramPage getSubProgramPage(SubProgram subProgram, AbstractProgram parentProgram, String siteName)
200    {
201        Page odfRootPage = getOdfRootPage(siteName, subProgram.getLanguage(), subProgram.getCatalog());
202        
203        if (odfRootPage == null)
204        {
205            return null;
206        }
207        
208        return getSubProgramPage(odfRootPage, subProgram, parentProgram);
209    }
210    
211    /**
212     * Return the subprogram page
213     * @param odfRootPage the odf root page
214     * @param subProgram the subprogram
215     * @param parentAbstractProgram The parent program or subprogram
216     * @return the subprogram page or null
217     */
218    public ProgramPage getSubProgramPage (Page odfRootPage, SubProgram subProgram, AbstractProgram parentAbstractProgram)
219    {
220        AbstractProgram parent = getNearestAncestorAbstractProgram(subProgram, parentAbstractProgram);
221        Program parentProgram = getParentProgram(subProgram, parentAbstractProgram);
222        
223        if (parent == null || parentProgram == null)
224        {
225            // No page
226            return null;
227        }
228     
229        // Id is like program://path/to/subprogram?rootId=xxxx&programId=xxxx&parentId=xxxx
230        String pageId =  "program://" + getPathInProgram(parent, parentProgram) + "?rootId=" + odfRootPage.getId() + "&programId=" + subProgram.getId() + "&parentId=" + parentProgram.getId();
231        try
232        {
233            return _ametysResolver.resolveById(pageId);
234        }
235        catch (UnknownAmetysObjectException e)
236        {
237            return null;
238        }
239    }
240    
241    /**
242     * Return the course page
243     * @param course the course
244     * @param parentProgram the parent program or subprogram. Can be null.
245     * @param siteName The current site name. If the no ODF root page is present in this site, the default ODF site will be used instead.
246     * @return the course page or null if not found
247     */
248    public CoursePage getCoursePage(Course course, AbstractProgram parentProgram, String siteName)
249    {
250        String catalog = course.getCatalog();
251        Page odfRootPage = getOdfRootPage(siteName, course.getLanguage(), catalog);
252        
253        if (odfRootPage == null)
254        {
255            return null;
256        }
257        
258        return getCoursePage(odfRootPage, course, parentProgram);
259    }
260    
261    /**
262     * Return the course page
263     * @param odfRootPage the odf root page
264     * @param course the course
265     * @param parentAbstractProgram the parent program or subprogram. Can be null.
266     * @return the course page or null if not found
267     */
268    public CoursePage getCoursePage (Page odfRootPage, Course course, AbstractProgram parentAbstractProgram)
269    {
270        AbstractProgram parent = getNearestAncestorAbstractProgram(course, parentAbstractProgram);
271        Program parentProgram = getParentProgram(course, parentAbstractProgram);
272        
273        if (parent == null || parentProgram == null)
274        {
275            // No page
276            return null;
277        }
278     
279        // Id is like course://path/from/program?rootId=xxx&courseId=xxx&programId=xxxx
280        String pageId =  "course://" + getPathInProgram(parent, parentProgram) + "?rootId=" + odfRootPage.getId() + "&courseId=" + course.getId() + "&programId=" + parentProgram.getId();
281        try
282        {
283            return _ametysResolver.resolveById(pageId);
284        }
285        catch (UnknownAmetysObjectException e)
286        {
287            return null;
288        }
289    }
290    
291    /**
292     * Return the course page
293     * @param course the course
294     * @param parentCourse the parent course. Can NOT be null.
295     * @param siteName The current site name. If the no ODF root page is present in this site, the default ODF site will be used instead.
296     * @return the course page or null if not found
297     */
298    public CoursePage getCoursePage (Course course, Course parentCourse, String siteName)
299    {
300        String catalog = course.getCatalog();
301        Page odfRootPage = getOdfRootPage(siteName, course.getLanguage(), catalog);
302        
303        if (odfRootPage == null)
304        {
305            return null;
306        }
307        
308        return getCoursePage(odfRootPage, course, parentCourse);
309    }
310    
311    /**
312     * Return the course page
313     * @param odfRootPage the odf root page
314     * @param course the course
315     * @param parentCourse the parent course. Can NOT be null.
316     * @return the course page or null if not found
317     */
318    public CoursePage getCoursePage (Page odfRootPage, Course course, Course parentCourse)
319    {
320        AbstractProgram parent = getNearestAncestorAbstractProgram(parentCourse, null);
321        Program parentProgram = getParentProgram(parentCourse, null);
322        
323        if (parent == null || parentProgram == null)
324        {
325            // No page
326            return null;
327        }
328     
329        // Id is like course://path/from/program?rootId=xxx&courseId=xxx&programId=xxxx
330        String pageId =  "course://" + getPathInProgram(parentCourse, parentProgram) + "?rootId=" + odfRootPage.getId() + "&courseId=" + course.getId() + "&programId=" + parentProgram.getId();
331        try
332        {
333            return _ametysResolver.resolveById(pageId);
334        }
335        catch (UnknownAmetysObjectException e)
336        {
337            return null;
338        }
339    }
340    
341    /**
342     * Get the ODF root page, either in the given site if it exists, or in the default ODF site.
343     * @param siteName the desired site name.
344     * @param language the sitemap language to search in.
345     * @param catalog The ODF catalog
346     * @return the ODF root page, either in the given site if it exists, or in the default ODF site.
347     */
348    public Page getOdfRootPage(String siteName, String language, String catalog)
349    {
350        Page odfRootPage = null;
351        
352        if (StringUtils.isNotEmpty(siteName))
353        {
354            odfRootPage = _odfPageHandler.getOdfRootPage(siteName, language, catalog);
355        }
356        
357        if (odfRootPage == null)
358        {
359            String odfSiteName = Config.getInstance().getValueAsString("odf.web.site.name");
360            odfRootPage = _odfPageHandler.getOdfRootPage(odfSiteName, language, catalog);
361        }
362        
363        return odfRootPage;
364    }
365    
366    /**
367     * Get the path in sitemap of a ODF content into a {@link Program} or {@link SubProgram}<br>
368     * Be careful, this is the path in sitemap, to get the path of a item into a Program, use {@link ODFHelper#getPathInProgram} instead.
369     * @param item The program item
370     * @param parentProgram The parent root (sub)program. Can be null.
371     * @return the path in sitemap from the parent program
372     */
373    public String getPathInProgram (ProgramItem item, AbstractProgram parentProgram)
374    {
375        if (item instanceof Program || item.equals(parentProgram))
376        {
377            // The program item is already the program it self
378            return _odfPageHandler.getPageName(item);
379        }
380        
381        List<String> paths = new ArrayList<>();
382        if (item instanceof AbstractProgram || item instanceof Course)
383        {
384            paths.add(_odfPageHandler.getPageName(item));
385        }
386        
387        ProgramItem parent = _odfHelper.getParentProgramItem(item, parentProgram);
388        while (parent != null && !(parent instanceof Program))
389        {
390            if (parent instanceof AbstractProgram || parent instanceof Course)
391            {
392                paths.add(_odfPageHandler.getPageName(parent));
393            }
394            parent = _odfHelper.getParentProgramItem(parent, parentProgram);
395        }
396        
397        if (parent != null)
398        {
399            paths.add(_odfPageHandler.getPageName(parent));
400            Collections.reverse(paths);
401            return org.apache.commons.lang3.StringUtils.join(paths, "/");
402        }
403        
404        return null;
405    }
406    
407    /**
408     * Returns the first {@link Program} ancestor, ensuring that the given parent content 'parentProgram' is in the hierarchy, if not null.<br>
409     * If 'parentProgram' is null, the first {@link Program} ancestor will be returned regardless of parent hierarchy.<br>
410     * If 'parentProgram' is a {@link SubProgram}, the first {@link Program} ancestor from this {@link SubProgram} will be returned regardless of parent hierarchy
411     * @param programItem a {@link ProgramItem}
412     * @param parentProgram The parent program or subprogram. Can be null.
413     * @return the parent {@link Program} into this (sub)program or null if not found
414     */
415    public Program getParentProgram (ProgramItem programItem, AbstractProgram parentProgram)
416    {
417        AbstractProgram parent = parentProgram;
418        
419        ProgramItem parentItem = _odfHelper.getParentProgramItem(programItem, parentProgram);
420        while (parentItem != null && !(parentItem instanceof Program))
421        {
422            if (parent != null && parentItem.equals(parent))
423            {
424                // Once the desired abstract program parent is passed, the parent is null
425                parent = null;
426            }
427            parentItem = _odfHelper.getParentProgramItem(parentItem, parent);
428        }
429        
430        return parentItem != null ? (Program) parentItem : null;
431    }
432    
433    /**
434     * Returns the nearest {@link AbstractProgram} ancestors.
435     * @param programPart a {@link ProgramPart}
436     * @return the nearest {@link AbstractProgram} ancestors containing this program part
437     */
438    public List<AbstractProgram> getNearestAncestorAbstractPrograms (ProgramPart programPart)
439    {
440        List<AbstractProgram> ancestors = new ArrayList<>();
441        
442        List<ProgramPart> parents = programPart.getProgramPartParents();
443        for (ProgramPart parent : parents)
444        {
445            if (parent instanceof AbstractProgram)
446            {
447                ancestors.add((AbstractProgram) parent);
448            }
449            else
450            {
451                ancestors.addAll(getNearestAncestorAbstractPrograms(parent));
452            }
453        }
454        
455        return ancestors;
456    }
457    
458    /**
459     * Returns the nearest {@link AbstractProgram} ancestor.
460     * @param programItem a {@link ProgramItem}
461     * @param parentProgram The parent program or subprogram
462     * @return the nearest {@link AbstractProgram} ancestor into this (sub)program or null if not found
463     */
464    public AbstractProgram getNearestAncestorAbstractProgram (ProgramItem programItem, AbstractProgram parentProgram)
465    {
466        ProgramItem parentItem = _odfHelper.getParentProgramItem(programItem, parentProgram);
467        while (parentItem != null && !(parentItem instanceof AbstractProgram))
468        {
469            parentItem = _odfHelper.getParentProgramItem(parentItem, parentProgram);
470        }
471        
472        return parentItem != null ? (AbstractProgram) parentItem : null;
473    }
474    
475    /**
476     * Get the path of a Program page from the Program content.
477     * @param siteName the site name.
478     * @param language the language.
479     * @param program the program.
480     * @return the page path or empty if no page matches
481     */
482    public String getProgramPagePath(String siteName, String language, Program program)
483    {
484        return getProgramItemPagePath(siteName, language, program, program);
485    }
486    
487    /**
488     * Get the path of a {@link ProgramItem} page into the given {@link Program}
489     * @param siteName the site name
490     * @param language the language
491     * @param programItem the subprogram.
492     * @param parentProgram The parent program
493     * @return the page path or empty if no page matches
494     */
495    public String getProgramItemPagePath(String siteName, String language, ProgramItem programItem, Program parentProgram)
496    {
497        StringBuilder sb = new StringBuilder();
498        
499        Page rootPage = _odfPageHandler.getOdfRootPage(siteName, language, programItem.getCatalog());
500        if (rootPage != null)
501        {
502            if (programItem instanceof Program)
503            {
504                sb.append(rootPage.getSitemapName()).append('/')
505                    .append(rootPage.getPathInSitemap()).append('/')
506                    .append(_odfPageHandler.getLevel1PageName(rootPage, parentProgram)).append('/')
507                    .append(_odfPageHandler.getLevel2PageName(rootPage, parentProgram)).append('/')
508                    .append(_odfPageHandler.getPageName(programItem));
509            }
510            else
511            {
512                String pathInProgram = getPathInProgram(programItem, parentProgram);
513                if (pathInProgram != null)
514                {
515                    sb.append(rootPage.getSitemapName()).append('/')
516                        .append(rootPage.getPathInSitemap()).append('/')
517                        .append(_odfPageHandler.getLevel1PageName(rootPage, parentProgram)).append('/')
518                        .append(_odfPageHandler.getLevel2PageName(rootPage, parentProgram)).append('/')
519                        .append(pathInProgram);
520                }
521            }
522            
523        }
524        
525        return sb.toString();
526    }
527}