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.service.ServiceException;
026import org.apache.avalon.framework.service.ServiceManager;
027import org.apache.avalon.framework.service.Serviceable;
028import org.apache.commons.lang.StringUtils;
029
030import org.ametys.cms.repository.Content;
031import org.ametys.odf.ODFHelper;
032import org.ametys.odf.ProgramItem;
033import org.ametys.odf.course.Course;
034import org.ametys.odf.courselist.CourseList;
035import org.ametys.odf.program.AbstractProgram;
036import org.ametys.odf.program.Program;
037import org.ametys.odf.program.ProgramPart;
038import org.ametys.odf.program.SubProgram;
039import org.ametys.plugins.repository.AmetysObjectResolver;
040import org.ametys.plugins.repository.UnknownAmetysObjectException;
041import org.ametys.runtime.config.Config;
042import org.ametys.runtime.plugin.component.AbstractLogEnabled;
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        ProgramItem parent = null;
271        
272        Course parentCourse = getNearestAncestorCourse(course, parentAbstractProgram);
273        if (parentCourse != null)
274        {
275            parent = parentCourse;
276        }
277        else
278        {
279            parent = getNearestAncestorAbstractProgram(course, parentAbstractProgram);
280        }
281        Program parentProgram = getParentProgram(course, parentAbstractProgram);
282        
283        if (parent == null || parentProgram == null)
284        {
285            // No page
286            return null;
287        }
288     
289        // Id is like course://path/from/program?rootId=xxx&courseId=xxx&programId=xxxx
290        String pageId =  "course://" + getPathInProgram(parent, parentProgram) + "?rootId=" + odfRootPage.getId() + "&courseId=" + course.getId() + "&programId=" + parentProgram.getId();
291        try
292        {
293            return _ametysResolver.resolveById(pageId);
294        }
295        catch (UnknownAmetysObjectException e)
296        {
297            return null;
298        }
299    }
300    
301    /**
302     * Return the course page
303     * @param course the course
304     * @param parentCourse the parent course. Can NOT be null.
305     * @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.
306     * @return the course page or null if not found
307     */
308    public CoursePage getCoursePage (Course course, Course parentCourse, String siteName)
309    {
310        String catalog = course.getCatalog();
311        Page odfRootPage = getOdfRootPage(siteName, course.getLanguage(), catalog);
312        
313        if (odfRootPage == null)
314        {
315            return null;
316        }
317        
318        return getCoursePage(odfRootPage, course, parentCourse);
319    }
320    
321    /**
322     * Return the course page
323     * @param odfRootPage the odf root page
324     * @param course the course
325     * @param parentCourse the parent course. Can NOT be null.
326     * @return the course page or null if not found
327     */
328    public CoursePage getCoursePage (Page odfRootPage, Course course, Course parentCourse)
329    {
330        AbstractProgram parent = getNearestAncestorAbstractProgram(parentCourse, null);
331        Program parentProgram = getParentProgram(parentCourse, null);
332        
333        if (parent == null || parentProgram == null)
334        {
335            // No page
336            return null;
337        }
338     
339        // Id is like course://path/from/program?rootId=xxx&courseId=xxx&programId=xxxx
340        String pageId =  "course://" + getPathInProgram(parentCourse, parentProgram) + "?rootId=" + odfRootPage.getId() + "&courseId=" + course.getId() + "&programId=" + parentProgram.getId();
341        try
342        {
343            return _ametysResolver.resolveById(pageId);
344        }
345        catch (UnknownAmetysObjectException e)
346        {
347            return null;
348        }
349    }
350    
351    /**
352     * Get the ODF root page, either in the given site if it exists, or in the default ODF site.
353     * @param siteName the desired site name.
354     * @param language the sitemap language to search in.
355     * @param catalog The ODF catalog
356     * @return the ODF root page, either in the given site if it exists, or in the default ODF site.
357     */
358    public Page getOdfRootPage(String siteName, String language, String catalog)
359    {
360        Page odfRootPage = null;
361        
362        if (StringUtils.isNotEmpty(siteName))
363        {
364            odfRootPage = _odfPageHandler.getOdfRootPage(siteName, language, catalog);
365        }
366        
367        if (odfRootPage == null)
368        {
369            String odfSiteName = Config.getInstance().getValue("odf.web.site.name");
370            odfRootPage = _odfPageHandler.getOdfRootPage(odfSiteName, language, catalog);
371        }
372        
373        return odfRootPage;
374    }
375    
376    /**
377     * Get the path in sitemap of a ODF content into a {@link Program} or {@link SubProgram}<br>
378     * Be careful, this is the path in sitemap, to get the path of a item into a Program, use {@link ODFHelper#getPathInProgram} instead.
379     * @param item The program item
380     * @param parentProgram The parent root (sub)program. Can be null.
381     * @return the path in sitemap from the parent program
382     */
383    public String getPathInProgram (ProgramItem item, AbstractProgram parentProgram)
384    {
385        if (item instanceof Program || item.equals(parentProgram))
386        {
387            // The program item is already the program it self
388            return _odfPageHandler.getPageName(item);
389        }
390        
391        List<String> paths = new ArrayList<>();
392        if (item instanceof AbstractProgram || item instanceof Course)
393        {
394            paths.add(_odfPageHandler.getPageName(item));
395        }
396        
397        ProgramItem parent = _odfHelper.getParentProgramItem(item, parentProgram);
398        while (parent != null && !(parent instanceof Program))
399        {
400            if (parent instanceof AbstractProgram || parent instanceof Course)
401            {
402                paths.add(_odfPageHandler.getPageName(parent));
403            }
404            parent = _odfHelper.getParentProgramItem(parent, parentProgram);
405        }
406        
407        if (parent != null)
408        {
409            paths.add(_odfPageHandler.getPageName(parent));
410            Collections.reverse(paths);
411            return org.apache.commons.lang3.StringUtils.join(paths, "/");
412        }
413        
414        return null;
415    }
416    
417    /**
418     * Returns the first {@link Program} ancestor, ensuring that the given parent content 'parentProgram' is in the hierarchy, if not null.<br>
419     * If 'parentProgram' is null, the first {@link Program} ancestor will be returned regardless of parent hierarchy.<br>
420     * If 'parentProgram' is a {@link SubProgram}, the first {@link Program} ancestor from this {@link SubProgram} will be returned regardless of parent hierarchy
421     * @param programItem a {@link ProgramItem}
422     * @param parentProgram The parent program or subprogram. Can be null.
423     * @return the parent {@link Program} into this (sub)program or null if not found
424     */
425    public Program getParentProgram (ProgramItem programItem, AbstractProgram parentProgram)
426    {
427        AbstractProgram parent = parentProgram;
428        
429        ProgramItem parentItem = _odfHelper.getParentProgramItem(programItem, parentProgram);
430        while (parentItem != null && !(parentItem instanceof Program))
431        {
432            if (parent != null && parentItem.equals(parent))
433            {
434                // Once the desired abstract program parent is passed, the parent is null
435                parent = null;
436            }
437            parentItem = _odfHelper.getParentProgramItem(parentItem, parent);
438        }
439        
440        return parentItem != null ? (Program) parentItem : null;
441    }
442    
443    /**
444     * Returns the nearest {@link AbstractProgram} ancestors.
445     * @param programPart a {@link ProgramPart}
446     * @return the nearest {@link AbstractProgram} ancestors containing this program part
447     */
448    public List<AbstractProgram> getNearestAncestorAbstractPrograms (ProgramPart programPart)
449    {
450        List<AbstractProgram> ancestors = new ArrayList<>();
451        
452        List<ProgramPart> parents = programPart.getProgramPartParents();
453        for (ProgramPart parent : parents)
454        {
455            if (parent instanceof AbstractProgram)
456            {
457                ancestors.add((AbstractProgram) parent);
458            }
459            else
460            {
461                ancestors.addAll(getNearestAncestorAbstractPrograms(parent));
462            }
463        }
464        
465        return ancestors;
466    }
467    
468    /**
469     * Returns the nearest {@link AbstractProgram} ancestor.
470     * @param programItem a {@link ProgramItem}
471     * @param parentProgram The parent program or subprogram
472     * @return the nearest {@link AbstractProgram} ancestor into this (sub)program or null if not found
473     */
474    public AbstractProgram getNearestAncestorAbstractProgram (ProgramItem programItem, AbstractProgram parentProgram)
475    {
476        ProgramItem parentItem = _odfHelper.getParentProgramItem(programItem, parentProgram);
477        while (parentItem != null && !(parentItem instanceof AbstractProgram))
478        {
479            parentItem = _odfHelper.getParentProgramItem(parentItem, parentProgram);
480        }
481        
482        return parentItem != null ? (AbstractProgram) parentItem : null;
483    }
484    
485    /**
486     * Returns the nearest {@link Course} ancestor.
487     * @param course a {@link Course}
488     * @param parentProgram The parent program or subprogram
489     * @return the nearest {@link Course} ancestor into this (sub)program or null if not found
490     */
491    public Course getNearestAncestorCourse (Course course, AbstractProgram parentProgram)
492    {
493        ProgramItem parentItem = _odfHelper.getParentProgramItem(course, parentProgram);
494        while (parentItem != null && !(parentItem instanceof Course) && !(parentItem instanceof AbstractProgram))
495        {
496            parentItem = _odfHelper.getParentProgramItem(parentItem, parentProgram);
497        }
498        
499        return parentItem != null && parentItem instanceof Course ? (Course) parentItem : null;
500    }
501    
502    /**
503     * Get the path of a Program page from the Program content.
504     * @param siteName the site name.
505     * @param language the language.
506     * @param program the program.
507     * @return the page path or empty if no page matches
508     */
509    public String getProgramPagePath(String siteName, String language, Program program)
510    {
511        return getProgramItemPagePath(siteName, language, program, program);
512    }
513    
514    /**
515     * Get the path of a {@link ProgramItem} page into the given {@link Program}
516     * @param siteName the site name
517     * @param language the language
518     * @param programItem the subprogram.
519     * @param parentProgram The parent program
520     * @return the page path or empty if no page matches
521     */
522    public String getProgramItemPagePath(String siteName, String language, ProgramItem programItem, Program parentProgram)
523    {
524        StringBuilder sb = new StringBuilder();
525        
526        Page rootPage = _odfPageHandler.getOdfRootPage(siteName, language, programItem.getCatalog());
527        if (rootPage != null)
528        {
529            if (programItem instanceof Program)
530            {
531                sb.append(rootPage.getSitemapName()).append('/')
532                    .append(rootPage.getPathInSitemap()).append('/')
533                    .append(_odfPageHandler.getLevel1PageName(rootPage, parentProgram)).append('/')
534                    .append(_odfPageHandler.getLevel2PageName(rootPage, parentProgram)).append('/')
535                    .append(_odfPageHandler.getPageName(programItem));
536            }
537            else
538            {
539                String pathInProgram = getPathInProgram(programItem, parentProgram);
540                if (pathInProgram != null)
541                {
542                    sb.append(rootPage.getSitemapName()).append('/')
543                        .append(rootPage.getPathInSitemap()).append('/')
544                        .append(_odfPageHandler.getLevel1PageName(rootPage, parentProgram)).append('/')
545                        .append(_odfPageHandler.getLevel2PageName(rootPage, parentProgram)).append('/')
546                        .append(pathInProgram);
547                }
548            }
549            
550        }
551        
552        return sb.toString();
553    }
554}