001/* 002 * Copyright 2025 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.catalog; 017 018import java.util.ArrayList; 019import java.util.HashMap; 020import java.util.List; 021import java.util.Map; 022import java.util.stream.Stream; 023 024import org.apache.avalon.framework.service.ServiceException; 025import org.apache.avalon.framework.service.ServiceManager; 026import org.apache.avalon.framework.service.Serviceable; 027 028import org.ametys.cms.contenttype.ContentTypesHelper; 029import org.ametys.cms.repository.Content; 030import org.ametys.cms.repository.ContentQueryHelper; 031import org.ametys.cms.repository.ContentTypeExpression; 032import org.ametys.cms.repository.DefaultContent; 033import org.ametys.cms.repository.ModifiableContent; 034import org.ametys.odf.course.Course; 035import org.ametys.odf.program.Program; 036import org.ametys.odf.skill.ODFSkillsHelper; 037import org.ametys.odf.skill.workflow.SkillEditionFunction; 038import org.ametys.plugins.repository.AmetysObjectResolver; 039import org.ametys.plugins.repository.AmetysRepositoryException; 040import org.ametys.plugins.repository.ModifiableTraversableAmetysObject; 041import org.ametys.plugins.repository.jcr.NameHelper; 042import org.ametys.plugins.repository.query.expression.AndExpression; 043import org.ametys.plugins.repository.query.expression.Expression.Operator; 044import org.ametys.plugins.repository.query.expression.StringExpression; 045import org.ametys.runtime.model.ModelItem; 046 047/** 048 * Copy updater to update the micro skills on a macro skill and the macro skills on a {@link Program} and (@link Course}. 049 */ 050public class SkillsCopyUpdater extends AbstractProgramItemAttributeCopyUpdater implements Serviceable 051{ 052 /** The ametys object resolver */ 053 protected AmetysObjectResolver _resolver; 054 /** The content types helper */ 055 protected ContentTypesHelper _contentTypesHelper; 056 057 public void service(ServiceManager manager) throws ServiceException 058 { 059 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 060 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 061 } 062 063 public List<Content> getAdditionalContents(String catalogName) 064 { 065 List<Content> results = new ArrayList<>(); 066 results.addAll(_getContents(catalogName, SkillEditionFunction.MICRO_SKILL_TYPE)); 067 results.addAll(_getContents(catalogName, SkillEditionFunction.MACRO_SKILL_TYPE)); 068 return results; 069 } 070 071 public Map<Content, Content> copyAdditionalContents(String initialCatalogName, String newCatalogName) 072 { 073 // Only copy the skills if they are enabled 074 if (!ODFSkillsHelper.isSkillsEnabled()) 075 { 076 return Map.of(); 077 } 078 079 Map<Content, Content> copiedContents = new HashMap<>(); 080 copiedContents.putAll(_copySkills(_getContents(initialCatalogName, SkillEditionFunction.MICRO_SKILL_TYPE), newCatalogName)); 081 copiedContents.putAll(_copySkills(_getContents(initialCatalogName, SkillEditionFunction.MACRO_SKILL_TYPE), newCatalogName)); 082 return copiedContents; 083 } 084 085 public void updateContents(String initialCatalogName, String newCatalogName, Map<Content, Content> copiedContents, Content targetParentContent) 086 { 087 // For every copied program, update its links from the original macro skills to the copied macro skills 088 for (Content copiedContent : copiedContents.values()) 089 { 090 try 091 { 092 if (copiedContent instanceof Program program) 093 { 094 if (ODFSkillsHelper.isSkillsEnabled()) 095 { 096 _updateContentAttribute(program, Program.SKILLS, copiedContents); 097 _updateContentAttribute(program, Program.TRANSVERSAL_SKILLS, copiedContents); 098 _updateContentAttribute(program, Program.BLOCKING_SKILLS, copiedContents); 099 program.saveChanges(); 100 } 101 } 102 else if (copiedContent instanceof Course course) 103 { 104 if (ODFSkillsHelper.isSkillsEnabled()) 105 { 106 _updateContentAttribute(course, Course.ACQUIRED_MICRO_SKILLS + ModelItem.ITEM_PATH_SEPARATOR + Course.ACQUIRED_MICRO_SKILLS_SKILLS, copiedContents); 107 _updateContentAttribute(course, Course.ACQUIRED_MICRO_SKILLS + ModelItem.ITEM_PATH_SEPARATOR + Course.ACQUIRED_MICRO_SKILLS_PROGRAM, copiedContents); 108 course.saveChanges(); 109 } 110 } 111 else if (copiedContent instanceof ModifiableContent modifiableCopiedContent) 112 { 113 if (_contentTypesHelper.isInstanceOf(copiedContent, SkillEditionFunction.MACRO_SKILL_TYPE)) 114 { 115 if (ODFSkillsHelper.isSkillsEnabled()) 116 { 117 _updateContentAttribute(modifiableCopiedContent, "parentProgram", copiedContents); 118 } 119 _updateContentAttribute(modifiableCopiedContent, "microSkills", copiedContents); 120 modifiableCopiedContent.saveChanges(); 121 } 122 else if (_contentTypesHelper.isInstanceOf(copiedContent, SkillEditionFunction.MICRO_SKILL_TYPE)) 123 { 124 _updateContentAttribute(modifiableCopiedContent, "parentMacroSkill", copiedContents); 125 modifiableCopiedContent.saveChanges(); 126 } 127 } 128 } 129 catch (Exception e) 130 { 131 getLogger().error("An error occurred while updating the content '{}'", copiedContent.getId(), e); 132 } 133 } 134 } 135 136 private Map<Content, Content> _copySkills(List<DefaultContent> skills, String newCatalogName) 137 { 138 Map<Content, Content> copiedSkills = new HashMap<>(); 139 for (DefaultContent skill : skills) 140 { 141 // Copy the skill in the targeted catalog 142 try 143 { 144 // Create the skill 145 ModifiableContent newSkill = skill.copyTo((ModifiableTraversableAmetysObject) skill.getParent(), NameHelper.filterName(skill.getTitle())); 146 147 // Set the new catalog 148 newSkill.setValue("catalog", newCatalogName); 149 newSkill.saveChanges(); 150 151 copiedSkills.put(skill, newSkill); 152 } 153 catch (AmetysRepositoryException e) 154 { 155 getLogger().error("Impossible to create the skill '{}' ({}) while creating the catalog {}", skill.getTitle(), skill.getId(), newCatalogName, e); 156 } 157 } 158 159 return copiedSkills; 160 } 161 162 private <T extends Content> List<T> _getContents(String catalogName, String contentType) 163 { 164 return this.<T>_getContents(catalogName, contentType, null).toList(); 165 } 166 167 private <T extends Content> Stream<T> _getContents(String catalogName, String contentType, Content skill) 168 { 169 AndExpression expression = new AndExpression(); 170 expression.add(new ContentTypeExpression(Operator.EQ, contentType)); 171 expression.add(new StringExpression("catalog", Operator.EQ, catalogName)); 172 if (skill != null) 173 { 174 expression.add(new StringExpression("code", Operator.EQ, skill.getValue("code"))); 175 } 176 177 String query = ContentQueryHelper.getContentXPathQuery(expression); 178 return _resolver.<T>query(query).stream(); 179 } 180}