001/* 002 * Copyright 2018 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.odfpilotage.helper; 017 018import java.util.Collections; 019import java.util.HashSet; 020import java.util.Objects; 021import java.util.Optional; 022import java.util.Set; 023import java.util.stream.Collectors; 024 025import org.apache.avalon.framework.activity.Initializable; 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; 030import org.apache.commons.lang3.StringUtils; 031import org.apache.commons.lang3.tuple.Pair; 032 033import org.ametys.cms.data.ContentValue; 034import org.ametys.core.cache.AbstractCacheManager; 035import org.ametys.core.cache.Cache; 036import org.ametys.odf.ODFHelper; 037import org.ametys.odf.ProgramItem; 038import org.ametys.odf.course.Course; 039import org.ametys.odf.enumeration.OdfReferenceTableEntry; 040import org.ametys.odf.enumeration.OdfReferenceTableHelper; 041import org.ametys.odf.program.Container; 042import org.ametys.runtime.i18n.I18nizableText; 043import org.ametys.runtime.plugin.component.AbstractLogEnabled; 044 045/** 046 * The pilotage helper. 047 */ 048public class PilotageHelper extends AbstractLogEnabled implements Component, Serviceable, Initializable 049{ 050 /** The avalon role */ 051 public static final String ROLE = PilotageHelper.class.getName(); 052 053 /** MCC Course nature */ 054 public static final String MCC_MODALITE_SESSION1 = "odf-enumeration.MccModaliteSession1"; 055 056 /** MCC Course nature */ 057 public static final String MCC_MODALITE_SESSION2 = "odf-enumeration.MccModaliteSession2"; 058 059 /** MCC Course nature */ 060 public static final String MCC_SESSION_NATURE = "odf-enumeration.MccSessionNature"; 061 062 /** Norme */ 063 public static final String NORME = "odf-enumeration.Norme"; 064 065 /** The cache id for step holders by program item */ 066 protected static final String _STEP_HOLDERS_BY_ITEM_CACHE_ID = PilotageHelper.class.getName() + "$stepHoldersByItem"; 067 068 private static final String __STEPS_BY_ITEM_CACHE_ID = PilotageHelper.class.getName() + "$stepsByItem"; 069 070 /** The helper for ODF contents */ 071 protected ODFHelper _odfHelper; 072 /** The helper for reference tables from ODF */ 073 protected OdfReferenceTableHelper _refTableHelper; 074 /** The cache manager */ 075 protected AbstractCacheManager _cacheManager; 076 077 /** 078 * Enumeration for the step holder status 079 */ 080 public enum StepHolderStatus 081 { 082 /** No step holder */ 083 NONE, 084 /** Single step holder - Nominal behavior */ 085 SINGLE, 086 /** Multiple steps holder */ 087 MULTIPLE, 088 /** Unknown year id */ 089 NO_YEAR, 090 /** Year is not an ascendant of the given element */ 091 WRONG_YEAR; 092 } 093 094 @Override 095 public void service(ServiceManager manager) throws ServiceException 096 { 097 _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE); 098 _refTableHelper = (OdfReferenceTableHelper) manager.lookup(OdfReferenceTableHelper.ROLE); 099 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 100 } 101 102 @Override 103 public void initialize() throws Exception 104 { 105 if (!_cacheManager.hasCache(__STEPS_BY_ITEM_CACHE_ID)) 106 { 107 _cacheManager.createRequestCache(__STEPS_BY_ITEM_CACHE_ID, 108 new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_CACHE_STEPS_BY_ITEM_LABEL"), 109 new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_CACHE_STEPS_BY_ITEM_DESCRIPTION"), 110 false 111 ); 112 } 113 if (!_cacheManager.hasCache(_STEP_HOLDERS_BY_ITEM_CACHE_ID)) 114 { 115 _cacheManager.createRequestCache(_STEP_HOLDERS_BY_ITEM_CACHE_ID, 116 new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_CACHE_STEP_HOLDERS_BY_ITEM_LABEL"), 117 new I18nizableText("plugin.odf-pilotage", "PLUGINS_ODF_PILOTAGE_CACHE_STEP_HOLDERS_BY_ITEM_DESCRIPTION"), 118 false 119 ); 120 } 121 } 122 123 /** 124 * Get the step holder of a program item 125 * @param programItem The program item 126 * @return {@link Pair} with the step holder status as a key, and the step holder (if found) as a value 127 */ 128 public Pair<StepHolderStatus, Container> getStepHolder(ProgramItem programItem) 129 { 130 Optional<String> yearId = getYearId(); 131 132 if (yearId.isEmpty()) 133 { 134 return Pair.of(StepHolderStatus.NO_YEAR, null); 135 } 136 137 Set<Container> stepsHolder = _getStepsHolder(programItem, yearId.get(), _cacheManager.get(_STEP_HOLDERS_BY_ITEM_CACHE_ID)); 138 139 switch (stepsHolder.size()) 140 { 141 case 0: 142 return Pair.of(StepHolderStatus.NONE, null); 143 case 1: 144 Container stepHolder = stepsHolder.stream().findFirst().get(); 145 StepHolderStatus status = getSteps(programItem).contains(stepHolder) 146 ? StepHolderStatus.SINGLE 147 : StepHolderStatus.WRONG_YEAR; 148 return Pair.of(status, stepHolder); 149 default: 150 return Pair.of(StepHolderStatus.MULTIPLE, null); 151 } 152 } 153 154 /** 155 * Get the potential steps holder (step or field "etapePorteuse" in courses) of the {@link ProgramItem}. 156 * @param programItem The program item 157 * @param yearId The identifier of the year container nature 158 * @param cache The cache 159 * @return The list of potential steps holder linked to the programItem. It there are several, there is no defined step holder. 160 */ 161 protected Set<Container> _getStepsHolder(ProgramItem programItem, String yearId, Cache<String, Set<Container>> cache) 162 { 163 String programItemId = programItem.getId(); 164 165 Set<Container> containers = cache.get(programItemId); 166 if (containers == null) 167 { 168 containers = _getStepHolderFromCourse(programItem) // If the element is a course and has a step holder 169 .or(() -> _getStepHolderFromContainer(programItem, yearId)) // If the element is a step (container of type year) 170 .map(Collections::singleton) 171 .orElseGet(() -> _getStepsHolderFromParentElements(programItem, yearId, cache)); // In all other cases, search in the parent elements 172 cache.put(programItemId, containers); 173 } 174 175 return containers; 176 } 177 178 private Optional<Container> _getStepHolderFromCourse(ProgramItem programItem) 179 { 180 return Optional.of(programItem) 181 .filter(Course.class::isInstance) 182 .map(Course.class::cast) 183 .map(c -> c.<ContentValue>getValue("etapePorteuse")) 184 .flatMap(ContentValue::getContentIfExists) 185 .map(Container.class::cast); 186 } 187 188 private Optional<Container> _getStepHolderFromContainer(ProgramItem programItem, String yearId) 189 { 190 return Optional.of(programItem) 191 .filter(pi -> _isContainerOfNature(pi, yearId)) 192 .map(Container.class::cast); 193 } 194 195 /** 196 * Filter the program item to keep only container with the given nature. 197 * @param programItem The program item 198 * @param natureId The container nature identifier 199 * @return <code>true</code> if it is a container of the given nature, <code>false</code> otherwise 200 */ 201 protected boolean _isContainerOfNature(ProgramItem programItem, String natureId) 202 { 203 return Optional.of(programItem) 204 .filter(Container.class::isInstance) 205 .map(Container.class::cast) 206 .map(Container::getNature) 207 .map(natureId::equals) 208 .orElse(false); 209 } 210 211 private Set<Container> _getStepsHolderFromParentElements(ProgramItem programItem, String yearId, Cache<String, Set<Container>> cache) 212 { 213 return _odfHelper.getParentProgramItems(programItem) 214 .stream() 215 .map(pi -> _getStepsHolder(pi, yearId, cache)) 216 .flatMap(Set::stream) 217 .collect(Collectors.toSet()); 218 } 219 220 /** 221 * Get the steps of a program item 222 * @param programItem The program item 223 * @return The steps of the given program item 224 */ 225 public Set<Container> getSteps(ProgramItem programItem) 226 { 227 return getYearId() 228 .map(yearId -> _getSteps(programItem, yearId, _cacheManager.get(__STEPS_BY_ITEM_CACHE_ID))) 229 .orElseGet(() -> Set.of()); 230 } 231 232 /** 233 * Internal method to get the steps of a program item, can be directly called by subclasses. 234 * @param programItem The program item 235 * @param yearId The identifier of the year container nature 236 * @param cache The cache 237 * @return The steps of the given program item 238 */ 239 protected Set<Container> _getSteps(ProgramItem programItem, String yearId, Cache<String, Set<Container>> cache) 240 { 241 String programItemId = programItem.getId(); 242 243 Set<Container> containers = cache.get(programItemId); 244 if (containers == null) 245 { 246 containers = _getStepsToCache(programItem, yearId, cache); 247 cache.put(programItemId, containers); 248 } 249 250 return containers; 251 } 252 253 /** 254 * Get the steps to add to the cache. 255 * @param programItem The program item to search on 256 * @param yearId The identifier of the year container nature 257 * @param cache The cache 258 * @return A {@link Set} of {@link Container} corresponding to the steps of the given program item. 259 */ 260 protected Set<Container> _getStepsToCache(ProgramItem programItem, String yearId, Cache<String, Set<Container>> cache) 261 { 262 Set<Container> containers = new HashSet<>(); 263 264 if (programItem instanceof Container && Objects.equals(((Container) programItem).getNature(), yearId)) 265 { 266 containers.add((Container) programItem); 267 } 268 else 269 { 270 for (ProgramItem parent : _odfHelper.getParentProgramItems(programItem)) 271 { 272 containers.addAll(_getSteps(parent, yearId, cache)); 273 } 274 } 275 276 return containers; 277 } 278 279 /** 280 * Get the year container nature identifier. 281 * @return an {@link Optional} of the year identifier 282 */ 283 public Optional<String> getYearId() 284 { 285 return Optional.of(_refTableHelper) 286 .map(rth -> rth.getItemFromCode(OdfReferenceTableHelper.CONTAINER_NATURE, "annee")) 287 .map(OdfReferenceTableEntry::getId) 288 .filter(StringUtils::isNotBlank); 289 } 290 291 /** 292 * Transform eqTD (can be a quotient) to a double value. 293 * @param eqTD The eqTD to parse 294 * @return The double value equivalent to the given eqTD 295 */ 296 public static Double transformEqTD2Double(String eqTD) 297 { 298 Double eqTDDouble = null; 299 300 if (StringUtils.isNotEmpty(eqTD)) 301 { 302 // fraction 303 if (eqTD.indexOf('/') != -1) 304 { 305 Double numerator = Double.valueOf(eqTD.substring(0, eqTD.indexOf('/'))); 306 Double denominator = Double.valueOf(eqTD.substring(eqTD.indexOf('/') + 1)); 307 eqTDDouble = numerator / denominator; 308 } 309 // decimal ou entier 310 else 311 { 312 eqTDDouble = Double.valueOf(eqTD); 313 } 314 } 315 316 return eqTDDouble; 317 } 318}