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.service.ServiceException; 027import org.apache.avalon.framework.service.ServiceManager; 028import org.apache.commons.lang3.StringUtils; 029 030import org.ametys.cms.data.ContentValue; 031import org.ametys.cms.repository.Content; 032import org.ametys.core.cache.Cache; 033import org.ametys.odf.ProgramItem; 034import org.ametys.odf.course.Course; 035import org.ametys.odf.enumeration.OdfReferenceTableEntry; 036import org.ametys.odf.enumeration.OdfReferenceTableHelper; 037import org.ametys.odf.orgunit.OrgUnit; 038import org.ametys.odf.ose.export.OSEConstants; 039import org.ametys.odf.ose.export.impl.odf.LogUtils; 040import org.ametys.odf.program.AbstractProgram; 041import org.ametys.odf.program.Container; 042import org.ametys.plugins.odfpilotage.helper.PilotageHelper; 043import org.ametys.plugins.repository.AmetysObjectResolver; 044import org.ametys.plugins.repository.AmetysRepositoryException; 045import org.ametys.runtime.i18n.I18nizableText; 046import org.ametys.runtime.model.ModelHelper; 047 048/** 049 * A retriever used like a helper to retrieve some ODF elements from ODF items. 050 */ 051public class ElementRetriever extends PilotageHelper 052{ 053 /** Avalon Role */ 054 @SuppressWarnings("hiding") 055 public static final String ROLE = ElementRetriever.class.getName(); 056 057 private static final String __STEPS_BY_ITEM_CACHE_ID = ElementRetriever.class.getName() + "$stepsByItem"; 058 059 /** The resolver */ 060 protected AmetysObjectResolver _resolver; 061 062 @Override 063 public void service(ServiceManager manager) throws ServiceException 064 { 065 super.service(manager); 066 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 067 } 068 069 @Override 070 public void initialize() throws Exception 071 { 072 if (!_cacheManager.hasCache(__STEPS_BY_ITEM_CACHE_ID)) 073 { 074 _cacheManager.createRequestCache(__STEPS_BY_ITEM_CACHE_ID, 075 new I18nizableText("plugin.odf-ose", "PLUGINS_ODF_OSE_CACHE_STEPS_BY_ITEM_LABEL"), 076 new I18nizableText("plugin.odf-ose", "PLUGINS_ODF_OSE_CACHE_STEPS_BY_ITEM_DESCRIPTION"), 077 false 078 ); 079 } 080 } 081 082 @Override 083 public Set<Container> getSteps(ProgramItem programItem) 084 { 085 // The cache id is different than its superclass 086 return getYearId() 087 .map(yearId -> _getSteps(programItem, yearId, _cacheManager.get(__STEPS_BY_ITEM_CACHE_ID))) 088 .orElseGet(() -> Set.of()); 089 } 090 091 @Override 092 protected Set<Container> _getStepsToCache(ProgramItem programItem, String yearId, Cache<String, Set<Container>> cache) 093 { 094 return _mustNotExport(programItem) ? Collections.EMPTY_SET : super._getStepsToCache(programItem, yearId, cache); 095 } 096 097 private boolean _mustNotExport(ProgramItem programItem) 098 { 099 if (programItem instanceof Course || programItem instanceof Container) 100 { 101 return ((Content) programItem).getValue(OSEConstants.NO_OSE_EXPORT_ATTRIBUTE_NAME, false, false); 102 } 103 else 104 { 105 return false; 106 } 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 return Optional.of(parent) 122 .map(this::_getDirectOrgUnit) 123 .map(Collections::singleton) 124 .orElseGet(() -> _retrieveOrgUnitsFromParents(parent)); 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 OrgUnit _getDirectOrgUnit(ProgramItem programItem) 137 { 138 if (programItem instanceof Course) 139 { 140 List<String> orgUnitIds = ((Course) programItem).getOrgUnits(); 141 return _resolveAllAndGetFirst(orgUnitIds, (Course) programItem); 142 } 143 else if (programItem instanceof AbstractProgram) 144 { 145 List<String> orgUnitIds = ((AbstractProgram) programItem).getOrgUnits(); 146 return _resolveAllAndGetFirst(orgUnitIds, (AbstractProgram) programItem); 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 .orElse(null); 157 } 158 return null; 159 } 160 161 private <T extends ProgramItem & Content> OrgUnit _resolveAllAndGetFirst(Collection<String> orgUnitIds, T programElement) 162 { 163 List<OrgUnit> orgUnits = orgUnitIds.stream() 164 .map(this::_resolve) 165 .filter(Objects::nonNull) 166 .collect(Collectors.toList()); 167 168 if (orgUnits.size() > 1) 169 { 170 LogUtils.programElementWarningOrgUnits(getLogger(), programElement, orgUnits); 171 } 172 173 return orgUnits.isEmpty() ? null : orgUnits.get(0); 174 } 175 176 private OrgUnit _resolve(String id) 177 { 178 try 179 { 180 return _resolver.resolveById(id); 181 } 182 catch (AmetysRepositoryException e) 183 { 184 // Silently fail 185 return null; 186 } 187 } 188 189 /** 190 * Retrieve the degrees of a given {@link Container} 191 * @param container The container 192 * @return the degrees of the given container 193 */ 194 public Set<OdfReferenceTableEntry> retrieveDegree(Container container) 195 { 196 return container.getRootPrograms() 197 .stream() 198 .map(p -> p.<ContentValue>getValue(AbstractProgram.DEGREE)) 199 .filter(Objects::nonNull) 200 .map(ContentValue::getContentIfExists) 201 .filter(Optional::isPresent) 202 .map(Optional::get) 203 .map(OdfReferenceTableEntry::new) 204 .collect(Collectors.toSet()); 205 } 206 207 /** 208 * 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. 209 * @param programItem The {@link ProgramItem} on which we have to retrieve the potential steps holder 210 * @return the potential steps holder of the program item 211 */ 212 public Set<Container> retrieveStepsHolder(ProgramItem programItem) 213 { 214 Optional<String> yearId = getYearId(); 215 if (yearId.isPresent()) 216 { 217 return _getStepsHolder(programItem, yearId.get(), _cacheManager.get(_STEP_HOLDERS_BY_ITEM_CACHE_ID)); 218 } 219 220 return Set.of(); 221 } 222 223 /** 224 * 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. 225 * @param programItem The {@link ProgramItem} on which we have to retrieve the potential period types 226 * @return the potential period types of the program item 227 */ 228 public Set<OdfReferenceTableEntry> retrievePeriodTypes(ProgramItem programItem) 229 { 230 return getSemesterId() 231 .map(id -> _retrievePeriodTypes(programItem, id)) 232 .orElse(Collections.EMPTY_SET); 233 } 234 235 private Set<OdfReferenceTableEntry> _retrievePeriodTypes(ProgramItem programItem, String semesterId) 236 { 237 return _getPeriodType(programItem) // Try to get the period type on the current program item 238 .map(Collections::singleton) 239 .orElseGet(() -> _searchPeriodTypesInParents(programItem, semesterId)); // Or search in the parent program items 240 } 241 242 private Set<OdfReferenceTableEntry> _searchPeriodTypesInParents(ProgramItem programItem, String semesterId) 243 { 244 if (_isContainerOfNature(programItem, semesterId)) 245 { 246 return Collections.EMPTY_SET; 247 } 248 249 // Only if programItem not a semester 250 return _odfHelper.getParentProgramItems(programItem) // Get the direct parent program items 251 .stream() 252 .map(parent -> _retrievePeriodTypes(parent, semesterId)) // Retrieve the period types for all parents 253 .flatMap(Set::stream) 254 .collect(Collectors.toSet()); 255 } 256 257 private Optional<OdfReferenceTableEntry> _getPeriodType(ProgramItem programItem) 258 { 259 return Optional.of(programItem) 260 .map(Content.class::cast) // Cast to Content (ProgramItem is always a Content) 261 .filter(c -> ModelHelper.hasModelItem("period", c.getModel())) // Filter if the attribute "period" is not in the model 262 .map(c -> c.<ContentValue>getValue("period")) // Get the period 263 .flatMap(ContentValue::getContentIfExists) 264 .map(c -> c.<ContentValue>getValue("type")) // Get the period type 265 .flatMap(ContentValue::getContentIfExists) 266 .map(OdfReferenceTableEntry::new); 267 } 268 269 270 /** 271 * Get the semester container nature identifier. 272 * @return an {@link Optional} of the semester identifier 273 */ 274 public Optional<String> getSemesterId() 275 { 276 return Optional.of(_refTableHelper) 277 .map(rth -> rth.getItemFromCode(OdfReferenceTableHelper.CONTAINER_NATURE, "semestre")) 278 .map(OdfReferenceTableEntry::getId) 279 .filter(StringUtils::isNotBlank); 280 } 281}