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}