/*- * ============LICENSE_START======================================================= * SDC * ================================================================================ * Copyright (C) 2019 AT&T Intellectual Property. All rights reserved. * ================================================================================ * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. * ============LICENSE_END========================================================= */ package org.openecomp.sdc.be.components.merge.property; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.stream.Collectors; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.openecomp.sdc.be.model.DataTypeDefinition; import org.openecomp.sdc.be.model.PropertyDefinition; import org.openecomp.sdc.be.model.tosca.ToscaPropertyType; import org.openecomp.sdc.be.utils.TypeUtils; import org.springframework.stereotype.Component; @Component public class PropertyValueMerger { static boolean isEmptyValue(Object val) { return val == null || val instanceof String && StringUtils.isEmpty((String) val) || val instanceof Map && ((Map) val).isEmpty() || val instanceof List && ((List) val).isEmpty(); } @SuppressWarnings("unchecked") /** * merges property value oldVal into property value newVal recursively * @param oldVal - cannot be {@code Null} */ protected Object merge(Object oldVal, Object newVal, List inputNamesToMerge, String type, String innerType, Map dataTypes) { if (isEmptyValue(newVal)) { return removeUnwantedGetInputValues(oldVal, inputNamesToMerge); } if (isMapTypeValues(oldVal, newVal)) { return mergeMapValue((Map) oldVal, (Map) newVal, inputNamesToMerge, type, innerType, dataTypes); } if (isListTypeValues(oldVal, newVal)) { return mergeListValue((List) oldVal, (List) newVal, inputNamesToMerge, innerType, dataTypes); } if (isSameTypeValues(oldVal, newVal)) { return mergeScalarValue(oldVal, newVal); } return newVal; } private Map mergeMapValue(Map oldValMap, Map newValMap, List inputNamesToMerge, String type, String innertType, Map dataTypes) { mergeEntriesExistInOldValue(oldValMap, newValMap, inputNamesToMerge, type, innertType, dataTypes);//continue the recursion if (type != null && !type.equals("map") && !type.equals("json")) { setOldEntriesNotExistInNewValue(oldValMap, newValMap, inputNamesToMerge); } return newValMap; } private void mergeEntriesExistInOldValue(Map oldValMap, Map newValMap, List inputNamesToMerge, String type, String innerType, Map dataTypes) { for (Map.Entry newValEntry : newValMap.entrySet()) { Object oldVal = oldValMap.get(newValEntry.getKey()); if (oldVal != null) { ImmutablePair types = getTypeAndInnerTypePair(newValEntry.getKey(), type, innerType, dataTypes); newValMap.put(newValEntry.getKey(), merge(oldVal, newValEntry.getValue(), inputNamesToMerge, types.getLeft(), types.getRight(), dataTypes)); } } } private void setOldEntriesNotExistInNewValue(Map oldVal, Map newVal, List getInputNamesToMerge) { for (Map.Entry oldValEntry : oldVal.entrySet()) { if (!isInputEntry(oldValEntry) || isInputToMerge(getInputNamesToMerge, oldValEntry)) { Object oldValObj = oldValEntry.getValue(); newVal.computeIfAbsent(oldValEntry.getKey(), key -> removeUnwantedGetInputValues(oldValObj, getInputNamesToMerge)); } } } private ImmutablePair getTypeAndInnerTypePair(String propName, String type, String innerType, Map dataTypes) { if (type == null || (ToscaPropertyType.isScalarType(type) && ToscaPropertyType.isScalarType(innerType))) { return ImmutablePair.of(innerType, null); } String newInnerType = null; DataTypeDefinition innerTypeDef = dataTypes.get(type); if (innerTypeDef != null) { List properties = innerTypeDef.getProperties(); if (properties != null) { Optional optionalProperty = findProperty(properties, propName); innerType = optionalProperty.map(PropertyDefinition::getType).orElse(innerType); newInnerType = optionalProperty.map(PropertyDefinition::getSchemaType).orElse(null); } } return ImmutablePair.of(innerType, newInnerType); } private Optional findProperty(List properties, String propName) { return properties.stream().filter(p -> propName.equals(p.getName())).findFirst(); } private List mergeListValue(List oldVal, List newVal, List inputNamesToMerge, String innerType, Map dataTypes) { List mergedList = newVal; if (oldVal.size() == newVal.size()) { mergedList = mergeLists(oldVal, newVal, inputNamesToMerge, innerType, dataTypes); } return mergedList; } private List mergeLists(List oldVal, List newVal, List inputNamesToMerge, String innerType, Map dataTypes) { int minListSize = Math.min(oldVal.size(), newVal.size()); List mergedList = new ArrayList<>(); for (int i = 0; i < minListSize; i++) { Object mergedVal = merge(oldVal.get(i), newVal.get(i), inputNamesToMerge, innerType, null, dataTypes); mergedList.add(mergedVal); } return mergedList; } private Object mergeScalarValue(Object oldVal, Object newVal) { return isEmptyValue(newVal) ? oldVal : newVal; } @SuppressWarnings("unchecked") Object removeUnwantedGetInputValues(Object val, List inputNamesToMerge) { if (val instanceof Map) { return removeUnwantedGetInputValues((Map) val, inputNamesToMerge); } if (val instanceof List) { return removeUnwantedGetInputValues((List) val, inputNamesToMerge); } return val; } private List removeUnwantedGetInputValues(List listVal, List inputNamesToMerge) { return listVal.stream().map(val -> removeUnwantedGetInputValues(val, inputNamesToMerge)).collect(Collectors.toList()); } private Map removeUnwantedGetInputValues(Map val, List inputNamesToMerge) { return val.entrySet().stream().filter(entry -> !isInputEntry(entry) || isInputToMerge(inputNamesToMerge, entry)) .collect(Collectors.toMap(Map.Entry::getKey, entry -> removeUnwantedGetInputValues(entry.getValue(), inputNamesToMerge))); } private boolean isInputToMerge(List inputNamesToMerge, Map.Entry entry) { return inputNamesToMerge.contains(retrieveInputName(entry.getValue())); } private boolean isMapTypeValues(Object oldVal, Object newVal) { return newVal instanceof Map && oldVal instanceof Map; } private boolean isListTypeValues(Object oldVal, Object newVal) { return newVal instanceof List && oldVal instanceof List; } private boolean isSameTypeValues(Object oldVal, Object newVal) { return oldVal.getClass().equals(newVal.getClass()); } private String retrieveInputName(Object inputValue) { return inputValue instanceof List ? (String) ((List) inputValue).get(0) : (String) inputValue; } protected boolean isInputEntry(Map.Entry oldValEntry) { return oldValEntry.getKey().equals(TypeUtils.ToscaTagNamesEnum.GET_INPUT.getElementName()); } }