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.skill.workflow; 017 018import java.util.List; 019import java.util.Map; 020import java.util.Objects; 021import java.util.Optional; 022import java.util.Set; 023import java.util.function.Function; 024 025import org.apache.avalon.framework.service.ServiceException; 026import org.apache.avalon.framework.service.ServiceManager; 027import org.apache.commons.lang3.StringUtils; 028 029import org.ametys.cms.contenttype.ContentTypesHelper; 030import org.ametys.cms.repository.Content; 031import org.ametys.cms.repository.ModifiableContent; 032import org.ametys.cms.repository.WorkflowAwareContent; 033import org.ametys.cms.workflow.AbstractContentWorkflowComponent; 034import org.ametys.cms.workflow.CreateContentFunction; 035import org.ametys.cms.workflow.EditContentFunction; 036import org.ametys.odf.workflow.AbstractCreateODFContentFunction; 037import org.ametys.plugins.repository.AmetysObjectResolver; 038import org.ametys.plugins.workflow.AbstractWorkflowComponent; 039import org.ametys.plugins.workflow.EnhancedFunction; 040import org.ametys.runtime.i18n.I18nizableText; 041 042import com.opensymphony.module.propertyset.PropertySet; 043import com.opensymphony.workflow.WorkflowException; 044 045/** 046 * OSWorkflow function for creating a MacroSkill or MicroSkill content 047 */ 048public class SkillEditionFunction extends AbstractContentWorkflowComponent implements EnhancedFunction 049{ 050 /** The content type of macro skills */ 051 public static final String ASBTRACT_SKILL_TYPE = "org.ametys.plugins.odf.Content.abstractSkill"; 052 /** The content type of macro skills */ 053 public static final String ASBTRACT_MACRO_SKILL_TYPE = "org.ametys.plugins.odf.Content.abstractMacroSkill"; 054 /** The content type of transversal macro skills */ 055 public static final String TRANSVERSAL_MACRO_SKILL_TYPE = "org.ametys.plugins.odf.Content.transversalMacroSkill"; 056 /** The content type of program macro skills */ 057 public static final String DISCIPLINARY_MACRO_SKILL_TYPE = "org.ametys.plugins.odf.Content.disciplinaryMacroSkill"; 058 /** The content type of micro skills */ 059 public static final String MICRO_SKILL_TYPE = "org.ametys.plugins.odf.Content.microSkill"; 060 /** Content name prefix for skills */ 061 public static final String CONTENT_NAME_PREFIX = "skill-"; 062 /** Constant for storing the catalog name to use into the transient variables map. */ 063 public static final String CONTENT_TRANSVERSAL_KEY = SkillEditionFunction.class.getName() + "$transversal"; 064 065 /** Constant for storing the catalog name to use into the transient variables map. */ 066 public static final Set<Integer> CONTENT_CREATION_ACTION_IDS = Set.of(0, 1); 067 068 /** Ametys object resolver available to subclasses. */ 069 protected AmetysObjectResolver _resolver; 070 /** The content types helper */ 071 protected ContentTypesHelper _contentTypesHelper; 072 073 @Override 074 public void service(ServiceManager manager) throws ServiceException 075 { 076 super.service(manager); 077 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 078 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 079 } 080 081 @Override 082 public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException 083 { 084 WorkflowAwareContent content = getContent(transientVars); 085 086 // If we are creating the skill, add the initial values 087 if (_contentTypesHelper.isInstanceOf(content, ASBTRACT_SKILL_TYPE) && CONTENT_CREATION_ACTION_IDS.contains((int) transientVars.get("actionId"))) 088 { 089 if (_contentTypesHelper.isInstanceOf(content, TRANSVERSAL_MACRO_SKILL_TYPE)) 090 { 091 _updateSkill((ModifiableContent) content, transientVars, Optional.empty()); 092 } 093 if (_contentTypesHelper.isInstanceOf(content, DISCIPLINARY_MACRO_SKILL_TYPE)) 094 { 095 _updateSkillWithParent((ModifiableContent) content, transientVars, "parentProgram"); 096 } 097 else if (_contentTypesHelper.isInstanceOf(content, MICRO_SKILL_TYPE)) 098 { 099 _updateSkillWithParent((ModifiableContent) content, transientVars, "parentMacroSkill"); 100 } 101 } 102 } 103 104 private void _updateSkillWithParent(ModifiableContent skillContent, Map transientVars, String parentAttributeName) 105 { 106 // Try to get the parent program from parent context value transient var 107 Optional<String> optionalParentId = Optional.ofNullable((String) transientVars.get(CreateContentFunction.PARENT_CONTEXT_VALUE)); 108 109 Optional<String> catalog = Optional.empty(); 110 /* Set the parent */ 111 if (optionalParentId.isPresent()) 112 { 113 String parentId = optionalParentId.get(); 114 // Set the parent macro skill if value is present and synchronize it 115 skillContent.synchronizeValues(Map.of(parentAttributeName, parentId)); 116 117 catalog = _getParentCatalog(parentId); 118 } 119 120 _updateSkill(skillContent, transientVars, catalog); 121 } 122 123 private void _updateSkill(ModifiableContent skillContent, Map transientVars, Optional<String> catalog) 124 { 125 // Try to retrieve the catalog from transient vars or initial value supplier 126 Optional<String> catalogToUse = catalog.or(() -> this.<String>_getValueFromTransientVars(transientVars, AbstractCreateODFContentFunction.CONTENT_CATALOG_KEY)) 127 // Used to get the catalog if the skill item is created by java code, like for the csv import. 128 .or(() -> this.<String>_getValueFromInitialValueSupplier(transientVars, List.of("catalog"))) 129 .or(() -> this.<String>_getValueFromParameters(transientVars, "catalog")); 130 131 /* Set the catalog */ 132 if (catalogToUse.isPresent()) 133 { 134 // Set the catalog if value is present 135 skillContent.setValue("catalog", catalogToUse.get()); 136 } 137 138 /* Set the code */ 139 // Used to get the code if the skill is created by java code, like for the csv import. 140 _getValueFromInitialValueSupplier(transientVars, List.of("code")) 141 // Set the code if value is present 142 .ifPresent(value -> skillContent.setValue("code", value)); 143 144 // Generate a code if empty 145 String code = skillContent.getValue("code"); 146 if (StringUtils.isEmpty(code)) 147 { 148 skillContent.setValue("code", org.ametys.core.util.StringUtils.generateKey().toUpperCase()); 149 } 150 151 skillContent.saveChanges(); 152 } 153 154 private Optional<String> _getParentCatalog(String parentId) 155 { 156 if (StringUtils.isNotBlank(parentId)) 157 { 158 Object parent = _resolver.resolveById(parentId); 159 if (parent != null && parent instanceof Content parentContent && parentContent.hasValue("catalog")) 160 { 161 return Optional.of(parentContent.getValue("catalog")); 162 } 163 } 164 165 return Optional.empty(); 166 } 167 168 @Override 169 public I18nizableText getLabel() 170 { 171 return new I18nizableText("plugin.odf", "PLUGINS_ODF_CREATE_SKILL_CONTENT_FUNCTION_LABEL"); 172 } 173 174 @SuppressWarnings("unchecked") 175 private <T> Optional<T> _getValueFromTransientVars(Map transientVars, String attributeName) 176 { 177 return Optional.ofNullable((T) transientVars.get(attributeName)) 178 .filter(Objects::nonNull); 179 } 180 181 @SuppressWarnings("unchecked") 182 private <T> Optional<T> _getValueFromInitialValueSupplier(Map transientVars, List<String> attributePath) 183 { 184 return Optional.ofNullable(transientVars.get(CreateContentFunction.INITIAL_VALUE_SUPPLIER)) 185 .map(Function.class::cast) 186 .map(function -> function.apply(attributePath)) 187 .filter(Objects::nonNull) 188 .map(value -> (T) value); 189 } 190 191 @SuppressWarnings("unchecked") 192 private <T> Optional<T> _getValueFromParameters(Map transientVars, String attributeName) 193 { 194 Object objectParameters = transientVars.get(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY); 195 196 if (objectParameters != null && objectParameters instanceof Map parameters) 197 { 198 Object values = parameters.get(EditContentFunction.FORM_RAW_VALUES); 199 if (values != null && values instanceof Map valuesMap) 200 { 201 return Optional.ofNullable((T) valuesMap.get(EditContentFunction.FORM_ELEMENTS_PREFIX + attributeName)) 202 .filter(Objects::nonNull); 203 } 204 } 205 206 return Optional.empty(); 207 } 208}