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.Optional; 021import java.util.function.Function; 022 023import org.apache.avalon.framework.service.ServiceException; 024import org.apache.avalon.framework.service.ServiceManager; 025import org.apache.commons.lang3.ArrayUtils; 026import org.apache.commons.lang3.StringUtils; 027 028import org.ametys.cms.repository.Content; 029import org.ametys.cms.repository.ModifiableContent; 030import org.ametys.cms.repository.WorkflowAwareContent; 031import org.ametys.cms.workflow.AbstractContentWorkflowComponent; 032import org.ametys.cms.workflow.CreateContentFunction; 033import org.ametys.odf.workflow.AbstractCreateODFContentFunction; 034import org.ametys.plugins.repository.AmetysObjectResolver; 035import org.ametys.plugins.workflow.EnhancedFunction; 036import org.ametys.runtime.i18n.I18nizableText; 037 038import com.opensymphony.module.propertyset.PropertySet; 039import com.opensymphony.workflow.WorkflowException; 040 041/** 042 * OSWorkflow function for creating a MacroSkill or MicroSkill content 043 */ 044public class SkillEditionFunction extends AbstractContentWorkflowComponent implements EnhancedFunction 045{ 046 /** The content type of macro skills */ 047 public static final String MACRO_SKILL_TYPE = "org.ametys.plugins.odf.Content.macroSkill"; 048 /** The content type of micro skills */ 049 public static final String MICRO_SKILL_TYPE = "org.ametys.plugins.odf.Content.microSkill"; 050 /** Content name prefix for skills */ 051 public static final String CONTENT_NAME_PREFIX = "skill-"; 052 053 /** Ametys object resolver available to subclasses. */ 054 protected AmetysObjectResolver _resolver; 055 056 @Override 057 public void service(ServiceManager manager) throws ServiceException 058 { 059 super.service(manager); 060 _resolver = (AmetysObjectResolver) manager.lookup(AmetysObjectResolver.ROLE); 061 } 062 063 @Override 064 public void execute(Map transientVars, Map args, PropertySet ps) throws WorkflowException 065 { 066 WorkflowAwareContent content = getContent(transientVars); 067 068 if (ArrayUtils.containsAny(content.getTypes(), SkillEditionFunction.MACRO_SKILL_TYPE, SkillEditionFunction.MICRO_SKILL_TYPE)) 069 { 070 ModifiableContent skillContent = (ModifiableContent) content; 071 Optional<String> catalog = Optional.empty(); 072 073 Optional<String> parentProgram = _getParent(transientVars, skillContent); 074 075 /* Set the parent program */ 076 if (ArrayUtils.contains(skillContent.getTypes(), SkillEditionFunction.MACRO_SKILL_TYPE)) 077 { 078 if (parentProgram.isPresent()) 079 { 080 String parentProgramId = parentProgram.get(); 081 // Set the parent program if value is present and synchronize it 082 skillContent.synchronizeValues(Map.of("parentProgram", parentProgramId)); 083 } 084 } 085 086 catalog = _getCatalog(transientVars, parentProgram); 087 088 /* Set the catalog */ 089 if (catalog.isPresent()) 090 { 091 // Set the catalog if value is present 092 skillContent.setValue("catalog", catalog.get()); 093 } 094 095 /* Set the code */ 096 097 // Used to get the code if the skill is created by java code, like for the csv import. 098 _getValueFromInitialValueSupplier(transientVars, List.of("code")) 099 // Set the code if value is present 100 .ifPresent(value -> skillContent.setValue("code", value)); 101 102 // Generate a code if empty 103 String code = content.getValue("code"); 104 if (StringUtils.isEmpty(code)) 105 { 106 skillContent.setValue("code", org.ametys.core.util.StringUtils.generateKey().toUpperCase()); 107 } 108 } 109 } 110 111 private Optional<String> _getCatalog(Map transientVars, Optional<String> parentId) 112 { 113 Optional<String> catalog = Optional.empty(); 114 115 // First try to retrieve the catalog from the parent if it is a microSkill 116 if (parentId.isPresent()) 117 { 118 catalog = _getParentCatalog(parentId.get()); 119 } 120 121 // If there was no value in the parent or no parent, try to retrieve the catalog from transient vars or initial value supplier 122 if (catalog.isEmpty()) 123 { 124 // Try to get the catalog from transient vars 125 catalog = _getValueFromTransientVars(transientVars, AbstractCreateODFContentFunction.CONTENT_CATALOG_KEY) 126 // Used to get the catalog if the skill item is created by java code, like for the csv import. 127 .or(() -> _getValueFromInitialValueSupplier(transientVars, List.of("catalog"))); 128 } 129 130 return catalog; 131 } 132 133 private Optional<String> _getParentCatalog(String parentId) 134 { 135 if (StringUtils.isNotBlank(parentId)) 136 { 137 Object parent = _resolver.resolveById(parentId); 138 if (parent != null && parent instanceof Content parentContent && parentContent.hasValue("catalog")) 139 { 140 return Optional.of(parentContent.getValue("catalog")); 141 } 142 } 143 144 return Optional.empty(); 145 } 146 147 private Optional<String> _getParent(Map transientVars, Content content) 148 { 149 // Try to get the parent program from parent context value transient var 150 Optional<String> parentId = Optional.ofNullable((String) transientVars.get(CreateContentFunction.PARENT_CONTEXT_VALUE)); 151 152 // If there was no value, search deeper in the transient vars 153 if (parentId.isEmpty() && ArrayUtils.contains(content.getTypes(), SkillEditionFunction.MACRO_SKILL_TYPE)) 154 { 155 // Try to get the parent program from transient vars 156 parentId = _getValueFromTransientVars(transientVars, "parentProgram") 157 // Used to get the parent program if the skill item is created by java code, like for the csv import. 158 .or(() -> _getValueFromInitialValueSupplier(transientVars, List.of("parentProgram"))); 159 } 160 161 return parentId; 162 } 163 164 @Override 165 public I18nizableText getLabel() 166 { 167 return new I18nizableText("plugin.odf", "PLUGINS_ODF_CREATE_SKILL_CONTENT_FUNCTION_LABEL"); 168 } 169 170 private Optional<String> _getValueFromTransientVars(Map transientVars, String attributeName) 171 { 172 return Optional.ofNullable(transientVars.get(attributeName)) 173 .map(String.class::cast) 174 .filter(StringUtils::isNotEmpty); 175 } 176 177 @SuppressWarnings("unchecked") 178 private Optional<String> _getValueFromInitialValueSupplier(Map transientVars, List<String> attributePath) 179 { 180 return Optional.ofNullable(transientVars.get(CreateContentFunction.INITIAL_VALUE_SUPPLIER)) 181 .map(Function.class::cast) 182 .map(function -> function.apply(attributePath)) 183 .map(String.class::cast) 184 .filter(StringUtils::isNotEmpty); 185 } 186}