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.Collections; 022import java.util.Iterator; 023import java.util.List; 024import java.util.NoSuchElementException; 025import java.util.Set; 026import java.util.stream.Collectors; 027import java.util.stream.Stream; 028 029import org.apache.commons.lang3.StringUtils; 030 031import org.ametys.cms.FilterNameHelper; 032import org.ametys.odf.ProgramItem; 033import org.ametys.odf.course.Course; 034import org.ametys.odf.courselist.CourseList; 035import org.ametys.odf.program.Program; 036import org.ametys.plugins.explorer.resources.ResourceCollection; 037import org.ametys.plugins.odfweb.repository.ProgramPage.AbstractTreeIterator; 038import org.ametys.plugins.repository.AmetysObject; 039import org.ametys.plugins.repository.AmetysObjectIterable; 040import org.ametys.plugins.repository.AmetysObjectIterator; 041import org.ametys.plugins.repository.AmetysObjectResolver; 042import org.ametys.plugins.repository.AmetysRepositoryException; 043import org.ametys.plugins.repository.CollectionIterable; 044import org.ametys.plugins.repository.UnknownAmetysObjectException; 045import org.ametys.plugins.repository.metadata.CompositeMetadata; 046import org.ametys.web.repository.page.Page; 047import org.ametys.web.repository.page.UnknownZoneException; 048import org.ametys.web.repository.page.Zone; 049import org.ametys.web.repository.site.Site; 050import org.ametys.web.repository.sitemap.Sitemap; 051 052import com.google.common.collect.Iterables; 053 054/** 055 * Page representing a course. 056 */ 057public class CoursePage implements Page 058{ 059 private AmetysObjectResolver _resolver; 060 private OdfPageHandler _odfPageHandler; 061 private Page _root; 062 private Course _course; 063 private String _path; 064 private Page _parentPage; 065 private Program _parentProgram; 066 067 /** 068 * Constructor. 069 * @param resolver the {@link AmetysObjectResolver}. 070 * @param odfPageHandler the {@link OdfPageHandler} to handle ODF pages. 071 * @param root the odf root page. 072 * @param course the course. 073 * @param parentProgram the parent program 074 * @param path path from the parent {@link ProgramPage} 075 * @param parentPage the parent {@link Page} or null if not yet computed. 076 */ 077 public CoursePage(AmetysObjectResolver resolver, OdfPageHandler odfPageHandler, Page root, Course course, Program parentProgram, String path, Page parentPage) 078 { 079 _resolver = resolver; 080 _odfPageHandler = odfPageHandler; 081 _root = root; 082 _course = course; 083 _path = path; 084 _parentPage = parentPage; 085 _parentProgram = parentProgram; 086 } 087 088 /** 089 * Compute the path from the root odf page, representing the first and second level pages. 090 * @return the path 091 */ 092 String _computeLevelsPath() 093 { 094 String level1 = _odfPageHandler.getProgramLevel1Value(_root, _parentProgram); 095 String level2 = _odfPageHandler.getProgramLevel2Value(_root, _parentProgram); 096 097 // The path is no more valid, re-calculate the real path 098 String secondLevelPageId = "odfLevel2://" + level1 + "/" + level2 + "?rootId=" + _root.getId(); 099 Page secondLevelPage = _resolver.resolveById(secondLevelPageId); 100 101 return secondLevelPage.getParent().getName() + "/" + secondLevelPage.getName(); 102 } 103 104 /** 105 * Returns the associated {@link Course}. 106 * @return the associated {@link Course}. 107 */ 108 public Course getCourse() 109 { 110 return _course; 111 } 112 113 @Override 114 public int getDepth() throws AmetysRepositoryException 115 { 116 return _root.getDepth() + 2 + _path.split("/").length; 117 } 118 119 @Override 120 public Set<String> getReferers() throws AmetysRepositoryException 121 { 122 throw new UnsupportedOperationException("getReferers not supported on virtual odf pages"); 123 } 124 125 @Override 126 public ResourceCollection getRootAttachments() throws AmetysRepositoryException 127 { 128 return null; 129 } 130 131 @Override 132 public String getTemplate() throws AmetysRepositoryException 133 { 134 return "course"; 135 } 136 137 @Override 138 public String getTitle() throws AmetysRepositoryException 139 { 140 return _course.getTitle(); 141 } 142 143 @Override 144 public String getLongTitle() throws AmetysRepositoryException 145 { 146 return _course.getTitle(); 147 } 148 149 @Override 150 public PageType getType() throws AmetysRepositoryException 151 { 152 return PageType.CONTAINER; 153 } 154 155 @Override 156 public String getURL() throws AmetysRepositoryException 157 { 158 throw new UnsupportedOperationException("getURL not supported on virtual odf pages"); 159 } 160 161 @Override 162 public LinkType getURLType() throws AmetysRepositoryException 163 { 164 throw new UnsupportedOperationException("getURLType not supported on virtual odf pages"); 165 } 166 167 @Override 168 public Zone getZone(String name) throws UnknownZoneException, AmetysRepositoryException 169 { 170 if (!"default".equals(name)) 171 { 172 throw new IllegalArgumentException("Only the zone named 'default' is actually supported on virtual program pages."); 173 } 174 175 return new CourseZone(this); 176 } 177 178 @Override 179 public AmetysObjectIterable< ? extends Zone> getZones() throws AmetysRepositoryException 180 { 181 ArrayList<Zone> zones = new ArrayList<>(); 182 zones.add(new CourseZone(this)); 183 return new CollectionIterable<>(zones); 184 } 185 186 @Override 187 public boolean hasZone(String name) throws AmetysRepositoryException 188 { 189 return "default".equals(name); 190 } 191 192 @Override 193 public AmetysObjectIterable<? extends Page> getChildrenPages() throws AmetysRepositoryException 194 { 195 List<Page> coursePages = _traverseCourseLists().map(this::_toCoursePage).collect(Collectors.toList()); 196 return new CollectionIterable<>(coursePages); 197 } 198 199 private CoursePage _toCoursePage(Course course) 200 { 201 return new CoursePage(_resolver, _odfPageHandler, _root, course, _parentProgram, _path + '/' + getName(), this); 202 } 203 204 @Override 205 public String getPathInSitemap() throws AmetysRepositoryException 206 { 207 return _root.getPathInSitemap() + "/" + _computeLevelsPath() + "/" + _path + "/" + getName(); 208 } 209 210 @Override 211 public Site getSite() throws AmetysRepositoryException 212 { 213 return _root.getSite(); 214 } 215 216 @Override 217 public String getSiteName() throws AmetysRepositoryException 218 { 219 return _root.getSiteName(); 220 } 221 222 @Override 223 public Sitemap getSitemap() throws AmetysRepositoryException 224 { 225 return _root.getSitemap(); 226 } 227 228 @Override 229 public String getSitemapName() throws AmetysRepositoryException 230 { 231 return _root.getSitemapName(); 232 } 233 234 @SuppressWarnings("unchecked") 235 @Override 236 public <A extends AmetysObject> A getChild(String path) throws AmetysRepositoryException, UnknownAmetysObjectException 237 { 238 if (path.isEmpty()) 239 { 240 throw new AmetysRepositoryException("path must be non empty"); 241 } 242 243 List<String> headQueuePath = Arrays.asList(StringUtils.split(path, "/", 2)); 244 String name = headQueuePath.get(0); 245 String queuePath = Iterables.get(headQueuePath, 1, null); 246 247 return (A) _traverseCourseLists() 248 .filter(course -> _filterByPageName(course, name)) 249 .findFirst() 250 .map(this::_toCoursePage) 251 .map(page -> StringUtils.isEmpty(queuePath) ? page : page.<Page>getChild(queuePath)) 252 .orElseThrow(() -> 253 new UnknownAmetysObjectException("Unknown child page '" + path + "' for page " + getId())); 254 } 255 256 private boolean _filterByPageName(Course course, String pageName) 257 { 258 String coursePageName = FilterNameHelper.filterName(course.getTitle()) + "-" + course.getCode(); 259 return StringUtils.equals(coursePageName, pageName); 260 } 261 262 @SuppressWarnings("unchecked") 263 @Override 264 public AmetysObjectIterable<? extends Page> getChildren() throws AmetysRepositoryException 265 { 266 return getChildrenPages(); 267 } 268 269 @Override 270 public boolean hasChild(String name) throws AmetysRepositoryException 271 { 272 return _traverseCourseLists() 273 .filter(course -> _filterByPageName(course, name)) 274 .findFirst() 275 .isPresent(); 276 } 277 278 @Override 279 public String getId() throws AmetysRepositoryException 280 { 281 // E.g: course://licence-lea-anglais-allemand-program-h6yolddj/parcours-1-subprogram-f7usj1ss?rootId=xxx&courseId=xxx&programId=xxxx 282 return "course://" + _path + "?rootId=" + _root.getId() + "&courseId=" + _course.getId() + "&programId=" + _parentProgram.getId(); 283 } 284 @Override 285 public String getName() throws AmetysRepositoryException 286 { 287 // E.g: langue-anglaise-1-h6yp1p98 288 return FilterNameHelper.filterName(_course.getTitle()) + "-" + _course.getCode(); 289 } 290 291 @SuppressWarnings("unchecked") 292 @Override 293 public Page getParent() throws AmetysRepositoryException 294 { 295 if (_parentPage == null) 296 { 297 String relParentPath = _computeLevelsPath() + "/" + _path; 298 _parentPage = _root.getChild(relParentPath); 299 } 300 301 return _parentPage; 302 } 303 304 @Override 305 public String getParentPath() throws AmetysRepositoryException 306 { 307 return _root.getPath() + "/" + _computeLevelsPath() + "/" + _path; 308 } 309 310 @Override 311 public String getPath() throws AmetysRepositoryException 312 { 313 return getParentPath() + "/" + getName(); 314 } 315 316 @Override 317 public CompositeMetadata getMetadataHolder() 318 { 319 return new StaticCompositeMetadata(); 320 } 321 322 @Override 323 public Set<String> getTags() throws AmetysRepositoryException 324 { 325 return Collections.emptySet(); 326 } 327 328 @Override 329 public boolean isVisible() throws AmetysRepositoryException 330 { 331 return true; 332 } 333 334 @Override 335 public AmetysObjectIterable< ? extends Page> getChildrenPages(boolean includeInvisiblePages) throws AmetysRepositoryException 336 { 337 return getChildrenPages(); 338 } 339 340 public Page getChildPageAt(int index) throws UnknownAmetysObjectException, AmetysRepositoryException 341 { 342 // TODO make a default method or call a helper instead of duplicate the code into each Page (FirstLevel / SecondLevel / Program etc...) 343 344 if (index < 0) 345 { 346 throw new AmetysRepositoryException("Child page index cannot be negative"); 347 } 348 349 AmetysObjectIterator<? extends Page> childPages = getChildrenPages().iterator(); 350 351 try 352 { 353 childPages.skip(index); 354 return childPages.next(); 355 } 356 catch (NoSuchElementException e) 357 { 358 throw new UnknownAmetysObjectException("There's no child page at index " + index + " for page " + this.getId()); 359 } 360 } 361 362 private Stream<Course> _traverseCourseLists() 363 { 364 CourseListTraverser traverser = new CourseListTraverser(_course.getCourseLists()); 365 return traverser.stream() 366 .filter(Course.class::isInstance) 367 .map(Course.class::cast); 368 } 369 370 static class CourseListTraverser extends AbstractTreeIterator<ProgramItem> 371 { 372 public CourseListTraverser(Collection<? extends ProgramItem> programPartChildren) 373 { 374 super(programPartChildren); 375 } 376 377 @Override 378 protected Iterator<ProgramItem> provideChildIterator(ProgramItem parent) 379 { 380 if (parent instanceof CourseList) 381 { 382 CourseList courseList = (CourseList) parent; 383 return new CourseListTraverser(courseList.getCourses()); 384 } 385 386 return null; 387 } 388 } 389}