001/* 002 * Copyright 2022 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.hyperplanning; 017 018import java.time.Duration; 019import java.time.ZonedDateTime; 020import java.time.temporal.ChronoUnit; 021import java.util.Comparator; 022import java.util.GregorianCalendar; 023import java.util.List; 024import java.util.Set; 025import java.util.TreeSet; 026 027import javax.xml.datatype.DatatypeConfigurationException; 028import javax.xml.datatype.DatatypeFactory; 029import javax.xml.datatype.XMLGregorianCalendar; 030import javax.xml.ws.BindingProvider; 031import javax.xml.ws.WebServiceException; 032 033import org.apache.avalon.framework.activity.Initializable; 034import org.apache.avalon.framework.component.Component; 035import org.apache.avalon.framework.service.ServiceException; 036import org.apache.avalon.framework.service.ServiceManager; 037import org.apache.avalon.framework.service.Serviceable; 038 039import org.ametys.core.cache.AbstractCacheManager; 040import org.ametys.core.cache.Cache; 041import org.ametys.core.user.UserIdentity; 042import org.ametys.runtime.config.Config; 043import org.ametys.runtime.i18n.I18nizableText; 044import org.ametys.runtime.plugin.component.AbstractLogEnabled; 045 046import com.indexeducation.frahtm.hpsvcw.HpSvcWDonnees; 047import com.indexeducation.frahtm.hpsvcw.IHpSvcWCoursAnnules; 048import com.indexeducation.frahtm.hpsvcw.IHpSvcWEtudiants; 049import com.indexeducation.frahtm.hpsvcw.IHpSvcWMatieres; 050import com.indexeducation.frahtm.hpsvcw.THpSvcWCleCoursAnnule; 051import com.indexeducation.frahtm.hpsvcw.THpSvcWCleEtudiant; 052import com.indexeducation.frahtm.hpsvcw.THpSvcWTableauClesCoursAnnules; 053import com.indexeducation.frahtm.hpsvcw.THpSvcWTableauClesMatieres; 054import com.indexeducation.frahtm.hpsvcw.THpSvcWTableauTypesSeances; 055import com.indexeducation.frahtm.hpsvcw.THpSvcWTypeSeance; 056 057/** 058 * Component handling the communication with a remote hyperplanning server 059 */ 060public class HyperplanningManager extends AbstractLogEnabled implements Initializable, Component, Serviceable 061{ 062 /** The avalon role */ 063 public static final String ROLE = HyperplanningManager.class.getName(); 064 private static final String __CANCELLED_LESSONS_CACHE = HyperplanningManager.class.getName() + "$cancelledLessons"; 065 066 private String _connectionLogin; 067 private String _connectionPass; 068 private String _serverUrl; 069 070 private AbstractCacheManager _cacheManager; 071 072 private HpSvcWDonnees _dataService; 073 private IHpSvcWCoursAnnules _cancelledCoursesPort; 074 private IHpSvcWEtudiants _studentPort; 075 private IHpSvcWMatieres _subjectPort; 076 private HyperplannningStudentProvider _studentProvider; 077 078 public void service(ServiceManager manager) throws ServiceException 079 { 080 _cacheManager = (AbstractCacheManager) manager.lookup(AbstractCacheManager.ROLE); 081 _studentProvider = (HyperplannningStudentProvider) manager.lookup(HyperplannningStudentProvider.ROLE); 082 } 083 084 public void initialize() throws Exception 085 { 086 _connectionLogin = Config.getInstance().getValue("org.ametys.plugins.hyperplanning.login"); 087 _connectionPass = Config.getInstance().getValue("org.ametys.plugins.hyperplanning.password"); 088 _serverUrl = Config.getInstance().getValue("org.ametys.plugins.hyperplanning.url"); 089 090 _cacheManager.createMemoryCache(__CANCELLED_LESSONS_CACHE, 091 new I18nizableText("plugin.hyperplanning", "PLUGIN_HYPERPLANNING_CANCELLED_LESSONS_CACHE_LABEL"), 092 new I18nizableText("plugin.hyperplanning", "PLUGIN_HYPERPLANNING_CANCELLED_LESSONS_CACHE_DESCRIPTION"), 093 true, 094 Duration.ofMinutes(Config.getInstance().getValue("org.ametys.plugins.hyperplanning.cache-validity"))); 095 096 _initializeWebService(); 097 } 098 099 /** 100 * Get the list of cancelled lessons for a given user in the next 2 weeks 101 * @param userIdentity the user identity 102 * @return a list of {@link CancelledLesson} representing the cancelled lessons or null if the user is unknown 103 * @throws UnknownStudentException when user is not linked to hyperplanning 104 */ 105 public Set<CancelledLesson> getUpcomingCancelledLessons(UserIdentity userIdentity) throws UnknownStudentException 106 { 107 if (userIdentity == null) 108 { 109 throw new IllegalArgumentException("User is not connected"); 110 } 111 112 Set<CancelledLesson> cancelledLessons = getCache().get(userIdentity, this::_getCancelledLessons); 113 if (cancelledLessons == null) 114 { 115 throw new UnknownStudentException("User '" + userIdentity + "' has no link hyperplanning id"); 116 } 117 118 return cancelledLessons; 119 } 120 121 /** 122 * Get the list of cancelled lessons for a given user in the next 2 weeks 123 * @param userIdentity the user identity 124 * @return a list of {@link CancelledLesson} representing the cancelled lessons or null if the user is not linked to hyperplanning 125 */ 126 private Set<CancelledLesson> _getCancelledLessons(UserIdentity userIdentity) 127 { 128 String hypIdentity = _studentProvider.getStudentId(userIdentity); 129 THpSvcWCleEtudiant identifiant = null; 130 try 131 { 132 identifiant = _studentPort.accederEtudiantParIdentifiant(hypIdentity); 133 } 134 catch (WebServiceException e) 135 { 136 // null means that the user is not linked 137 getLogger().warn("User '" + userIdentity + "' has no link hyperplanning id"); 138 return null; 139 } 140 141 ZonedDateTime startDate = ZonedDateTime.now().truncatedTo(ChronoUnit.DAYS); 142 ZonedDateTime endDate = startDate.plusWeeks(2).plusDays(1); // add one day to 'compensate' the rounding 143 XMLGregorianCalendar startXmlCalendar = null; 144 XMLGregorianCalendar endXmlCalendar = null; 145 try 146 { 147 startXmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar.from(startDate)); 148 endXmlCalendar = DatatypeFactory.newInstance().newXMLGregorianCalendar(GregorianCalendar.from(endDate)); 149 } 150 catch (DatatypeConfigurationException e) 151 { 152 throw new RuntimeException("Can't load xmlGregorian datatype implementation", e); 153 } 154 155 THpSvcWTableauClesCoursAnnules cancelledCourses = _cancelledCoursesPort.coursAnnulesParEtudiantEntre2Dates(identifiant, startXmlCalendar, endXmlCalendar); 156 157 // retrieve the info related to the courses 158 List<THpSvcWCleCoursAnnule> cancelledCourseKeys = cancelledCourses.getTHpSvcWCleCoursAnnule(); 159 List<String> cancelledCoursesRationale = _cancelledCoursesPort.motifAnnulationTableauDeCoursAnnules(cancelledCourses).getString(); 160 List<String> cancelledCoursesComment = _cancelledCoursesPort.commentairesTableauDeCoursAnnules(cancelledCourses).getString(); 161 162 // retrieve info related to the subject of the courses 163 THpSvcWTableauClesMatieres cancelledCoursesSubject = _cancelledCoursesPort.matieresTableauDeCoursAnnules(cancelledCourses); 164 List<String> cancelledCoursesSubjectCode = _subjectPort.codesTableauDeMatieres(cancelledCoursesSubject).getString(); 165 List<String> cancelledCourseSubjectLabel = _subjectPort.libellesTableauDeMatieres(cancelledCoursesSubject).getString(); 166 List<String> cancelledCoursesSubjectLongLabel = _subjectPort.libellesLongsTableauDeMatieres(cancelledCoursesSubject).getString(); 167 168 Comparator<CancelledLesson> comparator = (c1, c2) -> c1.getDate().compareTo(c2.getDate()); 169 Set<CancelledLesson> result = new TreeSet<>(comparator); 170 171 // For each cancelled courses, iterate on the lesson to retrieve the information and store the lesson in a single set ordered by lesson date 172 int size = cancelledCourseKeys.size(); 173 for (int i = 0; i < size; i++) 174 { 175 THpSvcWCleCoursAnnule cancelledCourseKey = cancelledCourseKeys.get(i); 176 // Retrieve all the lessons cancelled by this cancelledCourseKey. Some lessons can be outside of the time windows and need to be filtered 177 THpSvcWTableauTypesSeances cancelledLessons = _cancelledCoursesPort.detailDesSeancesPlaceesDuCoursAnnule(cancelledCourseKey); 178 179 for (THpSvcWTypeSeance lessons : cancelledLessons.getTHpSvcWTypeSeance()) 180 { 181 ZonedDateTime date = lessons.getJourEtHeureDebut().toGregorianCalendar().toZonedDateTime(); 182 if (date.isAfter(startDate) && date.isBefore(endDate)) 183 { 184 result.add(new CancelledLesson(cancelledCourseKey, 185 cancelledCoursesSubjectCode.get(i), 186 cancelledCourseSubjectLabel.get(i), 187 cancelledCoursesSubjectLongLabel.get(i), 188 date, 189 cancelledCoursesRationale.get(i), 190 cancelledCoursesComment.get(i) 191 )); 192 } 193 } 194 } 195 196 return result; 197 } 198 199 // Long operation 200 private void _initializeWebService() 201 { 202 _dataService = new HpSvcWDonnees(); 203 204 _cancelledCoursesPort = _dataService.getPortCoursAnnules(); 205 ((BindingProvider) _cancelledCoursesPort).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, _connectionLogin); 206 ((BindingProvider) _cancelledCoursesPort).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, _connectionPass); 207 ((BindingProvider) _cancelledCoursesPort).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, _serverUrl); 208 209 _studentPort = _dataService.getPortEtudiants(); 210 ((BindingProvider) _studentPort).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, _connectionLogin); 211 ((BindingProvider) _studentPort).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, _connectionPass); 212 ((BindingProvider) _studentPort).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, _serverUrl); 213 214 _subjectPort = _dataService.getPortMatieres(); 215 ((BindingProvider) _subjectPort).getRequestContext().put(BindingProvider.USERNAME_PROPERTY, _connectionLogin); 216 ((BindingProvider) _subjectPort).getRequestContext().put(BindingProvider.PASSWORD_PROPERTY, _connectionPass); 217 ((BindingProvider) _subjectPort).getRequestContext().put(BindingProvider.ENDPOINT_ADDRESS_PROPERTY, _serverUrl); 218 } 219 220 private Cache<UserIdentity, Set<CancelledLesson>> getCache() 221 { 222 return _cacheManager.get(__CANCELLED_LESSONS_CACHE); 223 } 224}