re base code
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / components / impl / PropertyBusinessLogic.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
21 package org.openecomp.sdc.be.components.impl;
22
23 import com.google.gson.JsonElement;
24 import fj.data.Either;
25 import org.apache.commons.collections.CollectionUtils;
26 import org.apache.commons.lang3.tuple.ImmutablePair;
27 import org.openecomp.sdc.be.config.BeEcompErrorManager;
28 import org.openecomp.sdc.be.dao.api.ActionStatus;
29 import org.openecomp.sdc.be.dao.titan.TitanOperationStatus;
30 import org.openecomp.sdc.be.datatypes.elements.PropertyDataDefinition;
31 import org.openecomp.sdc.be.datatypes.elements.SchemaDefinition;
32 import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum;
33 import org.openecomp.sdc.be.impl.WebAppContextWrapper;
34 import org.openecomp.sdc.be.model.DataTypeDefinition;
35 import org.openecomp.sdc.be.model.IComplexDefaultValue;
36 import org.openecomp.sdc.be.model.PropertyDefinition;
37 import org.openecomp.sdc.be.model.Resource;
38 import org.openecomp.sdc.be.model.operations.api.IElementOperation;
39 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
40 import org.openecomp.sdc.be.model.operations.utils.ComponentValidationUtils;
41 import org.openecomp.sdc.be.model.tosca.ToscaPropertyType;
42 import org.openecomp.sdc.be.model.tosca.converters.PropertyValueConverter;
43 import org.openecomp.sdc.be.model.tosca.validators.PropertyTypeValidator;
44 import org.openecomp.sdc.be.resources.data.EntryData;
45 import org.openecomp.sdc.common.api.Constants;
46 import org.openecomp.sdc.common.log.wrappers.Logger;
47 import org.openecomp.sdc.exception.ResponseFormat;
48 import org.springframework.stereotype.Component;
49 import org.springframework.web.context.WebApplicationContext;
50
51 import javax.servlet.ServletContext;
52 import java.util.HashMap;
53 import java.util.List;
54 import java.util.Map;
55 import java.util.Map.Entry;
56 import java.util.function.Supplier;
57
58 @Component("propertyBusinessLogic")
59 public class PropertyBusinessLogic extends BaseBusinessLogic {
60
61     private static final String CREATE_PROPERTY = "CreateProperty";
62
63     private static final Logger log = Logger.getLogger(PropertyBusinessLogic.class);
64
65     private static final String EMPTY_VALUE = null;
66
67     protected static IElementOperation getElementDao(Class<IElementOperation> class1, ServletContext context) {
68         WebAppContextWrapper webApplicationContextWrapper = (WebAppContextWrapper) context.getAttribute(Constants.WEB_APPLICATION_CONTEXT_WRAPPER_ATTR);
69
70         WebApplicationContext webApplicationContext = webApplicationContextWrapper.getWebAppContext(context);
71
72         return webApplicationContext.getBean(class1);
73     }
74
75     public Either<Map<String, DataTypeDefinition>, ResponseFormat> getAllDataTypes() {
76         return getAllDataTypes(applicationDataTypeCache);
77     }
78
79     /**
80      * Create new property on resource in graph
81      *
82      * @param resourceId
83      * @param propertyName
84      * @param newPropertyDefinition
85      * @param userId
86      * @return either properties or response format
87      */
88     public Either<EntryData<String, PropertyDefinition>, ResponseFormat> createProperty(String resourceId, String propertyName, PropertyDefinition newPropertyDefinition, String userId) {
89
90         Either<EntryData<String, PropertyDefinition>, ResponseFormat> result = null;
91
92         validateUserExists(userId, "create Property", false);
93
94         StorageOperationStatus lockResult = graphLockOperation.lockComponent(resourceId, NodeTypeEnum.Resource);
95         if (!lockResult.equals(StorageOperationStatus.OK)) {
96             BeEcompErrorManager.getInstance().logBeFailedLockObjectError(CREATE_PROPERTY, NodeTypeEnum.Resource.name().toLowerCase(), resourceId);
97             log.info("Failed to lock component {}. Error - {}", resourceId, lockResult);
98             result = Either.right(componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR));
99             return result;
100         }
101
102         try {
103             // Get the resource from DB
104             Either<Resource, StorageOperationStatus> status = toscaOperationFacade.getToscaElement(resourceId);
105             if (status.isRight()) {
106                 result = Either.right(componentsUtils.getResponseFormat(ActionStatus.RESOURCE_NOT_FOUND, ""));
107                 return result;
108             }
109             Resource resource = status.left().value();
110
111             // verify that resource is checked-out and the user is the last
112             // updater
113             if (!ComponentValidationUtils.canWorkOnResource(resource, userId)) {
114                 result = Either.right(componentsUtils.getResponseFormat(ActionStatus.RESTRICTED_OPERATION));
115                 return result;
116             }
117
118             // verify property not exist in resource
119             List<PropertyDefinition> resourceProperties = resource.getProperties();
120
121             if (resourceProperties != null && isPropertyExist(resourceProperties, resourceId, propertyName, newPropertyDefinition.getType())) {
122                 result = Either.right(componentsUtils.getResponseFormat(ActionStatus.PROPERTY_ALREADY_EXIST, propertyName));
123                 return result;
124             }
125
126             Either<Map<String, DataTypeDefinition>, ResponseFormat> allDataTypes = getAllDataTypes(applicationDataTypeCache);
127             if (allDataTypes.isRight()) {
128                 result = Either.right(allDataTypes.right().value());
129                 return result;
130             }
131
132             Map<String, DataTypeDefinition> dataTypes = allDataTypes.left().value();
133
134             // validate property default values
135             Either<Boolean, ResponseFormat> defaultValuesValidation = validatePropertyDefaultValue(newPropertyDefinition, dataTypes);
136             if (defaultValuesValidation.isRight()) {
137                 result = Either.right(defaultValuesValidation.right().value());
138                 return result;
139             }
140             convertProperty(newPropertyDefinition, allDataTypes);
141
142
143             // add the new property to resource on graph
144             // need to get StorageOpaerationStatus and convert to ActionStatus
145             // from componentsUtils
146             Either<PropertyDefinition, StorageOperationStatus> either = toscaOperationFacade.addPropertyToResource(propertyName, newPropertyDefinition, resource);
147             if (either.isRight()) {
148                 result = Either.right(componentsUtils.getResponseFormat(componentsUtils.convertFromStorageResponse(either.right().value()), resource.getName()));
149                 return result;
150             }
151
152             PropertyDefinition createdPropertyDefinition = either.left().value();
153             EntryData<String, PropertyDefinition> property = new EntryData<>(propertyName, createdPropertyDefinition);
154             result = Either.left(property);
155             return result;
156
157         } finally {
158             commitOrRollback(result);
159             // unlock component
160             graphLockOperation.unlockComponent(resourceId, NodeTypeEnum.Resource);
161         }
162
163     }
164
165     private void convertProperty(PropertyDefinition newPropertyDefinition, Either<Map<String, DataTypeDefinition>, ResponseFormat> allDataTypes) {
166         ToscaPropertyType type = getType(newPropertyDefinition.getType());
167         if (type != null) {
168             String innerType = null;
169             SchemaDefinition schema = newPropertyDefinition.getSchema();
170             if (schema != null && schema.getProperty() != null) {
171                 innerType = schema.getProperty().getType();
172             }
173             if (newPropertyDefinition.getDefaultValue() != null) {
174                 newPropertyDefinition.setDefaultValue(
175                         type.getConverter().convert(
176                                 newPropertyDefinition.getDefaultValue(), innerType, allDataTypes.left().value()));
177             }
178         }
179     }
180
181     /**
182      * Get property of resource
183      *
184      * @param resourceId
185      * @param propertyId
186      * @param userId
187      * @return either properties or response format
188      */
189     public Either<Entry<String, PropertyDefinition>, ResponseFormat> getProperty(String resourceId, String propertyId, String userId) {
190
191         validateUserExists(userId, "create Component Instance", false);
192         // Get the resource from DB
193         Either<Resource, StorageOperationStatus> status =  toscaOperationFacade.getToscaElement(resourceId);
194         if (status.isRight()) {
195             return Either.right(componentsUtils.getResponseFormat(ActionStatus.RESOURCE_NOT_FOUND, ""));
196         }
197         Resource resource = status.left().value();
198
199         // verify property exist in resource
200         List<PropertyDefinition> properties = resource.getProperties();
201         if (properties == null) {
202             return Either.right(componentsUtils.getResponseFormat(ActionStatus.PROPERTY_NOT_FOUND, ""));
203         }
204         for (PropertyDefinition property : properties) {
205             if (property.getUniqueId().equals(propertyId) ) {
206                 Map<String, PropertyDefinition> propMap = new HashMap<>();
207                 propMap.put(property.getName(), property);
208                 return Either.left(propMap.entrySet().iterator().next());
209             }
210         }
211         return Either.right(componentsUtils.getResponseFormat(ActionStatus.PROPERTY_NOT_FOUND, ""));
212     }
213
214     /**
215      * delete property of resource from graph
216      *
217      * @param resourceId
218      * @param propertyId
219      * @param userId
220      * @return either properties or response format
221      */
222     public Either<Entry<String, PropertyDefinition>, ResponseFormat> deleteProperty(String resourceId, String propertyId, String userId) {
223
224         Either<Entry<String, PropertyDefinition>, ResponseFormat> result = null;
225
226         validateUserExists(userId, "delete Property", false);
227
228         StorageOperationStatus lockResult = graphLockOperation.lockComponent(resourceId, NodeTypeEnum.Resource);
229         if (!lockResult.equals(StorageOperationStatus.OK)) {
230             BeEcompErrorManager.getInstance().logBeFailedLockObjectError(CREATE_PROPERTY, NodeTypeEnum.Resource.name().toLowerCase(), resourceId);
231             result = Either.right(componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR));
232             return result;
233         }
234
235         try {
236
237             // Get the resource from DB
238             Either<Resource, StorageOperationStatus> getResourceRes = toscaOperationFacade.getToscaElement(resourceId);
239             if (getResourceRes.isRight()) {
240                 result = Either.right(componentsUtils.getResponseFormat(ActionStatus.RESOURCE_NOT_FOUND, ""));
241                 return result;
242             }
243             Resource resource = getResourceRes.left().value();
244
245             // verify that resource is checked-out and the user is the last
246             // updater
247             if (!ComponentValidationUtils.canWorkOnResource(resource, userId)) {
248                 result = Either.right(componentsUtils.getResponseFormat(ActionStatus.RESTRICTED_OPERATION));
249                 return result;
250             }
251
252             // verify property exist in resource
253             Either<Entry<String, PropertyDefinition>, ResponseFormat> statusGetProperty = getProperty(resourceId, propertyId, userId);
254             if (statusGetProperty.isRight()) {
255                 result = Either.right(statusGetProperty.right().value());
256                 return result;
257             }
258
259             StorageOperationStatus status = toscaOperationFacade.deletePropertyOfResource(resource, statusGetProperty.left().value().getKey());
260             if (status != StorageOperationStatus.OK) {
261                 result = Either.right(componentsUtils.getResponseFormat(componentsUtils.convertFromStorageResponse(status), resource.getName()));
262                 return result;
263             }
264             result = Either.left(statusGetProperty.left().value());
265             return result;
266
267         } finally {
268             commitOrRollback(result);
269             // unlock component
270             graphLockOperation.unlockComponent(resourceId, NodeTypeEnum.Resource);
271         }
272     }
273
274     /**
275      * update property
276      *
277      * @param resourceId
278      * @param propertyId
279      * @param newPropertyDefinition
280      * @param userId
281      * @return either properties or response format
282      */
283     public Either<EntryData<String, PropertyDefinition>, ResponseFormat> updateProperty(String resourceId, String propertyId, PropertyDefinition newPropertyDefinition, String userId) {
284
285         Either<EntryData<String, PropertyDefinition>, ResponseFormat> result = null;
286
287         Either<Resource, StorageOperationStatus> status = toscaOperationFacade.getToscaElement(resourceId);
288         if (status.isRight()) {
289             return Either.right(componentsUtils.getResponseFormat(ActionStatus.RESOURCE_NOT_FOUND, ""));
290         }
291         Resource resource = status.left().value();
292
293         if (!ComponentValidationUtils.canWorkOnResource(resource, userId)) {
294             return Either.right(componentsUtils.getResponseFormat(ActionStatus.RESTRICTED_OPERATION));
295         }
296
297         StorageOperationStatus lockResult = graphLockOperation.lockComponent(resourceId, NodeTypeEnum.Resource);
298         if (!lockResult.equals(StorageOperationStatus.OK)) {
299             BeEcompErrorManager.getInstance().logBeFailedLockObjectError(CREATE_PROPERTY, NodeTypeEnum.Resource.name().toLowerCase(), resourceId);
300             result = Either.right(componentsUtils.getResponseFormat(ActionStatus.GENERAL_ERROR));
301             return result;
302         }
303
304         try {
305             Either<Entry<String, PropertyDefinition>, ResponseFormat> statusGetProperty = getProperty(resourceId, propertyId, userId);
306             if (statusGetProperty.isRight()) {
307                 result = Either.right(statusGetProperty.right().value());
308                 return result;
309             }
310             String propertyName = statusGetProperty.left().value().getKey();
311
312             Either<Map<String, DataTypeDefinition>, ResponseFormat> allDataTypes = getAllDataTypes(applicationDataTypeCache);
313             if (allDataTypes.isRight()) {
314                 result = Either.right(allDataTypes.right().value());
315                 return result;
316             }
317             Map<String, DataTypeDefinition> dataTypes = allDataTypes.left().value();
318
319             Either<Boolean, ResponseFormat> defaultValuesValidation = validatePropertyDefaultValue(newPropertyDefinition, dataTypes);
320             if (defaultValuesValidation.isRight()) {
321                 result = Either.right(defaultValuesValidation.right().value());
322                 return result;
323             }
324
325             Either<PropertyDefinition, StorageOperationStatus> either = handleProperty(newPropertyDefinition, dataTypes);
326             if (either.isRight()) {
327                 log.debug("Problem while updating property with id {}. Reason - {}", propertyId, either.right().value());
328                 result = Either.right(componentsUtils.getResponseFormatByResource(componentsUtils.convertFromStorageResponse(either.right().value()), resource.getName()));
329                 return result;
330             }
331
332
333             either = toscaOperationFacade.updatePropertyOfResource(resource, newPropertyDefinition);
334             if (either.isRight()) {
335                 result = Either.right(componentsUtils.getResponseFormatByResource(componentsUtils.convertFromStorageResponse(either.right().value()), resource.getName()));
336                 return result;
337             }
338
339             EntryData<String, PropertyDefinition> property = new EntryData<>(propertyName, either.left().value());
340             result = Either.left(property);
341             return result;
342
343         } finally {
344             commitOrRollback(result);
345             graphLockOperation.unlockComponent(resourceId, NodeTypeEnum.Resource);
346         }
347
348     }
349
350     private boolean isPropertyExist(List<PropertyDefinition> properties, String resourceUid, String propertyName, String propertyType) {
351         boolean result = false;
352         if (!CollectionUtils.isEmpty(properties)) {
353             for (PropertyDefinition propertyDefinition : properties) {
354
355                 if ( propertyDefinition.getName().equals(propertyName) &&
356                         (propertyDefinition.getParentUniqueId().equals(resourceUid) || !propertyDefinition.getType().equals(propertyType)) ) {
357                     result = true;
358                     break;
359                 }
360             }
361         }
362         return result;
363     }
364
365     private Either<PropertyDefinition, StorageOperationStatus> handleProperty(PropertyDefinition newPropertyDefinition, Map<String, DataTypeDefinition> dataTypes) {
366
367         StorageOperationStatus validateAndUpdateProperty = validateAndUpdateProperty(newPropertyDefinition, dataTypes);
368         if (validateAndUpdateProperty != StorageOperationStatus.OK) {
369             return Either.right(validateAndUpdateProperty);
370         }
371
372         return Either.left(newPropertyDefinition);
373     }
374
375     private StorageOperationStatus validateAndUpdateProperty(IComplexDefaultValue propertyDefinition, Map<String, DataTypeDefinition> dataTypes) {
376
377         log.trace("Going to validate property type and value. {}", propertyDefinition);
378
379         String propertyType = propertyDefinition.getType();
380         String value = propertyDefinition.getDefaultValue();
381
382         ToscaPropertyType type = getType(propertyType);
383
384         if (type == null) {
385             DataTypeDefinition dataTypeDefinition = dataTypes.get(propertyType);
386             if (dataTypeDefinition == null) {
387                 log.debug("The type {} of property cannot be found.", propertyType);
388                 return StorageOperationStatus.INVALID_TYPE;
389             }
390             return validateAndUpdateComplexValue(propertyDefinition, propertyType, value, dataTypeDefinition, dataTypes);
391         }
392         String innerType;
393
394         Either<String, TitanOperationStatus> checkInnerType = getInnerType(type, propertyDefinition::getSchema);
395         if (checkInnerType.isRight()) {
396             return StorageOperationStatus.INVALID_TYPE;
397         }
398         innerType = checkInnerType.left().value();
399
400         log.trace("After validating property type {}", propertyType);
401
402         boolean isValidProperty = isValidValue(type, value, innerType, dataTypes);
403         if (!isValidProperty) {
404             log.info("The value {} of property from type {} is invalid", value, type);
405             return StorageOperationStatus.INVALID_VALUE;
406         }
407
408         PropertyValueConverter converter = type.getConverter();
409
410         if (isEmptyValue(value)) {
411             log.debug("Default value was not sent for property {}. Set default value to {}", propertyDefinition.getName(), EMPTY_VALUE);
412             propertyDefinition.setDefaultValue(EMPTY_VALUE);
413         } else if (!isEmptyValue(value)) {
414             String convertedValue = converter.convert(value, innerType, dataTypes);
415             propertyDefinition.setDefaultValue(convertedValue);
416         }
417         return StorageOperationStatus.OK;
418     }
419
420     private StorageOperationStatus validateAndUpdateComplexValue(IComplexDefaultValue propertyDefinition, String propertyType,
421                                                                  String value, DataTypeDefinition dataTypeDefinition, Map<String, DataTypeDefinition> dataTypes) {
422
423         ImmutablePair<JsonElement, Boolean> validateResult = dataTypeValidatorConverter.validateAndUpdate(value, dataTypeDefinition, dataTypes);
424
425         if (validateResult.right) {
426             log.debug("The value {} of property from type {} is invalid", propertyType, propertyType);
427             return StorageOperationStatus.INVALID_VALUE;
428         }
429
430         JsonElement jsonElement = validateResult.left;
431
432         log.trace("Going to update value in property definition {} {}" , propertyDefinition.getName() , jsonElement);
433
434         updateValue(propertyDefinition, jsonElement);
435
436         return StorageOperationStatus.OK;
437     }
438
439     private void updateValue(IComplexDefaultValue propertyDefinition, JsonElement jsonElement) {
440
441         propertyDefinition.setDefaultValue(getValueFromJsonElement(jsonElement));
442
443     }
444
445     @Override
446     protected String getValueFromJsonElement(JsonElement jsonElement) {
447         if (jsonElement == null || jsonElement.isJsonNull()) {
448             return EMPTY_VALUE;
449         }
450         if(jsonElement.toString().isEmpty()){
451             return "";
452         }
453         return jsonElement.toString();
454     }
455
456     private Either<String, TitanOperationStatus> getInnerType(ToscaPropertyType type, Supplier<SchemaDefinition> schemeGen) {
457         String innerType = null;
458         if (type == ToscaPropertyType.LIST || type == ToscaPropertyType.MAP) {
459
460             SchemaDefinition def = schemeGen.get();
461             if (def == null) {
462                 log.debug("Schema doesn't exists for property of type {}", type);
463                 return Either.right(TitanOperationStatus.ILLEGAL_ARGUMENT);
464             }
465             PropertyDataDefinition propDef = def.getProperty();
466             if (propDef == null) {
467                 log.debug("Property in Schema Definition inside property of type {} doesn't exist", type);
468                 return Either.right(TitanOperationStatus.ILLEGAL_ARGUMENT);
469             }
470             innerType = propDef.getType();
471         }
472         return Either.left(innerType);
473     }
474
475     @Override
476     protected boolean isValidValue(ToscaPropertyType type, String value, String innerType, Map<String, DataTypeDefinition> dataTypes) {
477         if (isEmptyValue(value)) {
478             return true;
479         }
480         PropertyTypeValidator validator = type.getValidator();
481         return validator.isValid(value, innerType, dataTypes);
482     }
483
484     @Override
485     public boolean isEmptyValue(String value) {
486         return value == null;
487     }
488 }