Improve handling 'empty'/null string in Service fields
[sdc.git] / catalog-be / src / main / java / org / openecomp / sdc / be / components / lifecycle / LifecycleBusinessLogic.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  * Modifications copyright (c) 2019 Nokia
20  * ================================================================================
21  */
22 package org.openecomp.sdc.be.components.lifecycle;
23
24 import com.google.common.annotations.VisibleForTesting;
25 import fj.data.Either;
26 import java.util.HashMap;
27 import java.util.Map;
28 import javax.annotation.PostConstruct;
29 import org.apache.commons.lang3.StringUtils;
30 import org.openecomp.sdc.be.catalog.enums.ChangeTypeEnum;
31 import org.openecomp.sdc.be.components.impl.ComponentBusinessLogic;
32 import org.openecomp.sdc.be.components.impl.ProductBusinessLogic;
33 import org.openecomp.sdc.be.components.impl.ResourceBusinessLogic;
34 import org.openecomp.sdc.be.components.impl.ServiceBusinessLogic;
35 import org.openecomp.sdc.be.components.impl.exceptions.ByActionStatusComponentException;
36 import org.openecomp.sdc.be.components.impl.exceptions.ByResponseFormatComponentException;
37 import org.openecomp.sdc.be.components.impl.exceptions.ComponentException;
38 import org.openecomp.sdc.be.components.impl.version.VesionUpdateHandler;
39 import org.openecomp.sdc.be.components.lifecycle.LifecycleChangeInfoWithAction.LifecycleChanceActionEnum;
40 import org.openecomp.sdc.be.dao.api.ActionStatus;
41 import org.openecomp.sdc.be.dao.janusgraph.JanusGraphDao;
42 import org.openecomp.sdc.be.datatypes.enums.ComponentTypeEnum;
43 import org.openecomp.sdc.be.datatypes.enums.NodeTypeEnum;
44 import org.openecomp.sdc.be.facade.operations.CatalogOperation;
45 import org.openecomp.sdc.be.impl.ComponentsUtils;
46 import org.openecomp.sdc.be.model.Component;
47 import org.openecomp.sdc.be.model.LifeCycleTransitionEnum;
48 import org.openecomp.sdc.be.model.LifecycleStateEnum;
49 import org.openecomp.sdc.be.model.Resource;
50 import org.openecomp.sdc.be.model.User;
51 import org.openecomp.sdc.be.model.jsonjanusgraph.datamodel.ToscaElement;
52 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.NodeTemplateOperation;
53 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.ToscaElementLifecycleOperation;
54 import org.openecomp.sdc.be.model.jsonjanusgraph.operations.ToscaOperationFacade;
55 import org.openecomp.sdc.be.model.jsonjanusgraph.utils.ModelConverter;
56 import org.openecomp.sdc.be.model.operations.api.IGraphLockOperation;
57 import org.openecomp.sdc.be.model.operations.api.StorageOperationStatus;
58 import org.openecomp.sdc.be.resources.data.auditing.AuditingActionEnum;
59 import org.openecomp.sdc.be.resources.data.auditing.model.ResourceCommonInfo;
60 import org.openecomp.sdc.be.resources.data.auditing.model.ResourceVersionInfo;
61 import org.openecomp.sdc.common.api.Constants;
62 import org.openecomp.sdc.common.log.wrappers.Logger;
63 import org.openecomp.sdc.common.util.ValidationUtils;
64 import org.openecomp.sdc.exception.ResponseFormat;
65 import org.springframework.beans.factory.annotation.Autowired;
66 import org.springframework.context.annotation.Lazy;
67
68 @org.springframework.stereotype.Component("lifecycleBusinessLogic")
69 public class LifecycleBusinessLogic {
70
71     private static final String COMMENT = "comment";
72     private static final Logger log = Logger.getLogger(LifecycleBusinessLogic.class);
73     @Autowired
74     ToscaOperationFacade toscaOperationFacade;
75     @Autowired
76     NodeTemplateOperation nodeTemplateOperation;
77     @Autowired
78     CatalogOperation catalogOperations;
79     @Autowired
80     VesionUpdateHandler groupUpdateHandler;
81     @Autowired
82     private IGraphLockOperation graphLockOperation = null;
83     @Autowired
84     private JanusGraphDao janusGraphDao;
85     @javax.annotation.Resource
86     private ComponentsUtils componentUtils;
87     @javax.annotation.Resource
88     private ToscaElementLifecycleOperation lifecycleOperation;
89     @Autowired
90     @Lazy
91     private ServiceBusinessLogic serviceBusinessLogic;
92     @Autowired
93     @Lazy
94     private ResourceBusinessLogic resourceBusinessLogic;
95     @Autowired
96     @Lazy
97     private ProductBusinessLogic productBusinessLogic;
98     private Map<String, LifeCycleTransition> stateTransitions;
99
100     @PostConstruct
101     public void init() {
102         initStateOperations();
103     }
104
105     private void initStateOperations() {
106         stateTransitions = new HashMap<>();
107         LifeCycleTransition checkoutOp = new CheckoutTransition(componentUtils, lifecycleOperation, toscaOperationFacade, janusGraphDao);
108         stateTransitions.put(checkoutOp.getName().name(), checkoutOp);
109         UndoCheckoutTransition undoCheckoutOp = new UndoCheckoutTransition(componentUtils, lifecycleOperation, toscaOperationFacade, janusGraphDao);
110         undoCheckoutOp.setCatalogOperations(catalogOperations);
111         stateTransitions.put(undoCheckoutOp.getName().name(), undoCheckoutOp);
112         LifeCycleTransition checkinOp = new CheckinTransition(componentUtils, lifecycleOperation, toscaOperationFacade, janusGraphDao,
113             groupUpdateHandler);
114         stateTransitions.put(checkinOp.getName().name(), checkinOp);
115         CertificationChangeTransition successCertification = new CertificationChangeTransition(serviceBusinessLogic, LifeCycleTransitionEnum.CERTIFY,
116             componentUtils, lifecycleOperation, toscaOperationFacade, janusGraphDao);
117         successCertification.setNodeTemplateOperation(nodeTemplateOperation);
118         stateTransitions.put(successCertification.getName().name(), successCertification);
119     }
120
121     @VisibleForTesting
122     Map<String, LifeCycleTransition> getStartTransition() {
123         return stateTransitions;
124     }
125
126     // TODO: rhalili - should use changeComponentState when possible
127     public Either<Resource, ResponseFormat> changeState(String resourceId, User modifier, LifeCycleTransitionEnum transitionEnum,
128                                                         LifecycleChangeInfoWithAction changeInfo, boolean inTransaction, boolean needLock) {
129         return changeComponentState(ComponentTypeEnum.RESOURCE, resourceId, modifier, transitionEnum, changeInfo, inTransaction, needLock);
130     }
131
132     public <T extends Component> Either<T, ResponseFormat> changeComponentState(ComponentTypeEnum componentType, String componentId, User modifier,
133                                                                                 LifeCycleTransitionEnum transitionEnum,
134                                                                                 LifecycleChangeInfoWithAction changeInfo, boolean inTransaction,
135                                                                                 boolean needLock) {
136         LifeCycleTransition lifeCycleTransition = stateTransitions.get(transitionEnum.name());
137         if (lifeCycleTransition == null) {
138             log.debug("state operation is not valid. operations allowed are: {}", LifeCycleTransitionEnum.valuesAsString());
139             ResponseFormat error = componentUtils.getInvalidContentErrorAndAudit(modifier, componentId, AuditingActionEnum.CHECKOUT_RESOURCE);
140             return Either.right(error);
141         }
142         log.debug("get resource from graph");
143         ResponseFormat errorResponse;
144         Either<T, ResponseFormat> eitherResourceResponse = getComponentForChange(componentType, componentId, modifier, lifeCycleTransition,
145             changeInfo);
146         if (eitherResourceResponse.isRight()) {
147             return eitherResourceResponse;
148         }
149         T component = eitherResourceResponse.left().value();
150         String resourceCurrVersion = component.getVersion();
151         LifecycleStateEnum resourceCurrState = component.getLifecycleState();
152         // lock resource
153         if (!inTransaction && needLock) {
154             log.debug("lock component {}", componentId);
155             try {
156                 lockComponent(componentType, component);
157             } catch (ComponentException e) {
158                 errorResponse = e.getResponseFormat();
159                 componentUtils.auditComponent(errorResponse, modifier, component, lifeCycleTransition.getAuditingAction(),
160                     new ResourceCommonInfo(componentType.getValue()),
161                     ResourceVersionInfo.newBuilder().state(resourceCurrState.name()).version(resourceCurrVersion).build());
162                 log.error("lock component {} failed", componentId);
163                 return Either.right(errorResponse);
164             }
165             log.debug("after lock component {}", componentId);
166         }
167         try {
168             Either<String, ResponseFormat> commentValidationResult = validateComment(changeInfo, transitionEnum);
169             if (commentValidationResult.isRight()) {
170                 errorResponse = commentValidationResult.right().value();
171                 componentUtils.auditComponent(errorResponse, modifier, component, lifeCycleTransition.getAuditingAction(),
172                     new ResourceCommonInfo(componentType.getValue()),
173                     ResourceVersionInfo.newBuilder().state(resourceCurrState.name()).version(resourceCurrVersion).build(),
174                     changeInfo.getUserRemarks());
175                 return Either.right(errorResponse);
176             }
177             changeInfo.setUserRemarks(commentValidationResult.left().value());
178             log.debug("after validate component");
179             Either<Boolean, ResponseFormat> validateHighestVersion = validateHighestVersion(modifier, lifeCycleTransition, component,
180                 resourceCurrVersion, componentType);
181             if (validateHighestVersion.isRight()) {
182                 return Either.right(validateHighestVersion.right().value());
183             }
184             log.debug("after validate Highest Version");
185             final T oldComponent = component;
186             Either<T, ResponseFormat> checkedInComponentEither = checkInBeforeCertifyIfNeeded(componentType, modifier, transitionEnum, changeInfo,
187                 inTransaction, component);
188             if (checkedInComponentEither.isRight()) {
189                 return Either.right(checkedInComponentEither.right().value());
190             }
191             component = checkedInComponentEither.left().value();
192             return changeState(component, lifeCycleTransition, componentType, modifier, changeInfo, inTransaction).left()
193                 .bind(c -> updateCatalog(c, oldComponent, ChangeTypeEnum.LIFECYCLE));
194         } finally {
195             component.setUniqueId(componentId);
196             if (!inTransaction && needLock) {
197                 log.info("unlock component {}", componentId);
198                 NodeTypeEnum nodeType = componentType.getNodeType();
199                 log.info("During change state, another component {} has been created/updated", componentId);
200                 graphLockOperation.unlockComponent(componentId, nodeType);
201             }
202         }
203     }
204
205     private <T extends Component> Either<T, ResponseFormat> updateCatalog(T component, T oldComponent, ChangeTypeEnum changeStatus) {
206         log.debug("updateCatalog start");
207         T result = component == null ? oldComponent : component;
208         if (component != null) {
209             ActionStatus status = catalogOperations.updateCatalog(changeStatus, component);
210             if (status != ActionStatus.OK) {
211                 return Either.right(componentUtils.getResponseFormat(status));
212             }
213         }
214         return Either.left(result);
215     }
216
217     private <T extends Component> Either<T, ResponseFormat> checkInBeforeCertifyIfNeeded(ComponentTypeEnum componentType, User modifier,
218                                                                                          LifeCycleTransitionEnum transitionEnum,
219                                                                                          LifecycleChangeInfoWithAction changeInfo,
220                                                                                          boolean inTransaction, T component) {
221         LifecycleStateEnum oldState = component.getLifecycleState();
222         log.debug("Certification request for resource {} ", component.getUniqueId());
223         if (oldState == LifecycleStateEnum.NOT_CERTIFIED_CHECKOUT && transitionEnum == LifeCycleTransitionEnum.CERTIFY) {
224             log.debug("Resource {} is in Checkout state perform checkin", component.getUniqueId());
225             Either<T, ResponseFormat> actionResponse = changeState(component, stateTransitions.get(LifeCycleTransitionEnum.CHECKIN.name()),
226                 componentType, modifier, changeInfo, inTransaction);
227             if (actionResponse.isRight()) {
228                 log.debug("Failed to check in Resource {} error {}", component.getUniqueId(), actionResponse.right().value());
229             }
230             return actionResponse;
231         }
232         return Either.left(component);
233     }
234
235     private <T extends Component> Either<T, ResponseFormat> changeState(T component, LifeCycleTransition lifeCycleTransition,
236                                                                         ComponentTypeEnum componentType, User modifier,
237                                                                         LifecycleChangeInfoWithAction changeInfo, boolean inTransaction) {
238         ResponseFormat errorResponse;
239         LifecycleStateEnum oldState = component.getLifecycleState();
240         String resourceCurrVersion = component.getVersion();
241         ComponentBusinessLogic bl = getComponentBL(componentType);
242         Either<User, ResponseFormat> ownerResult = lifeCycleTransition.getComponentOwner(component, componentType);
243         if (ownerResult.isRight()) {
244             return Either.right(ownerResult.right().value());
245         }
246         User owner = ownerResult.left().value();
247         log.info("owner of resource {} is {}", component.getUniqueId(), owner.getUserId());
248         Either<Boolean, ResponseFormat> stateValidationResult = lifeCycleTransition
249             .validateBeforeTransition(component, componentType, modifier, owner, oldState, changeInfo);
250         if (stateValidationResult.isRight()) {
251             log.error("Failed to validateBeforeTransition");
252             errorResponse = stateValidationResult.right().value();
253             componentUtils.auditComponent(errorResponse, modifier, component, lifeCycleTransition.getAuditingAction(),
254                 new ResourceCommonInfo(componentType.getValue()),
255                 ResourceVersionInfo.newBuilder().version(resourceCurrVersion).state(oldState.name()).build(), changeInfo.getUserRemarks());
256             return Either.right(errorResponse);
257         }
258         Either<T, ResponseFormat> operationResult = lifeCycleTransition
259             .changeState(componentType, component, bl, modifier, owner, false, inTransaction);
260         if (operationResult.isRight()) {
261             errorResponse = operationResult.right().value();
262             log.info("audit before sending error response");
263             componentUtils.auditComponentAdmin(errorResponse, modifier, component, lifeCycleTransition.getAuditingAction(), componentType,
264                 ResourceVersionInfo.newBuilder().state(oldState.name()).version(resourceCurrVersion).build());
265             return Either.right(errorResponse);
266         }
267         Component resourceAfterOperation = operationResult.left().value() == null ? component : operationResult.left().value();
268         componentUtils.auditComponent(componentUtils.getResponseFormat(ActionStatus.OK), modifier, resourceAfterOperation,
269             lifeCycleTransition.getAuditingAction(), new ResourceCommonInfo(componentType.getValue()),
270             ResourceVersionInfo.newBuilder().state(oldState.name()).version(resourceCurrVersion).build(), changeInfo.getUserRemarks());
271         return operationResult;
272     }
273
274     private <T extends Component> Either<T, ResponseFormat> getComponentForChange(ComponentTypeEnum componentType, String componentId, User modifier,
275                                                                                   LifeCycleTransition lifeCycleTransition,
276                                                                                   LifecycleChangeInfoWithAction changeInfo) {
277         Either<T, StorageOperationStatus> eitherResourceResponse = toscaOperationFacade.getToscaElement(componentId);
278         ResponseFormat errorResponse;
279         if (eitherResourceResponse.isRight()) {
280             ActionStatus actionStatus = componentUtils.convertFromStorageResponse(eitherResourceResponse.right().value(), componentType);
281             errorResponse = componentUtils.getResponseFormat(actionStatus, Constants.EMPTY_STRING);
282             log.debug("audit before sending response");
283             componentUtils.auditComponent(errorResponse, modifier, lifeCycleTransition.getAuditingAction(),
284                 new ResourceCommonInfo(componentId, componentType.getValue()), changeInfo.getUserRemarks());
285             return Either.right(errorResponse);
286         }
287         return Either.left(eitherResourceResponse.left().value());
288     }
289
290     private Either<Boolean, ResponseFormat> validateHighestVersion(User modifier, LifeCycleTransition lifeCycleTransition, Component component,
291                                                                    String resourceCurrVersion, ComponentTypeEnum componentType) {
292         ResponseFormat errorResponse;
293         if (!component.isHighestVersion()) {
294             log.debug("Component version {} is not the last version of component {}",
295                 component.getComponentMetadataDefinition().getMetadataDataDefinition().getVersion(),
296                 component.getComponentMetadataDefinition().getMetadataDataDefinition().getName());
297             errorResponse = componentUtils.getResponseFormat(ActionStatus.COMPONENT_HAS_NEWER_VERSION,
298                 component.getComponentMetadataDefinition().getMetadataDataDefinition().getName(), componentType.getValue().toLowerCase());
299             componentUtils.auditComponentAdmin(errorResponse, modifier, component, lifeCycleTransition.getAuditingAction(), componentType,
300                 ResourceVersionInfo.newBuilder().state(component.getLifecycleState().name()).version(resourceCurrVersion).build());
301             return Either.right(errorResponse);
302         }
303         return Either.left(true);
304     }
305
306     private Boolean lockComponent(ComponentTypeEnum componentType, Component component) {
307         NodeTypeEnum nodeType = componentType.getNodeType();
308         StorageOperationStatus lockResourceStatus = graphLockOperation.lockComponent(component.getUniqueId(), nodeType);
309         if (lockResourceStatus.equals(StorageOperationStatus.OK)) {
310             return true;
311         } else {
312             ActionStatus actionStatus = componentUtils.convertFromStorageResponse(lockResourceStatus);
313             throw new ByActionStatusComponentException(actionStatus,
314                 component.getComponentMetadataDefinition().getMetadataDataDefinition().getName());
315         }
316     }
317
318     private Either<String, ResponseFormat> validateComment(LifecycleChangeInfoWithAction changeInfo, LifeCycleTransitionEnum transitionEnum) {
319         String comment = changeInfo.getUserRemarks();
320         if (LifeCycleTransitionEnum.CERTIFY == transitionEnum || LifeCycleTransitionEnum.CHECKIN == transitionEnum
321             // import?
322         ) {
323             if (StringUtils.isEmpty(comment)) {
324                 log.debug("user comment cannot be empty or null.");
325                 ResponseFormat errorResponse = componentUtils.getResponseFormat(ActionStatus.MISSING_DATA, COMMENT);
326                 return Either.right(errorResponse);
327             }
328             comment = ValidationUtils.removeNoneUtf8Chars(comment);
329             comment = ValidationUtils.removeHtmlTags(comment);
330             comment = ValidationUtils.normaliseWhitespace(comment);
331             comment = ValidationUtils.stripOctets(comment);
332             if (!ValidationUtils.validateLength(comment, ValidationUtils.COMMENT_MAX_LENGTH)) {
333                 log.debug("user comment exceeds limit.");
334                 return Either
335                     .right(componentUtils.getResponseFormat(ActionStatus.EXCEEDS_LIMIT, COMMENT, String.valueOf(ValidationUtils.COMMENT_MAX_LENGTH)));
336             }
337             if (!ValidationUtils.validateIsEnglish(comment)) {
338                 return Either.right(componentUtils.getResponseFormat(ActionStatus.INVALID_CONTENT));
339             }
340         }
341         return Either.left(comment);
342     }
343
344     private ComponentBusinessLogic getComponentBL(ComponentTypeEnum componentTypeEnum) {
345         ComponentBusinessLogic businessLogic;
346         switch (componentTypeEnum) {
347             case RESOURCE:
348                 businessLogic = this.resourceBusinessLogic;
349                 break;
350             case SERVICE:
351                 businessLogic = this.serviceBusinessLogic;
352                 break;
353             case PRODUCT:
354                 businessLogic = this.productBusinessLogic;
355                 break;
356             default:
357                 throw new IllegalArgumentException("Illegal component type:" + componentTypeEnum.getValue());
358         }
359         return businessLogic;
360     }
361
362     public Either<Component, ResponseFormat> getLatestComponentByUuid(ComponentTypeEnum componentTypeEnum, String uuid) {
363         Either<Component, StorageOperationStatus> latestVersionEither = toscaOperationFacade.getLatestComponentByUuid(uuid);
364         if (latestVersionEither.isRight()) {
365             return Either.right(componentUtils
366                 .getResponseFormat(componentUtils.convertFromStorageResponse(latestVersionEither.right().value(), componentTypeEnum), uuid));
367         }
368         Component latestComponent = latestVersionEither.left().value();
369         return Either.left(latestComponent);
370     }
371
372     /**
373      * Performs Force certification. Note that a Force certification is allowed for the first certification only, as only a state and a version is
374      * promoted due a Force certification, skipping other actions required if a previous certified version exists.
375      *
376      * @param resource
377      * @param user
378      * @param lifecycleChangeInfo
379      * @param inTransaction
380      * @param needLock
381      * @return
382      */
383     public Resource forceResourceCertification(Resource resource, User user, LifecycleChangeInfoWithAction lifecycleChangeInfo, boolean inTransaction,
384                                                boolean needLock) {
385         Resource result = null;
386         Either<ToscaElement, StorageOperationStatus> certifyResourceRes = null;
387         if (lifecycleChangeInfo.getAction() != LifecycleChanceActionEnum.CREATE_FROM_CSAR) {
388             log.debug("Force certification is not allowed for the action {}. ", lifecycleChangeInfo.getAction());
389             throw new ByActionStatusComponentException(ActionStatus.NOT_ALLOWED);
390         }
391         if (!isFirstCertification(resource.getVersion())) {
392             log.debug("Failed to perform a force certification of resource{}. Force certification is allowed for the first certification only. ",
393                 resource.getName());
394             throw new ByActionStatusComponentException(ActionStatus.NOT_ALLOWED);
395         }
396         // lock resource
397         if (!inTransaction && needLock) {
398             log.info("lock component {}", resource.getUniqueId());
399             lockComponent(resource.getComponentType(), resource);
400             log.info("after lock component {}", resource.getUniqueId());
401         }
402         try {
403             certifyResourceRes = lifecycleOperation
404                 .forceCerificationOfToscaElement(resource.getUniqueId(), user.getUserId(), user.getUserId(), resource.getVersion());
405             if (certifyResourceRes.isRight()) {
406                 StorageOperationStatus status = certifyResourceRes.right().value();
407                 log.debug("Failed to perform a force certification of resource {}. The status is {}. ", resource.getName(), status);
408                 throw new ByResponseFormatComponentException(
409                     componentUtils.getResponseFormatByResource(componentUtils.convertFromStorageResponse(status), resource));
410             }
411             result = ModelConverter.convertFromToscaElement(certifyResourceRes.left().value());
412             resource.setComponentMetadataDefinition(result.getComponentMetadataDefinition());
413         } finally {
414             log.info("unlock component {}", resource.getUniqueId());
415             if (!inTransaction) {
416                 if (result != null) {
417                     janusGraphDao.commit();
418                 } else {
419                     janusGraphDao.rollback();
420                 }
421                 if (needLock) {
422                     NodeTypeEnum nodeType = resource.getComponentType().getNodeType();
423                     log.info("During change state, another component {} has been created/updated", resource.getUniqueId());
424                     graphLockOperation.unlockComponent(resource.getUniqueId(), nodeType);
425                 }
426             }
427         }
428         return result;
429     }
430
431     public boolean isFirstCertification(String previousVersion) {
432         return previousVersion.split("\\.")[0].equals("0");
433     }
434 }