Fix error handling valid yaml property values
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / tosca / PropertyConvertor.java
1 /*-
2  * ============LICENSE_START=======================================================
3  * SDC
4  * ================================================================================
5  * Copyright (C) 2017 AT&T Intellectual Property. All rights reserved.
6  * ================================================================================
7  * Licensed under the Apache License, Version 2.0 (the "License");
8  * you may not use this file except in compliance with the License.
9  * You may obtain a copy of the License at
10  *
11  *      http://www.apache.org/licenses/LICENSE-2.0
12  *
13  * Unless required by applicable law or agreed to in writing, software
14  * distributed under the License is distributed on an "AS IS" BASIS,
15  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
16  * See the License for the specific language governing permissions and
17  * limitations under the License.
18  * ============LICENSE_END=========================================================
19  */
20 package org.openecomp.sdc.be.tosca;
21
22 import com.google.gson.JsonElement;
23 import com.google.gson.JsonObject;
24 import com.google.gson.JsonParseException;
25 import com.google.gson.JsonParser;
26 import com.google.gson.stream.JsonReader;
27 import fj.data.Either;
28 import java.io.StringReader;
29 import java.util.HashMap;
30 import java.util.List;
31 import java.util.Map;
32 import java.util.Objects;
33 import java.util.function.Supplier;
34 import org.apache.commons.lang3.StringUtils;
35 import org.openecomp.sdc.be.datatypes.elements.PropertyDataDefinition;
36 import org.openecomp.sdc.be.datatypes.elements.SchemaDefinition;
37 import org.openecomp.sdc.be.model.Component;
38 import org.openecomp.sdc.be.model.DataTypeDefinition;
39 import org.openecomp.sdc.be.model.PropertyDefinition;
40 import org.openecomp.sdc.be.model.Resource;
41 import org.openecomp.sdc.be.model.tosca.ToscaPropertyType;
42 import org.openecomp.sdc.be.model.tosca.converters.DataTypePropertyConverter;
43 import org.openecomp.sdc.be.model.tosca.converters.ToscaMapValueConverter;
44 import org.openecomp.sdc.be.model.tosca.converters.ToscaValueBaseConverter;
45 import org.openecomp.sdc.be.model.tosca.converters.ToscaValueConverter;
46 import org.openecomp.sdc.be.tosca.model.ToscaNodeType;
47 import org.openecomp.sdc.be.tosca.model.ToscaProperty;
48 import org.openecomp.sdc.be.tosca.model.ToscaSchemaDefinition;
49 import org.openecomp.sdc.common.log.wrappers.Logger;
50 import org.openecomp.sdc.tosca.datatypes.ToscaFunctions;
51 import org.springframework.stereotype.Service;
52 import org.yaml.snakeyaml.Yaml;
53
54 @Service
55 public class PropertyConvertor {
56
57     private static final Logger log = Logger.getLogger(PropertyConvertor.class);
58
59     public Either<ToscaNodeType, ToscaError> convertProperties(Component component, ToscaNodeType toscaNodeType,
60                                                                Map<String, DataTypeDefinition> dataTypes) {
61         if (component instanceof Resource) {
62             Resource resource = (Resource) component;
63             List<PropertyDefinition> props = resource.getProperties();
64             if (props != null) {
65                 Map<String, ToscaProperty> properties = new HashMap<>();
66                 // take only the properties of this resource
67                 props.stream().filter(p -> p.getOwnerId() == null || p.getOwnerId().equals(component.getUniqueId())).forEach(property -> {
68                     properties.put(property.getName(), convertProperty(dataTypes, property, PropertyType.PROPERTY));
69                 });
70                 if (!properties.isEmpty()) {
71                     toscaNodeType.setProperties(properties);
72                 }
73             }
74         }
75         return Either.left(toscaNodeType);
76     }
77
78     public ToscaProperty convertProperty(Map<String, DataTypeDefinition> dataTypes, PropertyDefinition property, PropertyType propertyType) {
79         ToscaProperty prop = new ToscaProperty();
80         log.trace("try to convert property {} from type {} with default value [{}]", property.getName(), property.getType(),
81             property.getDefaultValue());
82         SchemaDefinition schema = property.getSchema();
83         if (schema != null && schema.getProperty() != null && schema.getProperty().getType() != null && !schema.getProperty().getType().isEmpty()) {
84             final ToscaSchemaDefinition toscaSchemaDefinition = new ToscaSchemaDefinition();
85             toscaSchemaDefinition.setType(schema.getProperty().getType());
86             toscaSchemaDefinition.setDescription(schema.getProperty().getDescription());
87             prop.setEntry_schema(toscaSchemaDefinition);
88         }
89         String defaultValue = property.getDefaultValue();
90         if (Objects.isNull(defaultValue)) {
91             defaultValue = property.getValue();
92         }
93         Object convertedObj = convertToToscaObject(property, defaultValue, dataTypes, false);
94         if (convertedObj != null) {
95             prop.setDefaultp(convertedObj);
96         }
97         prop.setType(property.getType());
98         prop.setDescription(property.getDescription());
99         prop.setRequired(property.isRequired());
100         if (propertyType.equals(PropertyType.CAPABILITY)) {
101             prop.setStatus(property.getStatus());
102         }
103         prop.setMetadata(property.getMetadata());
104         return prop;
105     }
106
107     public Object convertToToscaObject(PropertyDataDefinition property, String value, Map<String, DataTypeDefinition> dataTypes,
108                                        boolean preserveEmptyValue) {
109         String propertyType = property.getType();
110         String innerType = property.getSchemaType();
111         log.trace("try to convert propertyType {} , value [{}], innerType {}", propertyType, value, innerType);
112         if (StringUtils.isEmpty(value)) {
113             value = DataTypePropertyConverter.getInstance().getDataTypePropertiesDefaultValuesRec(propertyType, dataTypes);
114             if (StringUtils.isEmpty(value)) {
115                 return null;
116             }
117         }
118         try {
119             ToscaMapValueConverter mapConverterInst = ToscaMapValueConverter.getInstance();
120             ToscaValueConverter innerConverter = null;
121             boolean isScalar = true;
122             ToscaPropertyType type = ToscaPropertyType.isValidType(propertyType);
123             if (type == null) {
124                 log.trace("isn't prederfined type, get from all data types");
125                 DataTypeDefinition dataTypeDefinition = dataTypes.get(propertyType);
126                 if (innerType == null) {
127                     innerType = propertyType;
128                 }
129                 if ((type = mapConverterInst.isScalarType(dataTypeDefinition)) != null) {
130                     log.trace("This is scalar type. get suitable converter for type {}", type);
131                     innerConverter = type.getValueConverter();
132                 } else {
133                     isScalar = false;
134                 }
135             } else {
136                 ToscaPropertyType typeIfScalar = ToscaPropertyType.getTypeIfScalar(type.getType());
137                 if (typeIfScalar == null) {
138                     isScalar = false;
139                 }
140                 innerConverter = type.getValueConverter();
141                 if (ToscaPropertyType.STRING == type && valueStartsWithNonJsonChar(value)) {
142                     return innerConverter.convertToToscaValue(value, innerType, dataTypes);
143                 }
144             }
145             JsonElement jsonElement = null;
146             StringReader reader = new StringReader(value);
147             JsonReader jsonReader = new JsonReader(reader);
148             jsonReader.setLenient(true);
149             jsonElement = JsonParser.parseReader(jsonReader);
150             if (value.equals("")) {
151                 return value;
152             }
153             if (jsonElement.isJsonPrimitive() && isScalar) {
154                 log.trace("It's well defined type. convert it");
155                 ToscaValueConverter converter = type.getValueConverter();
156                 return converter.convertToToscaValue(value, innerType, dataTypes);
157             }
158             log.trace("It's data type or inputs in primitive type. convert as map");
159             if (jsonElement.isJsonObject()) {
160                 JsonObject jsonObj = jsonElement.getAsJsonObject();
161                 // check if value is a get_input function
162                 if (jsonObj.entrySet().size() == 1 && jsonObj.has(ToscaFunctions.GET_INPUT.getFunctionName())) {
163                     Object obj = mapConverterInst.handleComplexJsonValue(jsonElement);
164                     log.debug("It's get_input function. obj={}", obj);
165                     return obj;
166                 }
167             }
168             Object convertedValue;
169             if (innerConverter != null && (ToscaPropertyType.MAP == type || ToscaPropertyType.LIST == type)) {
170                 convertedValue = innerConverter.convertToToscaValue(value, innerType, dataTypes);
171             } else if (isScalar) {
172                 // complex json for scalar type
173                 convertedValue = mapConverterInst.handleComplexJsonValue(jsonElement);
174             } else if (innerConverter != null) {
175                 convertedValue = innerConverter.convertToToscaValue(value, innerType, dataTypes);
176             } else {
177                 convertedValue = mapConverterInst
178                     .convertDataTypeToToscaObject(innerType, dataTypes, innerConverter, isScalar, jsonElement, preserveEmptyValue);
179             }
180             return convertedValue;
181         
182         } catch (JsonParseException e) {
183             log.trace("{} not parsable as JSON. Convert as YAML instead", value);
184             return  new Yaml().load(value);
185         } catch (Exception e) {
186             log.debug("convertToToscaValue failed to parse json value :", e);
187             return null;
188         }
189     }
190
191     private boolean valueStartsWithNonJsonChar(String value) {
192         return value.startsWith("/") || value.startsWith(":");
193     }
194
195     public void convertAndAddValue(Map<String, DataTypeDefinition> dataTypes, Map<String, Object> props, PropertyDataDefinition prop,
196                                    Supplier<String> supplier) {
197         Object convertedValue = convertValue(dataTypes, prop, supplier);
198         if (!ToscaValueBaseConverter.isEmptyObjectValue(convertedValue)) {
199             props.put(prop.getName(), convertedValue);
200         }
201     }
202
203     private <T extends PropertyDataDefinition> Object convertValue(Map<String, DataTypeDefinition> dataTypes, T input, Supplier<String> supplier) {
204         return convertToToscaObject(input, supplier.get(), dataTypes, false);
205     }
206
207     public enum PropertyType {CAPABILITY, INPUT, PROPERTY}
208 }