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}