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.odf.ose.export.utils; 017 018import java.util.Collection; 019import java.util.Collections; 020import java.util.List; 021import java.util.Objects; 022import java.util.Optional; 023import java.util.Set; 024import java.util.stream.Collectors; 025 026import org.apache.avalon.framework.component.Component; 027import org.apache.avalon.framework.service.ServiceException; 028import org.apache.avalon.framework.service.ServiceManager; 029import org.apache.avalon.framework.service.Serviceable; 030 031import org.ametys.cms.data.ContentValue; 032import org.ametys.cms.repository.Content; 033import org.ametys.odf.ODFHelper; 034import org.ametys.odf.ProgramItem; 035import org.ametys.odf.course.Course; 036import org.ametys.odf.enumeration.OdfReferenceTableEntry; 037import org.ametys.odf.enumeration.OdfReferenceTableHelper; 038import org.ametys.odf.orgunit.OrgUnit; 039import org.ametys.odf.ose.export.OSEConstants; 040import org.ametys.odf.program.AbstractProgram; 041import org.ametys.odf.program.Container; 042import org.ametys.plugins.repository.AmetysObjectResolver; 043import org.ametys.plugins.repository.AmetysRepositoryException; 044import org.ametys.runtime.model.ModelHelper; 045 046/** 047 * A retriever used like a helper to retrieve some ODF elements from ODF items. 048 */ 049public class ElementRetriever implements Component, Serviceable 050{ 051 /** Avalon Role */ 052 public static final String ROLE = ElementRetriever.class.getName(); 053 054 /** The helper for ODF contents */ 055 protected ODFHelper _odfHelper; 056 /** The helper for reference tables from ODF */ 057 protected OdfReferenceTableHelper _refTableHelper; 058 /** The resolver */ 059 protected AmetysObjectResolver _resolver; 060 061 @Override 062 public void service(ServiceManager manager) throws ServiceException 063 { 064 _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE); 065 _refTableHelper = (OdfReferenceTableHelper) manager.lookup(OdfReferenceTableHelper.ROLE); 066 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 067 } 068 069 /** 070 * Retrieve the steps ({@link Container}s of nature 'annee') of a given {@link ProgramItem} 071 * @param programItem The program item 072 * @return the steps ({@link Container}s of nature 'annee') of the given program item 073 */ 074 public Set<Container> retrieveSteps(ProgramItem programItem) 075 { 076 if (_mustNotExport(programItem)) 077 { 078 return Collections.EMPTY_SET; 079 } 080 081 return _odfHelper.getParentProgramItems(programItem) 082 .stream() 083 .map(this::_getSelfAsStepOrParentSteps) 084 .flatMap(Set::stream) 085 .collect(Collectors.toSet()); 086 } 087 088 private boolean _mustNotExport(ProgramItem programItem) 089 { 090 if (programItem instanceof Course || programItem instanceof Container) 091 { 092 return ((Content) programItem).getValue(OSEConstants.NO_OSE_EXPORT_ATTRIBUTE_NAME, false, false); 093 } 094 else 095 { 096 return false; 097 } 098 } 099 100 private Set<Container> _getSelfAsStepOrParentSteps(ProgramItem programItem) 101 { 102 return Optional.of(programItem) 103 .filter(this::_isStep) 104 .map(Container.class::cast) 105 .map(Collections::singleton) // Get programItem if it is a step 106 .orElseGet(() -> retrieveSteps(programItem)); // Otherwise search in the parents 107 } 108 109 /** 110 * Retrieve the {@link OrgUnit}s of a given {@link ProgramItem} 111 * @param programItem The program item 112 * @return the {@link OrgUnit}s of the given program item 113 */ 114 public Set<OrgUnit> retrieveOrgUnits(ProgramItem programItem) 115 { 116 return _retrieveOrgUnits(programItem); 117 } 118 119 private Set<OrgUnit> _retrieveOrgUnits(ProgramItem parent) 120 { 121 Set<OrgUnit> directOrgUnits = _getDirectOrgUnits(parent); 122 return directOrgUnits.isEmpty() 123 ? _retrieveOrgUnitsFromParents(parent) 124 : directOrgUnits; 125 } 126 127 private Set<OrgUnit> _retrieveOrgUnitsFromParents(ProgramItem programItem) 128 { 129 return _odfHelper.getParentProgramItems(programItem) 130 .stream() 131 .map(this::_retrieveOrgUnits) 132 .flatMap(Set::stream) 133 .collect(Collectors.toSet()); 134 } 135 136 private Set<OrgUnit> _getDirectOrgUnits(ProgramItem programItem) 137 { 138 if (programItem instanceof Course) 139 { 140 List<String> orgUnitIds = ((Course) programItem).getOrgUnits(); 141 return _resolveAll(orgUnitIds); 142 } 143 else if (programItem instanceof AbstractProgram) 144 { 145 List<String> orgUnitIds = ((AbstractProgram) programItem).getOrgUnits(); 146 return _resolveAll(orgUnitIds); 147 } 148 else if (programItem instanceof Container) 149 { 150 return Optional.of(programItem) 151 .map(Container.class::cast) 152 .map(Container::getOrgUnit) 153 .filter(Objects::nonNull) 154 .map(this::_resolve) 155 .filter(Objects::nonNull) 156 .map(Collections::singleton) 157 .orElseGet(Collections::emptySet); 158 } 159 return Collections.EMPTY_SET; 160 } 161 162 private Set<OrgUnit> _resolveAll(Collection<String> orgUnitIds) 163 { 164 return orgUnitIds 165 .stream() 166 .map(this::_resolve) 167 .filter(Objects::nonNull) 168 .collect(Collectors.toSet()); 169 } 170 171 private OrgUnit _resolve(String id) 172 { 173 try 174 { 175 return _resolver.resolveById(id); 176 } 177 catch (AmetysRepositoryException e) 178 { 179 // Silently fail 180 return null; 181 } 182 } 183 184 /** 185 * Retrieve the degrees of a given {@link Container} 186 * @param container The container 187 * @return the degrees of the given container 188 */ 189 public Set<OdfReferenceTableEntry> retrieveDegree(Container container) 190 { 191 return container.getRootPrograms() 192 .stream() 193 .map(p -> p.<ContentValue>getValue(AbstractProgram.DEGREE)) 194 .filter(Objects::nonNull) 195 .map(ContentValue::getContentIfExists) 196 .filter(Optional::isPresent) 197 .map(Optional::get) 198 .map(OdfReferenceTableEntry::new) 199 .collect(Collectors.toSet()); 200 } 201 202 /** 203 * Get the potential steps holder of the program item. A step is a container of year type and it can be set manually on intermediate courses. 204 * @param programItem The {@link ProgramItem} on which we have to retrieve the potential steps holder 205 * @return the potential steps holder of the program item 206 */ 207 public Set<Container> retrieveStepsHolder(ProgramItem programItem) 208 { 209 return _getStepHolderFromCourse(programItem) // If the element is a course and has a step holder 210 .or(() -> _getStepHolderFromContainer(programItem)) // If the element is a step (container of type year) 211 .map(Collections::singleton) 212 .orElseGet(() -> _getStepsHolderFromParentElements(programItem)); // In all other cases, search in the parent elements 213 } 214 215 private Optional<Container> _getStepHolderFromCourse(ProgramItem programItem) 216 { 217 return Optional.of(programItem) 218 .filter(Course.class::isInstance) 219 .map(Course.class::cast) 220 .map(c -> c.<ContentValue>getValue("etapePorteuse")) 221 .flatMap(ContentValue::getContentIfExists) 222 .map(Container.class::cast); 223 } 224 225 private Optional<Container> _getStepHolderFromContainer(ProgramItem programItem) 226 { 227 return Optional.of(programItem) 228 .filter(this::_isStep) 229 .map(Container.class::cast); 230 } 231 232 private boolean _isStep(ProgramItem programItem) 233 { 234 return _isContainerOfNature(programItem, "annee"); 235 } 236 237 private Set<Container> _getStepsHolderFromParentElements(ProgramItem programItem) 238 { 239 return _odfHelper.getParentProgramItems(programItem) 240 .stream() 241 .map(this::retrieveStepsHolder) 242 .flatMap(Set::stream) 243 .collect(Collectors.toSet()); 244 } 245 246 /** 247 * Get the potential period types of the program item. It can be retrieved on courses or containers. The algorithm doesn't search in the parent of semester containers. 248 * @param programItem The {@link ProgramItem} on which we have to retrieve the potential period types 249 * @return the potential period types of the program item 250 */ 251 public Set<OdfReferenceTableEntry> retrievePeriodTypes(ProgramItem programItem) 252 { 253 return _getPeriodType(programItem) // Try to get the period type on the current program item 254 .map(Collections::singleton) 255 .orElseGet(() -> _searchPeriodTypesInParents(programItem)); // Or search in the parent program items 256 } 257 258 private Set<OdfReferenceTableEntry> _searchPeriodTypesInParents(ProgramItem programItem) 259 { 260 if (_isSemester(programItem)) 261 { 262 return Collections.EMPTY_SET; 263 } 264 265 // Only if programItem not a semester 266 return _odfHelper.getParentProgramItems(programItem) // Get the direct parent program items 267 .stream() 268 .map(this::retrievePeriodTypes) // Retrieve the period types for all parents 269 .flatMap(Set::stream) 270 .collect(Collectors.toSet()); 271 } 272 273 private Optional<OdfReferenceTableEntry> _getPeriodType(ProgramItem programItem) 274 { 275 return Optional.of(programItem) 276 .map(Content.class::cast) // Cast to Content (ProgramItem is always a Content) 277 .filter(c -> ModelHelper.hasModelItem("period", c.getModel())) // Filter if the attribute "period" is not in the model 278 .map(c -> c.<ContentValue>getValue("period")) // Get the period 279 .flatMap(ContentValue::getContentIfExists) 280 .map(c -> c.<ContentValue>getValue("type")) // Get the period type 281 .flatMap(ContentValue::getContentIfExists) 282 .map(OdfReferenceTableEntry::new); 283 } 284 285 private boolean _isSemester(ProgramItem programItem) 286 { 287 return _isContainerOfNature(programItem, "semestre"); 288 } 289 290 private boolean _isContainerOfNature(ProgramItem programItem, String natureCode) 291 { 292 return Optional.of(programItem) 293 .filter(Container.class::isInstance) 294 .map(Container.class::cast) 295 .map(Container::getNature) 296 .map(_refTableHelper::getItemCode) 297 .map(natureCode::equals) 298 .orElse(false); 299 } 300}