001/* 002 * Copyright 2026 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.odfsync.apogee.scc.impl; 017 018import java.math.BigDecimal; 019import java.util.ArrayList; 020import java.util.Comparator; 021import java.util.HashMap; 022import java.util.List; 023import java.util.Map; 024import java.util.Optional; 025import java.util.Set; 026import java.util.stream.Collectors; 027import java.util.stream.Stream; 028 029import org.apache.avalon.framework.service.ServiceException; 030import org.apache.avalon.framework.service.ServiceManager; 031import org.apache.commons.lang3.StringUtils; 032import org.slf4j.Logger; 033 034import org.ametys.cms.repository.Content; 035import org.ametys.cms.repository.ModifiableContent; 036import org.ametys.cms.repository.WorkflowAwareContent; 037import org.ametys.core.schedule.progression.ContainerProgressionTracker; 038import org.ametys.odf.ProgramItem; 039import org.ametys.odf.cdmfr.CDMFRHandler; 040import org.ametys.plugins.contentio.synchronize.impl.AbstractDefaultSynchronizableContentsCollection; 041import org.ametys.plugins.odfsync.apogee.ApogeeStatistiquesDAO; 042import org.ametys.plugins.odfsync.apogee.scc.ApogeeSynchronizableContentsCollectionHelper; 043import org.ametys.plugins.repository.query.expression.Expression; 044import org.ametys.plugins.repository.query.expression.Expression.Operator; 045import org.ametys.plugins.repository.query.expression.StringExpression; 046import org.ametys.runtime.model.ModelItem; 047import org.ametys.runtime.model.View; 048 049import com.opensymphony.workflow.WorkflowException; 050 051/** 052 * SCC used to fill statistics on "Bac général" from Apogée data, it is a complementary SCC. 053 */ 054public class StatistiquesBacGeneralSynchronizableContentsCollection extends AbstractDefaultSynchronizableContentsCollection 055{ 056 /** Name of parameter holding the data source id */ 057 public static final String PARAM_DATASOURCE_ID = "datasourceId"; 058 059 private static final String __ATTRIBUTE_ADMISSIONS_VET = "statistiquesBacGeneral" + ModelItem.ITEM_PATH_SEPARATOR + "admissionsVET"; 060 061 /** The DAO for remote DB Apogee */ 062 protected ApogeeStatistiquesDAO _apogeeStatistiquesDAO; 063 064 /** The Apogée SCC helper */ 065 protected ApogeeSynchronizableContentsCollectionHelper _apogeeSCCHelper; 066 067 /** The CDM-fr handler */ 068 protected CDMFRHandler _cdmfrHandler; 069 070 @Override 071 public void service(ServiceManager manager) throws ServiceException 072 { 073 super.service(manager); 074 _apogeeStatistiquesDAO = (ApogeeStatistiquesDAO) manager.lookup(ApogeeStatistiquesDAO.ROLE); 075 _apogeeSCCHelper = (ApogeeSynchronizableContentsCollectionHelper) manager.lookup(ApogeeSynchronizableContentsCollectionHelper.ROLE); 076 _cdmfrHandler = (CDMFRHandler) manager.lookup(CDMFRHandler.ROLE); 077 } 078 079 /** 080 * Get the id of data source 081 * @return The id of data source 082 */ 083 protected String getDataSourceId() 084 { 085 return (String) getParameterValues().get(PARAM_DATASOURCE_ID); 086 } 087 088 089 public String getIdField() 090 { 091 return "code"; 092 } 093 094 @Override 095 public boolean checkCollection() 096 { 097 // We only synchronize, so we don't care about this parameter 098 // If set to true on first launch, no field will be synchronized, so always set to false 099 return false; 100 } 101 102 @Override 103 public boolean removalSync() 104 { 105 // We never delete the contents because it is only synchronization 106 return false; 107 } 108 109 @Override 110 public List<String> getLanguages() 111 { 112 return List.of(_apogeeSCCHelper.getSynchronizationLang()); 113 } 114 115 @Override 116 public void updateSyncInformations(ModifiableContent content, String syncCode, Logger logger) throws Exception 117 { 118 throw new UnsupportedOperationException("updateSyncInformations() method is not supported for this synchronizable contents collections."); 119 } 120 121 @Override 122 protected ModifiableContent _importContent(String idValue, Map<String, Object> additionalParameters, String lang, Map<String, List<Object>> remoteValues, Logger logger) throws Exception 123 { 124 throw new UnsupportedOperationException("The method _importContent is not handled by PreviousYearsSCC. The previous years fields can only be synchronized."); 125 } 126 127 @Override 128 public List<ModifiableContent> populate(Logger logger, ContainerProgressionTracker progressionTracker) 129 { 130 Set<String> contentIds = null; 131 132 try 133 { 134 _startHandleCDMFR(); 135 List<ModifiableContent> contents = super.populate(logger, progressionTracker); 136 contentIds = contents.stream().map(ModifiableContent::getId).collect(Collectors.toSet()); 137 return contents; 138 } 139 finally 140 { 141 _endHandleCDMFR(contentIds); 142 } 143 } 144 145 /** 146 * Start handle CDM-fr treatments 147 */ 148 protected void _startHandleCDMFR() 149 { 150 _cdmfrHandler.suspendCDMFRObserver(); 151 } 152 153 /** 154 * End handle CDM-fr treatments 155 * @param contentIds the updated contents ids 156 */ 157 protected void _endHandleCDMFR(Set<String> contentIds) 158 { 159 _cdmfrHandler.unsuspendCDMFRObserver(contentIds); 160 } 161 162 @Override 163 protected Map<String, Map<String, Object>> internalSearch(Map<String, Object> initialSearchParameters, int offset, int limit, List<Object> sort, Logger logger) 164 { 165 return _getContentsToSynchronize((String) initialSearchParameters.get(getIdField())) 166 .collect(Collectors.toMap( 167 content -> content.<String>getValue(getIdField()), 168 content -> Map.of( 169 "code", content.<String>getValue(getIdField()), 170 "statistiquesBacGeneral" + ModelItem.ITEM_PATH_SEPARATOR + "distribution", _getContentValues(content) 171 ) 172 ) 173 ); 174 } 175 176 private Stream<Content> _getContentsToSynchronize(String idValue) 177 { 178 String query = _getContentPathQuery(_apogeeSCCHelper.getSynchronizationLang(), idValue, getContentType(), false); 179 return _resolver.<Content>query(query).stream(); 180 } 181 182 private List<Map<String, List<Object>>> _getContentValues(Content content) 183 { 184 String steps = Stream.of(content.<String[]>getValue(__ATTRIBUTE_ADMISSIONS_VET)) 185 .map(vet -> "'" + vet + "'") 186 .collect(Collectors.joining(", ")); 187 Map<String, Object> params = Map.of("steps", steps); 188 return _apogeeStatistiquesDAO.getStatistiquesBacGeneral(getDataSourceId(), getParameterValues(), params) 189 .stream() 190 .map(result -> 191 Map.<String, List<Object>>of( 192 "speciality1", List.of(result.get("SPE1")), 193 "speciality2", List.of(result.get("SPE2")), 194 "admission", List.of(((BigDecimal) result.get("ADMISSIONS")).doubleValue()) 195 ) 196 ) 197 .toList(); 198 } 199 200 @Override 201 protected List<Expression> _getExpressionsList(String lang, String idValue, String contentType, boolean forceStrictCheck) 202 { 203 List<Expression> expList = super._getExpressionsList(lang, idValue, contentType, forceStrictCheck); 204 205 String catalog = _apogeeSCCHelper.getSynchronizationCatalog(); 206 if (catalog != null) 207 { 208 expList.add(new StringExpression(ProgramItem.CATALOG, Operator.EQ, catalog)); 209 } 210 211 expList.add(new StringExpression(__ATTRIBUTE_ADMISSIONS_VET, Operator.NE, StringUtils.EMPTY)); 212 213 return expList; 214 } 215 216 @Override 217 protected boolean _editContent(WorkflowAwareContent content, Optional<View> view, Map<String, Object> values, Map<String, Object> additionalParameters, boolean create, Set<String> notSynchronizedContentIds, Logger logger) throws WorkflowException 218 { 219 return super._editContent(content, view, _groupSpecialities(values), additionalParameters, create, notSynchronizedContentIds, logger); 220 } 221 222 @SuppressWarnings("unchecked") 223 private Map<String, Object> _groupSpecialities(Map<String, Object> values) 224 { 225 List<Map<String, Object>> distribution = (List<Map<String, Object>>) ((Map<String, Object>) values.get("statistiquesBacGeneral")).get("distribution"); 226 227 // Group specialities when spe1 = A and spe2 = B, and spe1 = B and spe2 = A in one line 228 // Also because of mapping, we have some double lines like spe1 = A and spe2 = B 229 List<Map<String, Object>> groupedDistribution = new ArrayList<>(); 230 231 for (Map<String, Object> entry : distribution) 232 { 233 Object speciality1 = entry.get("speciality1"); 234 Object speciality2 = entry.get("speciality2"); 235 236 if (speciality1 != null && speciality2 != null) 237 { 238 groupedDistribution.stream() 239 // Get the entry of the distribution already computed 240 .filter(currentEntry -> 241 speciality1.equals(currentEntry.get("speciality1")) && speciality2.equals(currentEntry.get("speciality2")) 242 || speciality1.equals(currentEntry.get("speciality2")) && speciality2.equals(currentEntry.get("speciality1")) 243 ) 244 .findAny() 245 .ifPresentOrElse( 246 // If there is a corresponding entry : merge the admissions 247 groupedEntry -> groupedEntry.put("admission", (Double) groupedEntry.get("admission") + (Double) entry.get("admission")), 248 // If there is not entry, add the entry (by copying) 249 () -> groupedDistribution.add(new HashMap<>(entry)) 250 ); 251 } 252 } 253 254 groupedDistribution.sort(Comparator.<Map<String, Object>>comparingDouble(map -> (Double) map.get("admission")).reversed()); 255 256 Map<String, Object> groupedValues = new HashMap<>(values); 257 ((Map<String, Object>) groupedValues.get("statistiquesBacGeneral")).put("distribution", groupedDistribution); 258 return groupedValues; 259 } 260 261 @Override 262 protected void ensureTitleIsPresent(Content content, Map<String, List<Object>> remoteValues, Logger logger) 263 { 264 // Do nothing because title will be ignored in all cases 265 } 266}