001/* 002 * Copyright 2019 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.service.search; 017 018import java.util.ArrayDeque; 019import java.util.ArrayList; 020import java.util.Collection; 021import java.util.Collections; 022import java.util.Date; 023import java.util.Deque; 024import java.util.List; 025import java.util.Locale; 026import java.util.Objects; 027import java.util.stream.Collectors; 028 029import org.apache.cocoon.xml.XMLUtils; 030import org.slf4j.Logger; 031import org.xml.sax.ContentHandler; 032import org.xml.sax.SAXException; 033 034import org.ametys.cms.repository.Content; 035import org.ametys.core.util.DateUtils; 036import org.ametys.odf.ODFHelper; 037import org.ametys.odf.ProgramItem; 038import org.ametys.odf.course.Course; 039import org.ametys.odf.program.Program; 040import org.ametys.plugins.repository.AmetysObject; 041import org.ametys.web.frontoffice.search.metamodel.ReturnableSaxer; 042import org.ametys.web.frontoffice.search.metamodel.impl.PageReturnable; 043import org.ametys.web.frontoffice.search.metamodel.impl.PageSaxer; 044import org.ametys.web.frontoffice.search.requesttime.SearchComponentArguments; 045 046/** 047 * {@link ReturnableSaxer} for {@link CourseReturnable} 048 * <br> 049 * <br>Note for developpers: This Saxer extends PageSaxer to have access to its protected method #saxContent() 050 */ 051public class CourseSaxer extends PageSaxer 052{ 053 private CourseReturnable _courseReturnable; 054 055 /** 056 * Constructor 057 * @param pageReturnable The course returnable (needed for superclass) 058 * @param courseReturnable The associated returnable on courses 059 */ 060 public CourseSaxer(PageReturnable pageReturnable, CourseReturnable courseReturnable) 061 { 062 super(pageReturnable); 063 _courseReturnable = courseReturnable; 064 } 065 066 @Override 067 public boolean canSax(AmetysObject hit, Logger logger, SearchComponentArguments args) 068 { 069 return hit instanceof Course; 070 } 071 072 @Override 073 public void sax(ContentHandler contentHandler, AmetysObject hit, Logger logger, SearchComponentArguments args) throws SAXException 074 { 075 Course course = (Course) hit; 076 _saxCourseContent(contentHandler, course, logger, args); 077 _saxCoursePages(contentHandler, course); 078 } 079 080 private void _saxCourseContent(ContentHandler contentHandler, Course course, Logger logger, SearchComponentArguments args) throws SAXException 081 { 082 XMLUtils.createElement(contentHandler, "title", course.getTitle()); 083 Date lastValidationDate = course.getLastValidationDate(); 084 if (lastValidationDate != null) 085 { 086 XMLUtils.createElement(contentHandler, "lastValidation", DateUtils.dateToString(lastValidationDate)); 087 } 088 Locale locale = new Locale(args.currentPage().getSitemapName()); 089 saxContent(course, "index", locale, contentHandler, logger); 090 } 091 092 private void _saxCoursePages(ContentHandler contentHandler, Course course) throws SAXException 093 { 094 CoursePathSaxer coursePathSaxer = new CoursePathSaxer(contentHandler); 095 096 List<CoursePagePath> coursePagePaths = new CoursePagePathsRetriever(_courseReturnable._odfHelper).retrieve(course); 097 098 for (CoursePagePath coursePagePath : coursePagePaths) 099 { 100 String uri = coursePagePath.toUri(); 101 String resolvedUri = _courseReturnable._odfURIResolver.resolve(uri, false, false, false); 102 103 XMLUtils.startElement(contentHandler, "coursePage"); 104 XMLUtils.createElement(contentHandler, "uri", uri); 105 XMLUtils.createElement(contentHandler, "resolvedUri", resolvedUri); 106 coursePathSaxer.saxPathTitles(coursePagePath); 107 XMLUtils.endElement(contentHandler, "coursePage"); 108 } 109 } 110 111 static class CoursePagePathsRetriever 112 { 113 private ODFHelper _odfHelper; 114 115 CoursePagePathsRetriever(ODFHelper odfHelper) 116 { 117 _odfHelper = odfHelper; 118 } 119 120 List<CoursePagePath> retrieve(Course course) 121 { 122 return _getCoursePagePaths(course) 123 .stream() 124 .distinct() 125 .collect(Collectors.toList()); 126 } 127 128 private List<CoursePagePath> _getCoursePagePaths(Course course) 129 { 130 CoursePagePath base = new CoursePagePath(course); 131 List<CoursePagePath> incompletePaths = new ArrayList<>(Collections.singletonList(base)); 132 List<CoursePagePath> completePaths = new ArrayList<>(); 133 134 while (!incompletePaths.isEmpty()) 135 { 136 completePaths.addAll(_tryCompletePaths(incompletePaths)); 137 } 138 139 return completePaths; 140 } 141 142 private List<CoursePagePath> _tryCompletePaths(List<CoursePagePath> incompletePaths) 143 { 144 List<CoursePagePath> completePaths = new ArrayList<>(); 145 List<CoursePagePath> newIncompletePaths = new ArrayList<>(); 146 147 for (CoursePagePath incompletePath : incompletePaths) 148 { 149 List<CoursePagePath> pathsOnParentsLevel = _pathsOnParentsLevel(incompletePath); 150 _determineIfComplete(pathsOnParentsLevel, completePaths, newIncompletePaths); 151 } 152 153 incompletePaths.clear(); 154 incompletePaths.addAll(newIncompletePaths); 155 return completePaths; 156 } 157 158 private List<CoursePagePath> _pathsOnParentsLevel(CoursePagePath incompletePath) 159 { 160 ProgramItem lastItem = incompletePath.lastItem(); 161 return _odfHelper.getParentAbstractPrograms(lastItem) 162 .stream() 163 .map(item -> new CoursePagePath(incompletePath).addParent(item)) 164 .collect(Collectors.toList()); 165 } 166 167 private void _determineIfComplete(List<CoursePagePath> pathsOnParentsLevel, List<CoursePagePath> completePaths, List<CoursePagePath> newIncompletePaths) 168 { 169 for (CoursePagePath pathOnParentLevel : pathsOnParentsLevel) 170 { 171 if (pathOnParentLevel.lastItem() instanceof Program) 172 { 173 completePaths.add(pathOnParentLevel); 174 } 175 else 176 { 177 newIncompletePaths.add(pathOnParentLevel); 178 } 179 } 180 } 181 } 182 183 184 static class CoursePagePath 185 { 186 private Deque<ProgramItem> _items = new ArrayDeque<>(); // sorted from deep in the hierarchy (course) to high in the hierarchy (program) 187 188 CoursePagePath(CoursePagePath other) 189 { 190 _items = new ArrayDeque<>(other._items); 191 } 192 193 CoursePagePath(Course course) 194 { 195 _items.addLast(course); 196 } 197 198 CoursePagePath addParent(ProgramItem parent) 199 { 200 _items.addLast(parent); 201 return this; 202 } 203 204 ProgramItem lastItem() 205 { 206 return _items.getLast(); 207 } 208 209 String toUri() 210 { 211 return _items 212 .stream() 213 .map(ProgramItem::getId) 214 .collect(Collectors.joining(";")); 215 } 216 217 Collection<ProgramItem> fromProgramToCourseItems() 218 { 219 ArrayList<ProgramItem> result = new ArrayList<>(_items); 220 Collections.reverse(result); 221 return result; 222 } 223 224 @Override 225 public int hashCode() 226 { 227 return Objects.hash(_items); 228 } 229 230 @Override 231 public boolean equals(Object obj) 232 { 233 if (this == obj) 234 { 235 return true; 236 } 237 if (obj == null) 238 { 239 return false; 240 } 241 if (getClass() != obj.getClass()) 242 { 243 return false; 244 } 245 CoursePagePath other = (CoursePagePath) obj; 246 return Objects.equals(_items, other._items); 247 } 248 } 249 250 static class CoursePathSaxer 251 { 252 private ContentHandler _contentHandler; 253 254 CoursePathSaxer(ContentHandler contentHandler) 255 { 256 _contentHandler = contentHandler; 257 } 258 259 void saxPathTitles(CoursePagePath coursePagePath) throws SAXException 260 { 261 XMLUtils.startElement(_contentHandler, "path"); 262 for (ProgramItem programItem : coursePagePath.fromProgramToCourseItems()) 263 { 264 if (programItem instanceof Content) 265 { 266 XMLUtils.createElement(_contentHandler, "element", ((Content) programItem).getTitle()); 267 } 268 } 269 XMLUtils.endElement(_contentHandler, "path"); 270 } 271 } 272} 273