001/*
002 *  Copyright 2024 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;
017
018import java.util.List;
019import java.util.Map;
020
021import org.apache.avalon.framework.service.ServiceException;
022import org.apache.avalon.framework.service.ServiceManager;
023import org.apache.avalon.framework.service.Serviceable;
024
025import org.ametys.core.observation.AsyncObserver;
026import org.ametys.core.observation.Event;
027import org.ametys.core.observation.Observer;
028import org.ametys.odf.EducationalPathHelper;
029import org.ametys.odf.ODFHelper;
030import org.ametys.odf.ProgramItem;
031import org.ametys.plugins.repository.AmetysObjectResolver;
032import org.ametys.runtime.plugin.component.AbstractLogEnabled;
033
034import com.opensymphony.workflow.WorkflowException;
035
036
037/**
038 * {@link Observer} when a educational path is no more valid after moving or removing a program item in ODF tree
039 */
040public class EducationalPathRemovedObserver extends AbstractLogEnabled implements AsyncObserver, Serviceable
041{
042    /** The educational path helper */
043    protected EducationalPathHelper _educationalPathHelper;
044    /** The oDF helper */
045    protected ODFHelper _odfHelper;
046    /** The Ametys object resolver */
047    protected AmetysObjectResolver _resolver;
048
049    public void service(ServiceManager smanager) throws ServiceException
050    {
051        _educationalPathHelper = (EducationalPathHelper) smanager.lookup(EducationalPathHelper.ROLE);
052        _odfHelper = (ODFHelper) smanager.lookup(ODFHelper.ROLE);
053        _resolver = (AmetysObjectResolver) smanager.lookup(AmetysObjectResolver.ROLE);
054    }
055
056    public boolean supports(Event event)
057    {
058        return event.getId().equals(OdfObservationConstants.EVENT_PROGRAM_ITEM_HIERARCHY_CHANGED);
059    }
060
061    public int getPriority(Event event)
062    {
063        return MIN_PRIORITY;
064    }
065
066    public void observe(Event event, Map<String, Object> transientVars) throws Exception
067    {
068        ProgramItem programItem = getProgramItem(event);
069        String oldParentProgramItemId = getOldParentProgramItemId(event);
070        
071        if (oldParentProgramItemId != null) // program item has been moved or removed
072        {
073            List<String> newParentProgramItems = getNewParentProgramItemIds(event);
074            if (newParentProgramItems.isEmpty()) 
075            {
076                // The program item has been removed, so him or its children may become unshared
077                _updateUnsharedProgramItem(programItem);
078            }
079            
080            // Then, when a program item has been detached from its parent (but its is still shared), all educational paths refering it AND its old parent are invalids and should be removed
081            _educationalPathHelper.removeEducationalPathReferences(List.of(programItem.getId(), oldParentProgramItemId));
082        }
083        
084    }
085    
086    // FIXME For now a program item is considered as shared if it has several parents whatever its ancestors are orphans
087    // So this method will do nothing except for the removed program item itself
088    // Here it would actually not be necessary to browse child program items as their shared status will not change
089    // When #isShared will ignore orphaned ancestors, this method will be the right way to do it
090    private void _updateUnsharedProgramItem(ProgramItem programItem) throws WorkflowException
091    {
092        if (!_odfHelper.isShared(programItem))
093        {
094            // The program item is not shared anymore, all repeaters with educational path should be removed
095            _educationalPathHelper.removeAllRepeatersWithEducationalPath(programItem);
096        }
097        
098        List<ProgramItem> childProgramItems = _odfHelper.getChildProgramItems(programItem);
099        for (ProgramItem childProgramItem : childProgramItems)
100        {
101            _updateUnsharedProgramItem(childProgramItem);
102        }
103    }
104    
105    /**
106     * Get the {@link ProgramItem} concerned by this event
107     * @param event the event
108     * @return the program item content or null
109     */
110    protected ProgramItem getProgramItem(Event event)
111    {
112        Map<String, Object> args = event.getArguments();
113
114        if (args.containsKey(OdfObservationConstants.ARGS_PROGRAM_ITEM_ID))
115        {
116            String programItemId = (String) args.get(OdfObservationConstants.ARGS_PROGRAM_ITEM_ID);
117            return _resolver.resolveById(programItemId);
118        }
119        return null;
120    }
121    
122    /**
123     * Get the {@link ProgramItem} concerned by this event
124     * @param event the event
125     * @return the program item content or null
126     */
127    protected String getOldParentProgramItemId(Event event)
128    {
129        Map<String, Object> args = event.getArguments();
130
131        if (args.containsKey(OdfObservationConstants.ARGS_OLD_PARENT_PROGRAM_ITEM_ID))
132        {
133            return (String) args.get(OdfObservationConstants.ARGS_OLD_PARENT_PROGRAM_ITEM_ID);
134        }
135
136        return null;
137    }
138    
139    /**
140     * Get the {@link ProgramItem} concerned by this event
141     * @param event the event
142     * @return the program item content or null
143     */
144    @SuppressWarnings("unchecked")
145    protected List<String> getNewParentProgramItemIds(Event event)
146    {
147        Map<String, Object> args = event.getArguments();
148
149        if (args.containsKey(OdfObservationConstants.ARGS_NEW_PARENT_PROGRAM_ITEM_IDS))
150        {
151            return (List<String>) args.get(OdfObservationConstants.ARGS_NEW_PARENT_PROGRAM_ITEM_IDS);
152        }
153
154        return List.of();
155    }
156}