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 MACRO_SKILL_TYPE = "org.ametys.plugins.odf.Content.macroSkill"; 054 /** The content type of micro skills */ 055 public static final String MICRO_SKILL_TYPE = "org.ametys.plugins.odf.Content.microSkill"; 056 /** Content name prefix for skills */ 057 public static final String CONTENT_NAME_PREFIX = "skill-"; 058 /** Constant for storing the catalog name to use into the transient variables map. */ 059 public static final String CONTENT_TRANSVERSAL_KEY = SkillEditionFunction.class.getName() + "$transversal"; 060 061 /** Constant for storing the catalog name to use into the transient variables map. */ 062 public static final Set<Integer> CONTENT_CREATION_ACTION_IDS = Set.of(0, 1); 063 064 /** Ametys object resolver available to subclasses. */ 065 protected AmetysObjectResolver _resolver; 066 /** The content types helper */ 067 protected ContentTypesHelper _contentTypesHelper; 068 069 @Override 070 public void service(ServiceManager manager) throws ServiceException 071 { 072 super.service(manager); 073 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 074 _contentTypesHelper = (ContentTypesHelper) manager.lookup(ContentTypesHelper.ROLE); 075 } 076 077 @Override 078 public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException 079 { 080 WorkflowAwareContent content = getContent(transientVars); 081 082 // If we are creating the skill, add the initial values 083 if (_contentTypesHelper.isInstanceOf(content, ASBTRACT_SKILL_TYPE) && CONTENT_CREATION_ACTION_IDS.contains((int) transientVars.get("actionId"))) 084 { 085 if (_contentTypesHelper.isInstanceOf(content, MACRO_SKILL_TYPE)) 086 { 087 _updateMacroSkill((ModifiableContent) content, transientVars); 088 } 089 else if (_contentTypesHelper.isInstanceOf(content, MICRO_SKILL_TYPE)) 090 { 091 _updateMicroSkill((ModifiableContent) content, transientVars); 092 } 093 } 094 } 095 096 private void _updateSkill(ModifiableContent skillContent, Map transientVars, Optional<String> parentId) 097 { 098 Optional<String> catalog = Optional.empty(); 099 if (parentId.isPresent()) 100 { 101 catalog = _getParentCatalog(parentId.get()); 102 } 103 104 if (catalog.isEmpty()) 105 { 106 // Try to retrieve the catalog from transient vars or initial value supplier 107 catalog = this.<String>_getValueFromTransientVars(transientVars, AbstractCreateODFContentFunction.CONTENT_CATALOG_KEY) 108 // Used to get the catalog if the skill item is created by java code, like for the csv import. 109 .or(() -> this.<String>_getValueFromInitialValueSupplier(transientVars, List.of("catalog"))) 110 .or(() -> this.<String>_getValueFromParameters(transientVars, "catalog")); 111 } 112 113 /* Set the catalog */ 114 if (catalog.isPresent()) 115 { 116 // Set the catalog if value is present 117 skillContent.setValue("catalog", catalog.get()); 118 } 119 120 /* Set the code */ 121 122 // Used to get the code if the skill is created by java code, like for the csv import. 123 _getValueFromInitialValueSupplier(transientVars, List.of("code")) 124 // Set the code if value is present 125 .ifPresent(value -> skillContent.setValue("code", value)); 126 127 // Generate a code if empty 128 String code = skillContent.getValue("code"); 129 if (StringUtils.isEmpty(code)) 130 { 131 skillContent.setValue("code", org.ametys.core.util.StringUtils.generateKey().toUpperCase()); 132 } 133 134 skillContent.saveChanges(); 135 } 136 137 private void _updateMacroSkill(ModifiableContent skillContent, Map transientVars) 138 { 139 140 // Try to get the parent program from parent context value transient var 141 Optional<String> optionalParentId = Optional.ofNullable((String) transientVars.get(CreateContentFunction.PARENT_CONTEXT_VALUE)); 142 143 // Try to get the transversal attribute from initial value supplier 144 Optional<Object> optionalTransversal = _getValueFromTransientVars(transientVars, CONTENT_TRANSVERSAL_KEY) 145 // Used to get the transversal if the skill item is created by java code, like for the csv import. 146 .or(() -> _getValueFromInitialValueSupplier(transientVars, List.of("transversal"))) 147 .or(() -> _getValueFromParameters(transientVars, "transversal")); 148 149 /* Set the parent */ 150 if (optionalParentId.isPresent()) 151 { 152 String parentId = optionalParentId.get(); 153 // Set the parent program if value is present and synchronize it 154 skillContent.synchronizeValues(Map.of("parentProgram", parentId)); 155 skillContent.setValue("transversal", false); 156 } 157 else if (optionalTransversal.isPresent()) 158 { 159 Object transversalObj = optionalTransversal.get(); 160 boolean transversal = transversalObj instanceof String ? Boolean.valueOf((String) transversalObj) : (Boolean) transversalObj; 161 162 skillContent.setValue("transversal", transversal); 163 } 164 else 165 { 166 // Defaults to true (example when importing skills via CSV) 167 skillContent.setValue("transversal", true); 168 } 169 170 _updateSkill(skillContent, transientVars, optionalParentId); 171 } 172 173 private void _updateMicroSkill(ModifiableContent skillContent, Map transientVars) 174 { 175 // Try to get the parent program from parent context value transient var 176 Optional<String> optionalParentId = Optional.ofNullable((String) transientVars.get(CreateContentFunction.PARENT_CONTEXT_VALUE)); 177 178 /* Set the parent */ 179 if (optionalParentId.isPresent()) 180 { 181 String parentId = optionalParentId.get(); 182 // Set the parent macro skill if value is present and synchronize it 183 skillContent.synchronizeValues(Map.of("parentMacroSkill", parentId)); 184 } 185 186 _updateSkill(skillContent, transientVars, optionalParentId); 187 } 188 189 private Optional<String> _getParentCatalog(String parentId) 190 { 191 if (StringUtils.isNotBlank(parentId)) 192 { 193 Object parent = _resolver.resolveById(parentId); 194 if (parent != null && parent instanceof Content parentContent && parentContent.hasValue("catalog")) 195 { 196 return Optional.of(parentContent.getValue("catalog")); 197 } 198 } 199 200 return Optional.empty(); 201 } 202 203 @Override 204 public I18nizableText getLabel() 205 { 206 return new I18nizableText("plugin.odf", "PLUGINS_ODF_CREATE_SKILL_CONTENT_FUNCTION_LABEL"); 207 } 208 209 @SuppressWarnings("unchecked") 210 private <T> Optional<T> _getValueFromTransientVars(Map transientVars, String attributeName) 211 { 212 return Optional.ofNullable((T) transientVars.get(attributeName)) 213 .filter(Objects::nonNull); 214 } 215 216 @SuppressWarnings("unchecked") 217 private <T> Optional<T> _getValueFromInitialValueSupplier(Map transientVars, List<String> attributePath) 218 { 219 return Optional.ofNullable(transientVars.get(CreateContentFunction.INITIAL_VALUE_SUPPLIER)) 220 .map(Function.class::cast) 221 .map(function -> function.apply(attributePath)) 222 .filter(Objects::nonNull) 223 .map(value -> (T) value); 224 } 225 226 @SuppressWarnings("unchecked") 227 private <T> Optional<T> _getValueFromParameters(Map transientVars, String attributeName) 228 { 229 Object objectParameters = transientVars.get(AbstractWorkflowComponent.CONTEXT_PARAMETERS_KEY); 230 231 if (objectParameters != null && objectParameters instanceof Map parameters) 232 { 233 Object values = parameters.get(EditContentFunction.FORM_RAW_VALUES); 234 if (values != null && values instanceof Map valuesMap) 235 { 236 return Optional.ofNullable((T) valuesMap.get(EditContentFunction.FORM_ELEMENTS_PREFIX + attributeName)) 237 .filter(Objects::nonNull); 238 } 239 } 240 241 return Optional.empty(); 242 } 243}