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