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