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}