001/*
002 *  Copyright 2010 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.Collection;
020import java.util.Iterator;
021import java.util.List;
022import java.util.Objects;
023import java.util.Optional;
024import java.util.stream.Collectors;
025import java.util.stream.Stream;
026
027import org.apache.commons.lang3.StringUtils;
028
029import org.ametys.odf.ProgramItem;
030import org.ametys.odf.course.Course;
031import org.ametys.odf.courselist.CourseList;
032import org.ametys.odf.program.Program;
033import org.ametys.plugins.odfweb.repository.ProgramPage.AbstractTreeIterator;
034import org.ametys.plugins.repository.AmetysObject;
035import org.ametys.plugins.repository.AmetysObjectIterable;
036import org.ametys.plugins.repository.AmetysRepositoryException;
037import org.ametys.plugins.repository.CollectionIterable;
038import org.ametys.plugins.repository.UnknownAmetysObjectException;
039import org.ametys.web.repository.page.Page;
040import org.ametys.web.repository.page.virtual.VirtualPageConfiguration;
041
042import com.google.common.collect.Iterables;
043
044/**
045 * Page representing a course.
046 */
047@SuppressWarnings("unchecked")
048public class CoursePage extends AbstractProgramItemPage<CoursePageFactory>
049{
050    private Course _course;
051    private String _path;
052    private Page _parentPage;
053    private Program _parentProgram;
054    
055    /**
056     * Constructor.
057     * @param factory The factory
058     * @param root the odf root page.
059     * @param course the course.
060     * @param parentProgram the parent program
061     * @param path path from the parent {@link ProgramPage}
062     * @param parentPage the parent {@link Page} or null if not yet computed.
063     * @param configuration The Course virtual page's configuration
064     */
065    public CoursePage(Page root, VirtualPageConfiguration configuration, CoursePageFactory factory, Course course, Program parentProgram, String path, Page parentPage)
066    {
067        super(root, configuration, factory.getScheme(), factory);
068        
069        _course = course;
070        _path = path;
071        _parentPage = parentPage;
072        _parentProgram = parentProgram;
073    }
074    
075    /**
076     * Returns the associated {@link Course}.
077     * @return the associated {@link Course}.
078     */
079    public Course getCourse()
080    {
081        return _course;
082    }
083    
084    @Override
085    protected ProgramItem getProgramItem()
086    {
087        return getCourse();
088    }
089
090    @Override
091    public int getDepth() throws AmetysRepositoryException
092    {
093        int levelDepth = 0;
094        if (StringUtils.isNotBlank(_factory.getODFPageHandler().getLevel1Metadata(_root)))
095        {
096            levelDepth++;
097            if (StringUtils.isNotBlank(_factory.getODFPageHandler().getLevel2Metadata(_root)))
098            {
099                levelDepth++;
100            }
101        }
102        
103        return _root.getDepth() + levelDepth + _path.split("/").length;
104    }
105
106    @Override
107    public String getTitle() throws AmetysRepositoryException
108    {
109        return _course.getTitle();
110    }
111
112    @Override
113    public String getLongTitle() throws AmetysRepositoryException
114    {
115        return _course.getTitle();
116    }
117 
118    @Override
119    public AmetysObjectIterable<? extends Page> getChildrenPages() throws AmetysRepositoryException
120    {
121        Collection<CoursePage> children = _transformChildrenPages(_traverseCourseLists()).toList();
122        return new CollectionIterable<>(children);
123    }
124
125    private CoursePage _createCoursePage(Course course)
126    {
127        return _factory.createCoursePage(_root, course, _parentProgram, _path + '/' + getName(), this);
128    }
129
130    @Override
131    public String getPathInSitemap() throws AmetysRepositoryException
132    {
133        String path = _computePath(_root.getPathInSitemap());
134        return path == null ? null : path + "/" + getName();
135    }
136    
137    private Program _getParentProgram()
138    {
139        return _parentProgram;
140    }
141    
142    @Override
143    public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException
144    {
145        if (path.isEmpty())
146        {
147            throw new AmetysRepositoryException("path must be non empty");
148        }
149        
150        List<String> headQueuePath = Arrays.asList(StringUtils.split(path, "/", 2));
151        String name = headQueuePath.get(0);
152        String queuePath = Iterables.get(headQueuePath, 1, null);
153        
154        return (A) _findChildPage(name)
155                .map(cp -> _factory.getODFPageHandler().addRedirectIfNeeded(cp, name))
156                .map(cp -> _factory.getODFPageHandler().exploreQueuePath(cp, queuePath))
157                .orElseThrow(() -> new UnknownAmetysObjectException("Unknown child page '" + path + "' for page " + getId()));
158    }
159    
160    private Optional<CoursePage> _findChildPage(String name)
161    {
162        return _transformChildrenPages(_traverseCourseLists().filter(child -> _filterByName(child, name))).findFirst();
163    }
164    
165    private boolean _filterByName(Course course, String pageName)
166    {
167        // If last part is equals to the course code, the page matches
168        return course.getCode().equals(pageName.substring(pageName.lastIndexOf("-") + 1));
169    }
170
171    @Override
172    public boolean hasChild(String name) throws AmetysRepositoryException
173    {
174        return _findChildPage(name).isPresent();
175    }
176
177    @Override
178    public String getId() throws AmetysRepositoryException
179    {
180        // E.g: course://licence-lea-anglais-allemand-H6YOLDDJ/parcours-1-f7usj1ss?rootId=xxx&courseId=xxx&programId=xxxx
181        return "course://" + _path + "?rootId=" + _root.getId() + "&courseId=" + _course.getId() + "&programId=" + _parentProgram.getId();
182    }
183    @Override
184    public String getName() throws AmetysRepositoryException
185    {
186        // E.g: langue-anglaise-1-H6YP1P98
187        return _factory.getODFPageHandler().getPageName(_course);
188    }
189
190    @Override
191    public Page getParent() throws AmetysRepositoryException
192    {
193        if (_parentPage == null)
194        {
195            String childPath = _computePath(null);
196            if (childPath != null)
197            {
198                _parentPage = childPath.isEmpty() ? _root : _root.getChild(childPath);
199            }
200        }
201        
202        return _parentPage;
203    }
204    
205    @Override
206    public String getParentPath() throws AmetysRepositoryException
207    {
208        return _computePath(_root.getPath());
209    }
210
211    private Stream<Course> _traverseCourseLists()
212    {
213        CourseListTraverser traverser = new CourseListTraverser(_course.getCourseLists());
214        return traverser.stream()
215                .filter(Course.class::isInstance)
216                .map(Course.class::cast);
217    }
218    
219    static class CourseListTraverser extends AbstractTreeIterator<ProgramItem>
220    {
221        public CourseListTraverser(Collection<? extends ProgramItem> programPartChildren)
222        {
223            super(programPartChildren);
224        }
225        
226        @Override
227        protected Iterator<ProgramItem> provideChildIterator(ProgramItem parent)
228        {
229            if (parent instanceof CourseList courseList)
230            {
231                return new CourseListTraverser(courseList.getCourses());
232            }
233            
234            return null;
235        }
236    }
237    
238    @Override
239    public Course getContent()
240    {
241        Course course = getCourse();
242        course.setContextPath(getPathInSitemap());
243        
244        if (!_factory.isIndexing())
245        {
246            // computing educational paths is actually very expensive, and only useful during rendering
247            // we are very conservative here and only disable that computing for specific indexing cases
248            // (we could have chosen to only enable it when rendering, but we don't want to forget specific cases)
249            setCurrentEducationalPaths(course);
250        }
251        
252        return course;
253    }
254    
255    private String _computePath(String rootPath)
256    {
257        String levelsPath = _factory.getODFPageHandler().computeLevelsPath(_root, _getParentProgram());
258        
259        // The current program has no valid attributes for the levels selected in the ODF root
260        if (levelsPath == null)
261        {
262            throw new UnknownAmetysObjectException("Page of program " + _getParentProgram().getId() + " does not have a valid level path");
263        }
264        
265        return Stream.of(rootPath, levelsPath, _path)
266            .filter(StringUtils::isNotEmpty)
267            .collect(Collectors.joining("/"));
268    }
269    
270    private Stream<CoursePage> _transformChildrenPages(Stream<Course> children)
271    {
272        return children
273            .map(this::_createCoursePage)
274            .filter(Objects::nonNull)
275            // Test if the child page is in existing virtual pages
276            .filter(page -> {
277                try
278                {
279                    page.getPathInSitemap();
280                    return true;
281                }
282                catch (UnknownAmetysObjectException e)
283                {
284                    return false;
285                }
286            });
287    }
288}