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