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.ArrayList;
019import java.util.Arrays;
020import java.util.Collection;
021import java.util.Iterator;
022import java.util.List;
023import java.util.stream.Collectors;
024import java.util.stream.Stream;
025
026import org.apache.commons.lang3.StringUtils;
027
028import org.ametys.cms.FilterNameHelper;
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.plugins.repository.data.holder.ModelLessDataHolder;
040import org.ametys.plugins.repository.data.holder.impl.DefaultModelLessDataHolder;
041import org.ametys.plugins.repository.data.repositorydata.RepositoryData;
042import org.ametys.plugins.repository.data.repositorydata.impl.MemoryRepositoryData;
043import org.ametys.web.repository.page.Page;
044import org.ametys.web.repository.page.UnknownZoneException;
045import org.ametys.web.repository.page.Zone;
046import org.ametys.web.repository.site.Site;
047import org.ametys.web.repository.sitemap.Sitemap;
048
049import com.google.common.collect.Iterables;
050
051/**
052 * Page representing a course.
053 */
054public class CoursePage extends AbstractOdfPage
055{
056    private Page _root;
057    private Course _course;
058    private String _path;
059    private Page _parentPage;
060    private Program _parentProgram;
061    private CoursePageFactory _factory;
062    
063    /**
064     * Constructor.
065     * @param factory The factory
066     * @param root the odf root page.
067     * @param course the course.
068     * @param parentProgram the parent program
069     * @param path path from the parent {@link ProgramPage}
070     * @param parentPage the parent {@link Page} or null if not yet computed.
071     */
072    public CoursePage(CoursePageFactory factory, Page root, Course course, Program parentProgram, String path, Page parentPage)
073    {
074        _factory = factory;
075        _root = root;
076        _course = course;
077        _path = path;
078        _parentPage = parentPage;
079        _parentProgram = parentProgram;
080    }
081    
082    /**
083     * Returns the associated {@link Course}.
084     * @return the associated {@link Course}.
085     */
086    public Course getCourse()
087    {
088        return _course;
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 getTemplate() throws AmetysRepositoryException
109    {
110        return "course";
111    }
112
113    @Override
114    public String getTitle() throws AmetysRepositoryException
115    {
116        return _course.getTitle();
117    }
118
119    @Override
120    public String getLongTitle() throws AmetysRepositoryException
121    {
122        return _course.getTitle();
123    }
124 
125    @Override
126    public Zone getZone(String name) throws UnknownZoneException, AmetysRepositoryException
127    {
128        if (!"default".equals(name))
129        {
130            throw new IllegalArgumentException("Only the zone named 'default' is actually supported on virtual program pages.");
131        }
132        
133        return new CourseZone(this, _factory.getZoneDataTypeEP(), _factory.getZoneItemDataTypeEP());
134    }
135
136    @Override
137    public AmetysObjectIterable< ? extends Zone> getZones() throws AmetysRepositoryException
138    {
139        ArrayList<Zone> zones = new ArrayList<>();
140        zones.add(new CourseZone(this, _factory.getZoneDataTypeEP(), _factory.getZoneItemDataTypeEP()));
141        return new CollectionIterable<>(zones);
142    }
143
144    @Override
145    public AmetysObjectIterable<? extends Page> getChildrenPages() throws AmetysRepositoryException
146    {
147        List<Page> coursePages = _traverseCourseLists().map(this::_toCoursePage).collect(Collectors.toList());
148        return new CollectionIterable<>(coursePages);
149    }
150
151    private CoursePage _toCoursePage(Course course)
152    {
153        return new CoursePage(_factory, _root, course, _parentProgram, _path + '/' + getName(), this);
154    }
155
156    @Override
157    public String getPathInSitemap() throws AmetysRepositoryException
158    {
159        String computeLevelsPath = _factory.getODFPageHandler().computeLevelsPath(_root, _parentProgram);
160        String path = StringUtils.isNotBlank(computeLevelsPath) ? computeLevelsPath + "/" + _path + "/" + getName() : _path + "/" + getName();
161        return _root.getPathInSitemap() + "/" + path;
162    }
163
164    @Override
165    public Site getSite() throws AmetysRepositoryException
166    {
167        return _root.getSite();
168    }
169
170    @Override
171    public String getSiteName() throws AmetysRepositoryException
172    {
173        return _root.getSiteName();
174    }
175
176    @Override
177    public Sitemap getSitemap() throws AmetysRepositoryException
178    {
179        return _root.getSitemap();
180    }
181
182    @Override
183    public String getSitemapName() throws AmetysRepositoryException
184    {
185        return _root.getSitemapName();
186    }
187
188    @SuppressWarnings("unchecked")
189    @Override
190    public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException
191    {
192        if (path.isEmpty())
193        {
194            throw new AmetysRepositoryException("path must be non empty");
195        }
196        
197        List<String> headQueuePath = Arrays.asList(StringUtils.split(path, "/", 2));
198        String name = headQueuePath.get(0);
199        String queuePath = Iterables.get(headQueuePath, 1, null); 
200        
201        return (A) _traverseCourseLists()
202            .filter(course -> _filterByPageName(course, name))
203            .findFirst()
204            .map(this::_toCoursePage)
205            .map(page -> StringUtils.isEmpty(queuePath) ? page : page.<Page>getChild(queuePath))
206            .orElseThrow(() -> 
207                new UnknownAmetysObjectException("Unknown child page '" + path + "' for page " + getId()));
208    }
209    
210    private boolean _filterByPageName(Course course, String pageName)
211    {
212        String coursePageName = FilterNameHelper.filterName(course.getTitle()) + "-" + course.getCode();
213        return StringUtils.equals(coursePageName, pageName);
214    }
215
216    @Override
217    public boolean hasChild(String name) throws AmetysRepositoryException
218    {
219        return _traverseCourseLists()
220            .filter(course -> _filterByPageName(course, name))
221            .findFirst()
222            .isPresent();
223    }
224
225    @Override
226    public String getId() throws AmetysRepositoryException
227    {
228        // E.g: course://licence-lea-anglais-allemand-H6YOLDDJ/parcours-1-f7usj1ss?rootId=xxx&courseId=xxx&programId=xxxx
229        return "course://" + _path + "?rootId=" + _root.getId() + "&courseId=" + _course.getId() + "&programId=" + _parentProgram.getId();
230    }
231    @Override
232    public String getName() throws AmetysRepositoryException
233    {
234        // E.g: langue-anglaise-1-H6YP1P98
235        return FilterNameHelper.filterName(_course.getTitle()) + "-" + _course.getCode();
236    }
237
238    @SuppressWarnings("unchecked")
239    @Override
240    public Page getParent() throws AmetysRepositoryException
241    {
242        if (_parentPage == null)
243        {
244            String computeLevelsPath = _factory.getODFPageHandler().computeLevelsPath(_root, _parentProgram);
245            String relParentPath =  StringUtils.isNotBlank(computeLevelsPath) ? computeLevelsPath + "/" + _path : _path;
246            _parentPage = _root.getChild(relParentPath);
247        }
248        
249        return _parentPage;
250    }
251    
252    @Override
253    public String getParentPath() throws AmetysRepositoryException
254    {
255        String computeLevelsPath = _factory.getODFPageHandler().computeLevelsPath(_root, _parentProgram);
256        String relParentPath =  StringUtils.isNotBlank(computeLevelsPath) ? computeLevelsPath + "/" + _path : _path;
257        
258        return _root.getPath() + "/" + relParentPath;
259    }
260
261    public ModelLessDataHolder getDataHolder()
262    {
263        RepositoryData repositoryData = new MemoryRepositoryData(getName());
264        return new DefaultModelLessDataHolder(_factory.getPageDataTypeEP(), repositoryData);
265    }
266
267    private Stream<Course> _traverseCourseLists()
268    {
269        CourseListTraverser traverser = new CourseListTraverser(_course.getCourseLists());
270        return traverser.stream()
271                .filter(Course.class::isInstance)
272                .map(Course.class::cast);
273    }
274    
275    static class CourseListTraverser extends AbstractTreeIterator<ProgramItem>
276    {
277        public CourseListTraverser(Collection<? extends ProgramItem> programPartChildren)
278        {
279            super(programPartChildren);
280        }
281        
282        @Override
283        protected Iterator<ProgramItem> provideChildIterator(ProgramItem parent)
284        {
285            if (parent instanceof CourseList)
286            {
287                CourseList courseList = (CourseList) parent;
288                return new CourseListTraverser(courseList.getCourses());
289            }
290            
291            return null;
292        }
293    }
294}