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.HashSet; 019import java.util.List; 020import java.util.Map; 021import java.util.Set; 022 023import org.apache.avalon.framework.service.ServiceException; 024import org.apache.avalon.framework.service.ServiceManager; 025import org.apache.avalon.framework.service.Serviceable; 026 027import org.ametys.cms.content.indexing.solr.SolrIndexer; 028import org.ametys.cms.content.indexing.solr.observation.ObserverHelper; 029import org.ametys.core.observation.AsyncObserver; 030import org.ametys.core.observation.Event; 031import org.ametys.core.observation.Observer; 032import org.ametys.odf.ODFHelper; 033import org.ametys.odf.ProgramItem; 034import org.ametys.odf.observation.OdfObservationConstants; 035import org.ametys.odf.program.Program; 036import org.ametys.odf.skill.ODFSkillsHelper; 037import org.ametys.plugins.repository.AmetysObjectResolver; 038import org.ametys.plugins.repository.RepositoryConstants; 039import org.ametys.runtime.plugin.component.AbstractLogEnabled; 040 041/** 042 * {@link Observer} when a program's skill property is no more valid after moving or removing a program item in ODF tree 043 */ 044public class IndexProgramMicroSkillsPropertyOnHierarchyChangedObserver extends AbstractLogEnabled implements AsyncObserver, Serviceable 045{ 046 private AmetysObjectResolver _resolver; 047 private SolrIndexer _solrIndexer; 048 private ODFHelper _odfHelper; 049 050 public void service(ServiceManager smanager) throws ServiceException 051 { 052 _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE); 053 _solrIndexer = (SolrIndexer) smanager.lookup(SolrIndexer.ROLE); 054 _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE); 055 } 056 057 public boolean supports(Event event) 058 { 059 return ODFSkillsHelper.isSkillsEnabled() && event.getId().equals(OdfObservationConstants.EVENT_PROGRAM_ITEM_HIERARCHY_CHANGED); 060 } 061 062 public int getPriority() 063 { 064 return MIN_PRIORITY - 3000; 065 } 066 067 public void observe(Event event, Map<String, Object> transientVars) throws Exception 068 { 069 if (ObserverHelper.isNotSuspendedObservationForIndexation()) 070 { 071 Set<Program> programToUpdate = _getProgramsToUpdate(event); 072 for (Program parentProgram : programToUpdate) 073 { 074 // Do a partial update of the parent program skills property 075 _solrIndexer.updateProperty(parentProgram, Program.COMPUTED_MICRO_SKILLS, RepositoryConstants.DEFAULT_WORKSPACE); 076 } 077 } 078 } 079 080 private Set<Program> _getProgramsToUpdate(Event event) 081 { 082 Set<Program> programToUpdate = new HashSet<>(); 083 ProgramItem programItem = getProgramItem(event); 084 if (programItem != null) 085 { 086 Set<Program> oldParentPrograms = getOldParentPrograms(event); 087 Set<Program> newParentPrograms = getNewParentPrograms(event); 088 089 // Only update the programs that changed (new ones and removed ones) 090 programToUpdate.addAll(newParentPrograms.stream().filter(program -> !oldParentPrograms.contains(program)).toList()); 091 programToUpdate.addAll(oldParentPrograms.stream().filter(program -> !newParentPrograms.contains(program)).toList()); 092 } 093 094 return programToUpdate; 095 } 096 097 /** 098 * Get the {@link ProgramItem} concerned by this event 099 * @param event the event 100 * @return the program item content or null 101 */ 102 protected ProgramItem getProgramItem(Event event) 103 { 104 Map<String, Object> args = event.getArguments(); 105 106 if (args.containsKey(OdfObservationConstants.ARGS_PROGRAM_ITEM_ID)) 107 { 108 String programItemId = (String) args.get(OdfObservationConstants.ARGS_PROGRAM_ITEM_ID); 109 return _resolver.resolveById(programItemId); 110 } 111 112 return null; 113 } 114 115 /** 116 * Get the old {@link Program} parents of the program item 117 * @param event the event 118 * @return the program item content or null 119 */ 120 protected Set<Program> getOldParentPrograms(Event event) 121 { 122 Map<String, Object> args = event.getArguments(); 123 124 if (args.containsKey(OdfObservationConstants.ARGS_OLD_PARENT_PROGRAM_ITEM_ID)) 125 { 126 String programItemIds = (String) args.get(OdfObservationConstants.ARGS_OLD_PARENT_PROGRAM_ITEM_ID); 127 return getParentPrograms(List.of(programItemIds)); 128 } 129 130 return Set.of(); 131 } 132 /** 133 * Get the new {@link Program} parent of the program item 134 * @param event the event 135 * @return the program item content or null 136 */ 137 protected Set<Program> getNewParentPrograms(Event event) 138 { 139 Map<String, Object> args = event.getArguments(); 140 141 if (args.containsKey(OdfObservationConstants.ARGS_NEW_PARENT_PROGRAM_ITEM_IDS)) 142 { 143 @SuppressWarnings("unchecked") 144 List<String> programItemIds = (List<String>) args.get(OdfObservationConstants.ARGS_NEW_PARENT_PROGRAM_ITEM_IDS); 145 return getParentPrograms(programItemIds); 146 } 147 148 return Set.of(); 149 } 150 151 private Set<Program> getParentPrograms(List<String> programItemIds) 152 { 153 Set<Program> programParents = new HashSet<>(); 154 for (String programItemId : programItemIds) 155 { 156 ProgramItem programItem = _resolver.resolveById(programItemId); 157 if (programItem instanceof Program program) 158 { 159 programParents.add(program); 160 } 161 else 162 { 163 programParents.addAll(_odfHelper.getParentPrograms(programItem)); 164 } 165 } 166 167 return programParents; 168 } 169}