package alien4cloud.paas.function;
import java.util.List;
import java.util.Map;
import org.alien4cloud.tosca.model.definitions.*;
import org.alien4cloud.tosca.model.types.AbstractInheritableToscaType;
import org.alien4cloud.tosca.model.types.AbstractToscaType;
import org.apache.commons.lang3.StringUtils;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import alien4cloud.common.AlienConstants;
import org.alien4cloud.tosca.model.templates.Capability;
import org.alien4cloud.tosca.model.templates.NodeTemplate;
import org.alien4cloud.tosca.model.templates.Requirement;
import org.alien4cloud.tosca.model.templates.Topology;
import alien4cloud.paas.IPaaSTemplate;
import alien4cloud.paas.exception.NotSupportedException;
import alien4cloud.paas.model.InstanceInformation;
import alien4cloud.paas.model.PaaSNodeTemplate;
import alien4cloud.paas.model.PaaSRelationshipTemplate;
import alien4cloud.paas.model.PaaSTopology;
import alien4cloud.rest.utils.JsonUtil;
import alien4cloud.tosca.ToscaUtils;
import alien4cloud.tosca.normative.ToscaFunctionConstants;
import alien4cloud.tosca.normative.ToscaType;
import alien4cloud.utils.AlienUtils;
import alien4cloud.utils.MapUtil;
import alien4cloud.utils.PropertyUtil;
import lombok.extern.slf4j.Slf4j;
/**
* Utility class to process functions defined in attributes or operations input level:
* ex:<br>
*
* <pre>
* attributes:
* url: "http://get_property: [the_node_tempalte_1, the_property_name_1]:get_property: [the_node_tempalte_2, the_property_name_2 ]/super"
* </pre>
*/
@Slf4j
@SuppressWarnings({ "unchecked", "rawtypes" })
public final class FunctionEvaluator {
/**
* Post process / enrich instance information by parsing all function in attributes and replacing them with real values
*
* @param instanceInformations the instance information to post process
* @param topology the topology
* @param paaSTopology the pass topology
*/
public static void postProcessInstanceInformation(Map<String, Map<String, InstanceInformation>> instanceInformations, Topology topology,
PaaSTopology paaSTopology) {
// parse attributes
for (Map.Entry<String, Map<String, InstanceInformation>> nodeInstanceId : instanceInformations.entrySet()) {
for (Map.Entry<String, InstanceInformation> nodeInstanceNumber : nodeInstanceId.getValue().entrySet()) {
if (nodeInstanceNumber.getValue().getAttributes() != null) {
for (Map.Entry<String, String> attributeEntry : nodeInstanceNumber.getValue().getAttributes().entrySet()) {
PaaSNodeTemplate nodeTemplate = paaSTopology.getAllNodes().get(nodeInstanceId.getKey());
Map<String, IValue> nodeTemplateAttributes = nodeTemplate.getIndexedToscaElement().getAttributes();
IValue attributeValue = nodeTemplateAttributes.get(attributeEntry.getKey());
if (attributeValue != null) {
String parsedAttribute = FunctionEvaluator.parseAttribute(attributeEntry.getKey(), attributeValue, topology, instanceInformations,
nodeInstanceNumber.getKey(), nodeTemplate, paaSTopology.getAllNodes());
attributeEntry.setValue(parsedAttribute);
}
}
}
}
}
}
/**
* Parse an attribute value that can be : {@link ConcatPropertyValue} / {@link AttributeDefinition}
*
* @param attributeId
* @param attributeValue
* @param topology
* @param runtimeInformations
* @param currentInstance
* @param basePaaSTemplate
* @param builtPaaSTemplates
* @return
*/
public static String parseAttribute(String attributeId, IValue attributeValue, Topology topology,
Map<String, Map<String, InstanceInformation>> runtimeInformations, String currentInstance,
IPaaSTemplate<? extends AbstractToscaType> basePaaSTemplate, Map<String, PaaSNodeTemplate> builtPaaSTemplates) {
if (attributeValue == null) {
return null;
}
// handle AttributeDefinition type
if (attributeValue instanceof AttributeDefinition) {
String runtimeAttributeValue = extractRuntimeInformationAttribute(runtimeInformations, currentInstance, Lists.newArrayList(basePaaSTemplate),
attributeId);
if (runtimeAttributeValue != null) {
if (!runtimeAttributeValue.contains("=Error!]") && !runtimeAttributeValue.equals("")) {
return runtimeAttributeValue;
}
}
return ((AttributeDefinition) attributeValue).getDefault();
}
// handle concat function
if (attributeValue instanceof ConcatPropertyValue) {
StringBuilder evaluatedAttribute = new StringBuilder();
ConcatPropertyValue concatPropertyValue = (ConcatPropertyValue) attributeValue;
for (IValue concatParam : concatPropertyValue.getParameters()) {
// scalar type
if (concatParam instanceof ScalarPropertyValue) {
// scalar case
evaluatedAttribute.append(((ScalarPropertyValue) concatParam).getValue());
} else if (concatParam instanceof PropertyDefinition) {
// Definition case
// TODO : ?? what should i do here ?? currently returns default value in the definition
evaluatedAttribute.append(((PropertyDefinition) concatParam).getDefault());
} else if (concatParam instanceof FunctionPropertyValue) {
// Function case
FunctionPropertyValue functionPropertyValue = (FunctionPropertyValue) concatParam;
List<? extends IPaaSTemplate> paasTemplates = getPaaSTemplatesFromKeyword(basePaaSTemplate, functionPropertyValue.getTemplateName(),
builtPaaSTemplates);
switch (functionPropertyValue.getFunction()) {
case ToscaFunctionConstants.GET_ATTRIBUTE:
evaluatedAttribute.append(extractRuntimeInformationAttribute(runtimeInformations, currentInstance, paasTemplates,
functionPropertyValue.getElementNameToFetch()));
break;
case ToscaFunctionConstants.GET_PROPERTY:
evaluatedAttribute.append(extractRuntimeInformationProperty(topology, functionPropertyValue.getElementNameToFetch(), paasTemplates));
break;
case ToscaFunctionConstants.GET_OPERATION_OUTPUT:
String defaultValue = "<" + functionPropertyValue.getElementNameToFetch() + ">";
evaluatedAttribute.append(extractRuntimeInformationOperationOutput(runtimeInformations, currentInstance, paasTemplates,
functionPropertyValue, defaultValue));
break;
default:
log.warn("Function [{}] is not yet handled in concat operation.", functionPropertyValue.getFunction());
break;
}
}
}
return evaluatedAttribute.toString();
}
// handle functions. For now, only support Get_OPERATION_OUTPUT on attributes scope
if (attributeValue instanceof FunctionPropertyValue) {
FunctionPropertyValue function = (FunctionPropertyValue) attributeValue;
switch (function.getFunction()) {
case ToscaFunctionConstants.GET_OPERATION_OUTPUT:
List<? extends IPaaSTemplate> paasTemplates = getPaaSTemplatesFromKeyword(basePaaSTemplate, function.getTemplateName(), builtPaaSTemplates);
return extractRuntimeInformationOperationOutput(runtimeInformations, currentInstance, paasTemplates, function, null);
default:
return null;
}
}
return null;
}
private static String extractRuntimeInformationOperationOutput(Map<String, Map<String, InstanceInformation>> runtimeInformations, String instanceId,
List<? extends IPaaSTemplate> nodes, FunctionPropertyValue function, String defaultValue) {
String outputRQN = AlienUtils.prefixWith(AlienConstants.OPERATION_NAME_SEPARATOR, function.getElementNameToFetch(),
new String[] { function.getInterfaceName(), function.getOperationName() });
// return the first found
for (IPaaSTemplate node : nodes) {
String nodeName = node.getId();
if (runtimeInformations.get(nodeName) != null) {
Map<String, String> outputs;
// get value for an instance if instance number found
if (runtimeInformations.get(nodeName).containsKey(instanceId)) {
outputs = runtimeInformations.get(nodeName).get(instanceId).getOperationsOutputs();
} else {
outputs = runtimeInformations.get(nodeName).entrySet().iterator().next().getValue().getAttributes();
}
String formatedOutputName = ToscaUtils.formatedOperationOutputName(nodeName, function.getInterfaceName(), function.getOperationName(),
function.getElementNameToFetch());
if (outputs.containsKey(formatedOutputName)) {
return outputs.get(formatedOutputName);
}
}
}
log.warn("Couldn't find output <{}> in nodes <{}>", outputRQN, nodes.toString());
return defaultValue;
}
/**
* Extract property value from runtime informations
*
* @param topology
* @param propertyOrAttributeName
* @param nodes
* @return
*/
private static String extractRuntimeInformationProperty(Topology topology, String propertyOrAttributeName, List<? extends IPaaSTemplate> nodes) {
AbstractPropertyValue propertyOrAttributeValue;
NodeTemplate template = null;
for (IPaaSTemplate node : nodes) {
String nodeName = node.getId();
template = topology.getNodeTemplates().get(nodeName);
if (template != null && template.getProperties() != null) {
propertyOrAttributeValue = template.getProperties().get(propertyOrAttributeName);
if (propertyOrAttributeValue != null) {
return getScalarValue(propertyOrAttributeValue);
}
}
}
log.warn("Couldn't find property <{}> of node <{}>", propertyOrAttributeName, nodes);
return "[" + nodes + "." + propertyOrAttributeName + "=Error!]";
}
/**
* Return the first matching value in parent nodes hierarchy
*
* @param runtimeInformations
* @param currentInstance
* @param nodes
* @param propertyOrAttributeName
* @return runtime value
*/
private static String extractRuntimeInformationAttribute(Map<String, Map<String, InstanceInformation>> runtimeInformations, String currentInstance,
List<? extends IPaaSTemplate> nodes, String propertyOrAttributeName) {
Map<String, String> attributes = null;
// return the first found
for (IPaaSTemplate node : nodes) {
String nodeName = node.getId();
// get the current attribute value
if (runtimeInformations.get(nodeName) != null) {
// get value for an instance if instance number found
if (runtimeInformations.get(nodeName).containsKey(currentInstance)) {
attributes = runtimeInformations.get(nodeName).get(currentInstance).getAttributes();
} else {
attributes = runtimeInformations.get(nodeName).entrySet().iterator().next().getValue().getAttributes();
}
if (attributes.containsKey(propertyOrAttributeName)) {
return attributes.get(propertyOrAttributeName);
}
}
}
log.warn("Couldn't find attribute <{}> in nodes <{}>", propertyOrAttributeName, nodes.toString());
return "<" + propertyOrAttributeName + ">"; // value not yet computed (or won't be computes)
}
/**
* Return the paaS entities based on a keyword. This latest can be a special keyword (SELF, SOURCE, TARGET, HOST), or a node template name
*
* @param basePaaSTemplate The base PaaSTemplate for which to get the entity name
* @param keyword The
* @param builtPaaSTemplates
* @return a list of PaaSTemplate(relationship or node) resulting from the evaluation
*/
public static List<? extends IPaaSTemplate> getPaaSTemplatesFromKeyword(IPaaSTemplate<? extends AbstractToscaType> basePaaSTemplate, String keyword,
Map<String, PaaSNodeTemplate> builtPaaSTemplates) {
switch (keyword) {
case ToscaFunctionConstants.SELF:
return Lists.<IPaaSTemplate> newArrayList(basePaaSTemplate);
case ToscaFunctionConstants.HOST:
return getWithParentsNodes(getHostNode(basePaaSTemplate));
case ToscaFunctionConstants.SOURCE:
return getWithParentsNodes(getSourceNode(basePaaSTemplate, builtPaaSTemplates));
case ToscaFunctionConstants.TARGET:
return getWithParentsNodes(getTargetNode(basePaaSTemplate, builtPaaSTemplates));
default:
// FIXME if the keyword is in fact the name of a relationship??
return Lists.<IPaaSTemplate> newArrayList(getPaaSNodeOrFail(keyword, builtPaaSTemplates));
}
}
/**
* Evaluate a get_property function type
*
* @param functionParam The property function of type {@link FunctionPropertyValue} to evaluate
* @param basePaaSTemplate The base PaaSTemplate in which the parameter is defined. Can be a {@link PaaSRelationshipTemplate} or a {@link PaaSNodeTemplate}.
* @param builtPaaSTemplates A map < {@link String}, {@link PaaSNodeTemplate}> of built nodetemplates of the processed topology. Note that these
* {@link PaaSNodeTemplate}s should have been built, thus referencing their related parents and relationships.
* @return the String result of the function evalutation
*/
public static String evaluateGetPropertyFunction(FunctionPropertyValue functionParam,
IPaaSTemplate<? extends AbstractInheritableToscaType> basePaaSTemplate, Map<String, PaaSNodeTemplate> builtPaaSTemplates) {
List<? extends IPaaSTemplate> paaSTemplates = getPaaSTemplatesFromKeyword(basePaaSTemplate, functionParam.getTemplateName(), builtPaaSTemplates);
for (IPaaSTemplate paaSTemplate : paaSTemplates) {
String propertyValue = getPropertyFromTemplateOrCapability(paaSTemplate, functionParam.getCapabilityOrRequirementName(),
functionParam.getElementNameToFetch());
// return the first value found
if (propertyValue != null) {
return propertyValue;
}
}
return null;
}
private static String serializeComplexPropertyValue(Object value) {
try {
if (value instanceof String) {
return (String) value;
} else if (value instanceof ComplexPropertyValue) {
return JsonUtil.toString(((ComplexPropertyValue) value).getValue());
} else {
return JsonUtil.toString(value);
}
} catch (JsonProcessingException e) {
return null;
}
}
private static String getPropertyValue(Map<String, AbstractPropertyValue> properties, Map<String, PropertyDefinition> propertyDefinitions,
String propertyAccessPath) {
if (properties == null || !properties.containsKey(propertyAccessPath)) {
String propertyName = PropertyUtil.getPropertyNameFromComplexPath(propertyAccessPath);
if (propertyName == null) {
// Non complex
String defaultValue = PropertyUtil.getDefaultValueFromPropertyDefinitions(propertyAccessPath, propertyDefinitions);
if (defaultValue != null) {
return defaultValue;
} else {
return null;
}
} else {
// Complex
PropertyDefinition propertyDefinition = propertyDefinitions.get(propertyName);
if (propertyDefinition == null) {
return null;
} else if (ToscaType.isSimple(propertyDefinition.getType())) {
// It's a complex path (with '.') but the type in definition is finally simple
return null;
} else if (properties != null) {
AbstractPropertyValue rawValue = properties.get(propertyName);
if (!(rawValue instanceof PropertyValue)) {
throw new NotSupportedException("Only support static value in a get_property");
}
Object value = MapUtil.get(((PropertyValue) rawValue).getValue(), propertyAccessPath.substring(propertyName.length() + 1));
return serializeComplexPropertyValue(value);
} else {
return null;
}
}
} else {
AbstractPropertyValue abstractPropertyValue = properties.get(propertyAccessPath);
if (abstractPropertyValue == null) {
return null;
} else if (!(abstractPropertyValue instanceof PropertyValue)) {
throw new NotSupportedException("Not a property value " + abstractPropertyValue);
} else if (abstractPropertyValue instanceof ScalarPropertyValue) {
return getScalarValue(properties.get(propertyAccessPath));
} else {
try {
return JsonUtil.toString(((PropertyValue) abstractPropertyValue).getValue());
} catch (JsonProcessingException e) {
return null;
}
}
}
}
/**
* Find a property from a template or capability / requirement if a name is provided
* first find in capability, and then in requirement if no found.
*
* @param paaSTemplate
* @param capabilityOrRequirementName
* @param elementName
* @return
*/
private static String getPropertyFromTemplateOrCapability(IPaaSTemplate<? extends AbstractInheritableToscaType> paaSTemplate,
String capabilityOrRequirementName, String elementName) {
// if no capability or requirement provided, return the value from the template property
if (StringUtils.isBlank(capabilityOrRequirementName)) {
return getPropertyValue(paaSTemplate.getTemplate().getProperties(), paaSTemplate.getIndexedToscaElement().getProperties(), elementName);
} else if (paaSTemplate instanceof PaaSNodeTemplate) {
// if capability or requirement name provided:
// FIXME how should I know that the provided name is capability or a requirement name?
NodeTemplate nodeTemplate = (NodeTemplate) paaSTemplate.getTemplate();
AbstractPropertyValue propertyValue = null;
Map<String, Capability> capabilities = nodeTemplate.getCapabilities();
Map<String, Requirement> requirements = nodeTemplate.getRequirements();
// Find in capability first
if (capabilities != null && capabilities.get(capabilityOrRequirementName) != null
&& capabilities.get(capabilityOrRequirementName).getProperties() != null) {
propertyValue = capabilities.get(capabilityOrRequirementName).getProperties().get(elementName);
}
// if not found in capability, find in requirement
if (propertyValue == null) {
if (requirements != null && requirements.containsKey(capabilityOrRequirementName)
&& requirements.get(capabilityOrRequirementName).getProperties() != null) {
propertyValue = requirements.get(capabilityOrRequirementName).getProperties().get(elementName);
}
}
if (propertyValue instanceof ComplexPropertyValue) {
return serializeComplexPropertyValue(((ComplexPropertyValue) propertyValue).getValue());
} else {
return getScalarValue(propertyValue);
}
}
log.warn("The keyword <" + ToscaFunctionConstants.SELF
+ "> can not be used on a Relationship Template level's parameter when trying to retrieve capability / requiement properties. Node<"
+ paaSTemplate.getId() + ">");
return null;
}
private static PaaSNodeTemplate getPaaSNodeOrFail(String nodeId, Map<String, PaaSNodeTemplate> builtPaaSTemplates) {
PaaSNodeTemplate toReturn = builtPaaSTemplates.get(nodeId);
if (toReturn == null) {
throw new FunctionEvaluationException(" Failled to retrieve the nodeTemplate with name <" + nodeId + ">");
}
return toReturn;
}
private static PaaSNodeTemplate getHostNode(IPaaSTemplate<? extends AbstractToscaType> basePaaSTemplate) {
if (basePaaSTemplate instanceof PaaSNodeTemplate) {
// TODO Must review this management of host
PaaSNodeTemplate template = (PaaSNodeTemplate) basePaaSTemplate;
return template.getParent() != null ? template.getParent() : template;
}
throw new BadUsageKeywordException("The keyword <" + ToscaFunctionConstants.HOST + "> can only be used on a NodeTemplate level's parameter. Node<"
+ basePaaSTemplate.getId() + ">");
}
private static PaaSNodeTemplate getSourceNode(IPaaSTemplate<? extends AbstractToscaType> basePaaSTemplate,
Map<String, PaaSNodeTemplate> builtPaaSTemplates) {
if (basePaaSTemplate instanceof PaaSRelationshipTemplate) {
return getPaaSNodeOrFail(((PaaSRelationshipTemplate) basePaaSTemplate).getSource(), builtPaaSTemplates);
}
throw new BadUsageKeywordException("The keyword <" + ToscaFunctionConstants.SOURCE + "> can only be used on a Relationship level's parameter. Node<"
+ basePaaSTemplate.getId() + ">");
}
private static PaaSNodeTemplate getTargetNode(IPaaSTemplate<? extends AbstractToscaType> basePaaSTemplate,
Map<String, PaaSNodeTemplate> builtPaaSTemplates) {
if (basePaaSTemplate instanceof PaaSRelationshipTemplate) {
return getPaaSNodeOrFail(((PaaSRelationshipTemplate) basePaaSTemplate).getRelationshipTemplate().getTarget(), builtPaaSTemplates);
}
throw new BadUsageKeywordException("The keyword <" + ToscaFunctionConstants.TARGET + "> can only be used on a Relationship level's parameter. Node<"
+ basePaaSTemplate.getId() + ">.");
}
private static List<PaaSNodeTemplate> getWithParentsNodes(final PaaSNodeTemplate paaSNodeTemplate) {
List<PaaSNodeTemplate> toReturn = Lists.newArrayList();
PaaSNodeTemplate parent = paaSNodeTemplate;
while (parent != null) {
toReturn.add(parent);
parent = parent.getParent();
}
return toReturn;
}
/**
* Get the scalar value
*
* @param propertyValue the property value
* @throws alien4cloud.paas.exception.NotSupportedException if called on a non ScalarPropertyValue
* @return the value or null if the propertyValue is null
*/
public static String getScalarValue(AbstractPropertyValue propertyValue) {
if (propertyValue == null) {
return null;
} else if (propertyValue instanceof ScalarPropertyValue) {
return ((ScalarPropertyValue) propertyValue).getValue();
} else {
throw new NotSupportedException("Property value is not of type scalar");
}
}
public static Map<String, String> getScalarValues(Map<String, AbstractPropertyValue> propertyValues) {
if (propertyValues == null) {
return null;
}
Map<String, String> properties = Maps.newHashMap();
for (Map.Entry<String, AbstractPropertyValue> propertyValueEntry : propertyValues.entrySet()) {
properties.put(propertyValueEntry.getKey(), getScalarValue(propertyValueEntry.getValue()));
}
return properties;
}
public static boolean isGetAttribute(FunctionPropertyValue function) {
return ToscaFunctionConstants.GET_ATTRIBUTE.equals(function.getFunction());
}
public static boolean isGetOperationOutput(FunctionPropertyValue function) {
return ToscaFunctionConstants.GET_OPERATION_OUTPUT.equals(function.getFunction());
}
/**
* Check if the given property value is a TOSCA get_input function.
*
* @param propertyValue The property value to evaluate.
* @return True if the property is a TOSCA get_input function, false if not.
*/
public static boolean isGetInput(AbstractPropertyValue propertyValue) {
return propertyValue instanceof FunctionPropertyValue && isGetInput((FunctionPropertyValue) propertyValue);
}
public static boolean isGetInput(FunctionPropertyValue function) {
return ToscaFunctionConstants.GET_INPUT.equals(function.getFunction());
}
}