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.odf.observation.skill; 017 018import java.util.ArrayList; 019import java.util.Arrays; 020import java.util.List; 021import java.util.Map; 022import java.util.Optional; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026 027import org.ametys.cms.ObservationConstants; 028import org.ametys.cms.content.indexing.solr.SolrIndexer; 029import org.ametys.cms.data.ContentValue; 030import org.ametys.cms.repository.Content; 031import org.ametys.cms.repository.ModifiableContent; 032import org.ametys.core.observation.Event; 033import org.ametys.odf.ODFHelper; 034import org.ametys.odf.course.Course; 035import org.ametys.odf.program.Program; 036import org.ametys.plugins.repository.RepositoryConstants; 037 038/** 039 * Observer to update program micro skill property if there was a change in assignment on courses. 040 * When the modified content is a Course, update the parent program micro skill property if necessary 041 */ 042public class IndexProgramMicroSkillsPropertyOnCourseModifiedStep2Observer extends AbstractSkillsStepObserver 043{ 044 /** The ODF helper */ 045 protected ODFHelper _odfHelper; 046 /** The Solr indexer */ 047 protected SolrIndexer _solrIndexer; 048 049 @Override 050 public void service(ServiceManager manager) throws ServiceException 051 { 052 super.service(manager); 053 _odfHelper = (ODFHelper) manager.lookup(ODFHelper.ROLE); 054 _solrIndexer = (SolrIndexer) manager.lookup(SolrIndexer.ROLE); 055 056 } 057 058 @Override 059 protected String getSupportedEventId() 060 { 061 return ObservationConstants.EVENT_CONTENT_MODIFIED; 062 } 063 064 @Override 065 protected boolean supportsContent(Content content) 066 { 067 // Only support Courses 068 return content instanceof Course; 069 } 070 071 @Override 072 public void observe(Event event, Map<String, Object> transientVars) throws Exception 073 { 074 Course course = (Course) event.getArguments().get(ObservationConstants.ARGS_CONTENT); 075 076 List<ContentValue> programsToUpdate = _getProgramIdsToUpdate(course); 077 for (ContentValue programToUpdate : programsToUpdate) 078 { 079 Optional<ModifiableContent> program = programToUpdate.getContentIfExists(); 080 if (program.isPresent()) 081 { 082 // Do a partial update of the skills property 083 _solrIndexer.updateProperty(program.get(), Program.COMPUTED_MICRO_SKILLS, RepositoryConstants.DEFAULT_WORKSPACE); 084 } 085 } 086 } 087 088 private List<ContentValue> _getProgramIdsToUpdate(Course course) 089 { 090 List<ContentValue> programsToUpdate = new ArrayList<>(); 091 092 Map<ContentValue, ContentValue[]> currentAcquiredSkills = course.getAcquiredSkills(); 093 // When the content is modified, we need to check if the acquired skills have been edited 094 Map<ContentValue, ContentValue[]> previousAcquiredSkills = _getRequestAttribute("previousAcquiredMicroSkills", course.getId()); 095 // Compare the previous acquired skills with the current ones to know when to update program property 096 if (currentAcquiredSkills != null && previousAcquiredSkills != null) 097 { 098 // Add every program that didn't previously have an entry 099 programsToUpdate.addAll(currentAcquiredSkills.keySet().stream().filter(program -> !previousAcquiredSkills.containsKey(program)).toList()); 100 // Add every program that previously had an entry but it got removed 101 programsToUpdate.addAll(previousAcquiredSkills.keySet().stream().filter(program -> !currentAcquiredSkills.containsKey(program)).toList()); 102 103 // For every program that already existed and were not removed, check if the micro skills changed 104 for (ContentValue programValue : previousAcquiredSkills.keySet()) 105 { 106 // Don't check the programs that are already set to be updated 107 if (!programsToUpdate.contains(programValue)) 108 { 109 ContentValue[] previousMicroSkillsForProgram = previousAcquiredSkills.get(programValue); 110 ContentValue[] currentMicroSkillsForProgram = currentAcquiredSkills.get(programValue); 111 112 // If there is more or less micro skills compared to previous version, update the program 113 if (previousMicroSkillsForProgram.length != currentMicroSkillsForProgram.length) 114 { 115 programsToUpdate.add(programValue); 116 } 117 else 118 { 119 List<ContentValue> previousSkillsList = Arrays.asList(previousMicroSkillsForProgram); 120 List<ContentValue> currentSkillsList = Arrays.asList(currentMicroSkillsForProgram); 121 122 // If there are new skills or removed skills for the entry, update the program 123 boolean hasAddedSkills = currentSkillsList.stream().anyMatch(microSkill -> !previousSkillsList.contains(microSkill)); 124 boolean hasRemovedSkills = previousSkillsList.stream().anyMatch(microSkill -> !currentSkillsList.contains(microSkill)); 125 if (hasAddedSkills || hasRemovedSkills) 126 { 127 programsToUpdate.add(programValue); 128 } 129 } 130 } 131 } 132 } 133 134 return programsToUpdate; 135 } 136}