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