Provide index token to tosca function for nested lists
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / components / csar / ToscaFunctionYamlParsingHandler.java
1 /*
2  * -
3  *  ============LICENSE_START=======================================================
4  *  Copyright (C) 2022 Nordix Foundation.
5  *  ================================================================================
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *       http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  *
18  *  SPDX-License-Identifier: Apache-2.0
19  *  ============LICENSE_END=========================================================
20  */
21
22 package org.openecomp.sdc.be.components.csar;
23
24 import java.util.ArrayList;
25 import java.util.HashMap;
26 import java.util.List;
27 import java.util.Map;
28 import java.util.Objects;
29 import java.util.Optional;
30 import java.util.stream.Collectors;
31 import java.util.stream.Stream;
32 import org.apache.commons.collections.CollectionUtils;
33 import org.apache.commons.lang3.StringUtils;
34 import org.openecomp.sdc.be.config.Configuration;
35 import org.openecomp.sdc.be.config.ConfigurationManager;
36 import org.openecomp.sdc.be.datatypes.elements.CustomYamlFunction;
37 import org.openecomp.sdc.be.datatypes.elements.ToscaConcatFunction;
38 import org.openecomp.sdc.be.datatypes.elements.ToscaCustomFunction;
39 import org.openecomp.sdc.be.datatypes.elements.ToscaFunction;
40 import org.openecomp.sdc.be.datatypes.elements.ToscaFunctionParameter;
41 import org.openecomp.sdc.be.datatypes.elements.ToscaFunctionType;
42 import org.openecomp.sdc.be.datatypes.elements.ToscaGetFunctionDataDefinition;
43 import org.openecomp.sdc.be.datatypes.elements.ToscaStringParameter;
44 import org.openecomp.sdc.be.datatypes.enums.PropertySource;
45 import org.openecomp.sdc.be.datatypes.tosca.ToscaGetFunctionType;
46
47 @org.springframework.stereotype.Component
48 public class ToscaFunctionYamlParsingHandler {
49
50     private static Optional<ToscaFunction> handleGetPropertyFunction(Map<String, Object> toscaFunctionPropertyValueMap, String functionType,
51                                                                      ToscaFunctionType toscaFunctionType) {
52         final ToscaGetFunctionDataDefinition toscaGetFunction = new ToscaGetFunctionDataDefinition();
53         toscaGetFunction.setFunctionType(
54             toscaFunctionType == ToscaFunctionType.GET_PROPERTY ? ToscaGetFunctionType.GET_PROPERTY : ToscaGetFunctionType.GET_ATTRIBUTE
55         );
56         final Object functionValueObj = toscaFunctionPropertyValueMap.get(functionType);
57         if (!(functionValueObj instanceof List)) {
58             return Optional.empty();
59         }
60         final List<String> functionParameters;
61         try {
62             functionParameters = ((List<Object>) functionValueObj).stream()
63                 .map(object -> Objects.toString(object, null))
64                 .collect(Collectors.toList());
65         } catch (final ClassCastException ignored) {
66             return Optional.empty();
67         }
68         if (functionParameters.size() < 2) {
69             return Optional.empty();
70         }
71         final String propertySourceType = functionParameters.get(0);
72         final PropertySource propertySource = PropertySource.findType(propertySourceType).orElse(null);
73         if (propertySource == PropertySource.SELF) {
74             toscaGetFunction.setPropertySource(propertySource);
75         } else {
76             toscaGetFunction.setPropertySource(PropertySource.INSTANCE);
77             toscaGetFunction.setSourceName(propertySourceType);
78         }
79         List<String> propertySourceIndex = functionParameters.subList(1, functionParameters.size());
80         List<String> propertySourcePath = new ArrayList<>();
81         propertySourcePath.add((String)propertySourceIndex.get(0));
82         if (propertySourceIndex.size() > 1 ) {
83             List<Object> indexParsedList = new ArrayList<Object>();
84             List<String> indexObjectList = propertySourceIndex.subList(1,propertySourceIndex.size());
85             boolean loopFlag = true;
86             for (String indexValue : indexObjectList) {
87                 if (!indexValue.equalsIgnoreCase("INDEX") && !StringUtils.isNumeric(indexValue) && loopFlag) {
88                     propertySourcePath.add(indexValue);
89                 } else {
90                     loopFlag = false;
91                     if (StringUtils.isNumeric(indexValue)) {
92                         indexParsedList.add(Integer.parseInt(indexValue));
93                     } else {
94                         indexParsedList.add(indexValue);
95                     }
96                 }
97             }
98             toscaGetFunction.setToscaIndexList(indexParsedList);
99         }
100         toscaGetFunction.setPropertyPathFromSource(propertySourcePath);
101         final String propertyName = toscaGetFunction.getPropertyPathFromSource().get(toscaGetFunction.getPropertyPathFromSource().size() - 1);
102         toscaGetFunction.setPropertyName(propertyName);
103         return Optional.of(toscaGetFunction);
104     }
105
106     private static Optional<ToscaFunction> handleGetInputFunction(Map<String, Object> toscaFunctionPropertyValueMap, String functionType) {
107         final ToscaGetFunctionDataDefinition toscaGetFunction = new ToscaGetFunctionDataDefinition();
108         toscaGetFunction.setFunctionType(ToscaGetFunctionType.GET_INPUT);
109         toscaGetFunction.setPropertySource(PropertySource.SELF);
110         final Object functionValueObj = toscaFunctionPropertyValueMap.get(functionType);
111         if (!(functionValueObj instanceof List) && !(functionValueObj instanceof String)) {
112             return Optional.empty();
113         }
114         if (functionValueObj instanceof String) {
115             toscaGetFunction.setPropertyPathFromSource(List.of((String) functionValueObj));
116         } else {
117             final List<String> functionParameters;
118             try {
119                 functionParameters = ((List<Object>) functionValueObj).stream()
120                     .map(object -> Objects.toString(object, null))
121                     .collect(Collectors.toList());
122             } catch (final ClassCastException ignored) {
123                 return Optional.empty();
124             }
125             List<String> propertySourcePath = new ArrayList<>();
126             propertySourcePath.add((String)functionParameters.get(0));
127             if (functionParameters.size() > 1 ) {
128                 List<Object> indexParsedList = new ArrayList<Object>();
129                 List<String> indexObjectList = functionParameters.subList(1,functionParameters.size());
130                 boolean loopFlag = true;
131                 for (String indexValue : indexObjectList) {
132                     if (!indexValue.equalsIgnoreCase("INDEX") && !StringUtils.isNumeric(indexValue) && loopFlag) {
133                         propertySourcePath.add(indexValue);
134                     } else {
135                         loopFlag = false;
136                         if (StringUtils.isNumeric(indexValue)) {
137                             indexParsedList.add(Integer.parseInt(indexValue));
138                         } else {
139                             indexParsedList.add(indexValue);
140                         }
141                     }
142                 }
143                 toscaGetFunction.setToscaIndexList(indexParsedList);
144             }
145             toscaGetFunction.setPropertyPathFromSource(propertySourcePath);
146         }
147         final String propertyName = toscaGetFunction.getPropertyPathFromSource().get(toscaGetFunction.getPropertyPathFromSource().size() - 1);
148         toscaGetFunction.setPropertyName(propertyName);
149         return Optional.of(toscaGetFunction);
150     }
151
152     /**
153      * Builds a {@link ToscaFunction} based on the property value. It will build the object with the maximum information available in the property
154      * value, as not all the necessary information can be extracted from it. It will only parse values from supported functions in
155      * {@link ToscaFunctionType}.
156      *
157      * @param toscaFunctionPropertyValueMap the value of a property calls a TOSCA function
158      * @return the partially filled {@link ToscaFunction} object
159      */
160     public Optional<ToscaFunction> buildToscaFunctionBasedOnPropertyValue(final Map<String, Object> toscaFunctionPropertyValueMap) {
161         if (!isPropertyValueToscaFunction(toscaFunctionPropertyValueMap)) {
162             return Optional.empty();
163         }
164         final String functionType = toscaFunctionPropertyValueMap.keySet().iterator().next();
165         final ToscaFunctionType toscaFunctionType =
166             ToscaFunctionType.findType(functionType).orElse(functionType.startsWith("$") ? ToscaFunctionType.CUSTOM : null);
167         if (toscaFunctionType == null) {
168             return Optional.empty();
169         }
170         switch (toscaFunctionType) {
171             case GET_INPUT: {
172                 return handleGetInputFunction(toscaFunctionPropertyValueMap, functionType);
173             }
174             case GET_PROPERTY:
175             case GET_ATTRIBUTE: {
176                 return handleGetPropertyFunction(toscaFunctionPropertyValueMap, functionType, toscaFunctionType);
177             }
178             case CONCAT:
179                 return handleConcatFunction(toscaFunctionPropertyValueMap, functionType);
180             case CUSTOM:
181                 return handleCustomFunction(toscaFunctionPropertyValueMap, functionType);
182             default:
183                 return Optional.empty();
184         }
185     }
186
187     private Optional<ToscaFunction> handleCustomFunction(Map<String, Object> toscaFunctionPropertyValueMap, String functionType) {
188         final ToscaCustomFunction toscaCustomFunction = new ToscaCustomFunction();
189         toscaCustomFunction.setName(functionType.substring(1));
190         final Object functionValueObj = toscaFunctionPropertyValueMap.get(functionType);
191         toscaCustomFunction.setToscaFunctionType(getCustomFunctionType(toscaCustomFunction.getName()));
192         if (ToscaFunctionType.GET_INPUT.equals(toscaCustomFunction.getToscaFunctionType())) {
193             return handelCustomFunctionGetInputType(toscaCustomFunction, functionValueObj);
194         }
195         return handelCustomFunctionCustomType(toscaCustomFunction, functionValueObj);
196     }
197
198     private Optional<ToscaFunction> handelCustomFunctionCustomType(ToscaCustomFunction toscaCustomFunction, Object functionValueObj) {
199         if (!(functionValueObj instanceof List)) {
200             return Optional.empty();
201         }
202         final List<Object> functionParameters = (List<Object>) functionValueObj;
203         functionParameters.forEach(parameter -> {
204             if (parameter instanceof String) {
205                 final var stringParameter = new ToscaStringParameter();
206                 stringParameter.setValue((String) parameter);
207                 toscaCustomFunction.addParameter(stringParameter);
208                 return;
209             }
210             if (isPropertyValueToscaFunction(parameter)) {
211                 buildToscaFunctionBasedOnPropertyValue((Map<String, Object>) parameter).ifPresent(toscaFunction -> {
212                     if (toscaFunction instanceof ToscaFunctionParameter) {
213                         toscaCustomFunction.addParameter((ToscaFunctionParameter) toscaFunction);
214                     }
215                 });
216                 return;
217             }
218             final var customYamlFunction = new CustomYamlFunction();
219             customYamlFunction.setYamlValue(parameter);
220             toscaCustomFunction.addParameter(customYamlFunction);
221         });
222         return Optional.of(toscaCustomFunction);
223     }
224
225     private Optional<ToscaFunction> handelCustomFunctionGetInputType(ToscaCustomFunction toscaCustomFunction, Object functionValueObj) {
226         if (!(functionValueObj instanceof String) && !(functionValueObj instanceof List)) {
227             return Optional.empty();
228         }
229         Map<String, Object> parameterMap = new HashMap<>();
230         parameterMap.put(ToscaFunctionType.GET_INPUT.getName(), functionValueObj);
231         buildToscaFunctionBasedOnPropertyValue(parameterMap).ifPresent(toscaFunction -> {
232             if (toscaFunction instanceof ToscaFunctionParameter) {
233                 toscaCustomFunction.addParameter((ToscaFunctionParameter) toscaFunction);
234             }
235         });
236         return Optional.of(toscaCustomFunction);
237     }
238
239     private ToscaFunctionType getCustomFunctionType(String name) {
240         List<Configuration.CustomToscaFunction> customFunctions =
241             ConfigurationManager.getConfigurationManager().getConfiguration().getDefaultCustomToscaFunctions();
242         if (CollectionUtils.isEmpty(customFunctions)) {
243             return ToscaFunctionType.CUSTOM;
244         }
245         Optional<Configuration.CustomToscaFunction> optionalFunc = customFunctions.stream().filter(func -> func.getName().equals(name)).findFirst();
246         if (optionalFunc.isEmpty()) {
247             return ToscaFunctionType.CUSTOM;
248         }
249         String type = optionalFunc.get().getType();
250         return ToscaFunctionType.findType(type).get();
251     }
252
253     /**
254      * Checks if the property value is a supported TOSCA function.
255      *
256      * @param propValueObj the value of a property
257      * @return {@code true} if the value is a supported TOSCA function, {@code false} otherwise
258      */
259     public boolean isPropertyValueToscaFunction(final Object propValueObj) {
260         if (propValueObj instanceof Map) {
261             final Map<String, Object> propValueMap = (Map<String, Object>) propValueObj;
262             if (propValueMap.keySet().size() > 1) {
263                 return false;
264             }
265             if (propValueMap.keySet().stream().anyMatch(keyValue -> keyValue.startsWith("$"))) {
266                 return true;
267             }
268
269             return Stream.of(ToscaFunctionType.GET_INPUT, ToscaFunctionType.GET_PROPERTY, ToscaFunctionType.GET_ATTRIBUTE, ToscaFunctionType.CONCAT)
270                 .anyMatch(type -> propValueMap.containsKey(type.getName()));
271         }
272         return false;
273     }
274
275     private Optional<ToscaFunction> handleConcatFunction(Map<String, Object> toscaFunctionPropertyValueMap, String functionType) {
276         final ToscaConcatFunction toscaConcatFunction = new ToscaConcatFunction();
277         final Object functionValueObj = toscaFunctionPropertyValueMap.get(functionType);
278         if (!(functionValueObj instanceof List)) {
279             return Optional.empty();
280         }
281         final List<Object> functionParameters = (List<Object>) functionValueObj;
282         if (functionParameters.size() < 2) {
283             return Optional.empty();
284         }
285         functionParameters.forEach(parameter -> {
286             if (parameter instanceof String) {
287                 final var stringParameter = new ToscaStringParameter();
288                 stringParameter.setValue((String) parameter);
289                 toscaConcatFunction.addParameter(stringParameter);
290                 return;
291             }
292             if (isPropertyValueToscaFunction(parameter)) {
293                 buildToscaFunctionBasedOnPropertyValue((Map<String, Object>) parameter).ifPresent(toscaFunction -> {
294                     if (toscaFunction instanceof ToscaFunctionParameter) {
295                         toscaConcatFunction.addParameter((ToscaFunctionParameter) toscaFunction);
296                     }
297                 });
298                 return;
299             }
300             final var customYamlFunction = new CustomYamlFunction();
301             customYamlFunction.setYamlValue(parameter);
302             toscaConcatFunction.addParameter(customYamlFunction);
303         });
304         return Optional.of(toscaConcatFunction);
305     }
306
307 }