package alien4cloud.tosca.parser.postprocess;
import java.util.Map;
import java.util.Set;
import javax.annotation.Resource;
import org.springframework.stereotype.Component;
import org.yaml.snakeyaml.nodes.Node;
import com.google.common.collect.Sets;
import org.alien4cloud.tosca.model.types.DataType;
import org.alien4cloud.tosca.model.definitions.PropertyConstraint;
import org.alien4cloud.tosca.model.definitions.PropertyDefinition;
import alien4cloud.tosca.context.ToscaContext;
import alien4cloud.tosca.model.ArchiveRoot;
import alien4cloud.tosca.normative.IPropertyType;
import alien4cloud.tosca.normative.ToscaType;
import alien4cloud.tosca.parser.ParsingContextExecution;
import alien4cloud.tosca.parser.ParsingError;
import alien4cloud.tosca.parser.ParsingErrorLevel;
import alien4cloud.tosca.parser.impl.ErrorCode;
import alien4cloud.tosca.properties.constraints.exception.ConstraintValueDoNotMatchPropertyTypeException;
import alien4cloud.tosca.properties.constraints.exception.ConstraintViolationException;
import alien4cloud.utils.services.ConstraintPropertyService;
/**
* Performs validation of a property definition:
* - check that constraints can apply on type and are well defined
* - check that default value match the definition.
*/
@Component
public class PropertyDefinitionPostProcessor implements IPostProcessor<Map.Entry<String, PropertyDefinition>> {
@Resource
private ConstraintPropertyService constraintPropertyService;
@Override
public void process(Map.Entry<String, PropertyDefinition> instance) {
PropertyDefinition propertyDefinition = instance.getValue();
validateType(propertyDefinition);
if (propertyDefinition.getConstraints() != null) {
Set<String> definedConstraints = Sets.newHashSet();
for (PropertyConstraint constraint : propertyDefinition.getConstraints()) {
validate(propertyDefinition, constraint);
if (!definedConstraints.add(constraint.getClass().getName())) {
Node node = ParsingContextExecution.getObjectToNodeMap().get(constraint);
ParsingContextExecution.getParsingErrors().add(new ParsingError(ParsingErrorLevel.ERROR, ErrorCode.VALIDATION_ERROR,
"ToscaPropertyConstraintDuplicate", node.getStartMark(), "Constraint duplicated", node.getEndMark(), "constraint"));
}
}
}
if (propertyDefinition.getDefault() != null) {
checkDefaultValue(instance.getKey(), propertyDefinition);
}
}
private void checkDefaultValue(String propertyName, PropertyDefinition propertyDefinition) {
try {
constraintPropertyService.checkPropertyConstraint(propertyName, propertyDefinition.getDefault().getValue(), propertyDefinition);
} catch (ConstraintValueDoNotMatchPropertyTypeException | ConstraintViolationException e) {
Node node = ParsingContextExecution.getObjectToNodeMap().get(propertyDefinition.getDefault());
StringBuilder problem = new StringBuilder("Validation issue ");
if (e.getConstraintInformation() != null) {
problem.append("for " + e.getConstraintInformation().toString());
}
problem.append(e.getMessage());
ParsingContextExecution.getParsingErrors().add(new ParsingError(ParsingErrorLevel.ERROR, ErrorCode.VALIDATION_ERROR, propertyName,
node.getStartMark(), problem.toString(), node.getEndMark(), propertyName));
}
}
private void validateType(PropertyDefinition propertyDefinition) {
String propertyType = propertyDefinition.getType();
if (propertyType == null) {
Node node = ParsingContextExecution.getObjectToNodeMap().get(propertyType);
ParsingContextExecution.getParsingErrors().add(new ParsingError(ErrorCode.VALIDATION_ERROR, "ToscaPropertyType", node.getStartMark(),
"Property type must be defined", node.getEndMark(), "type"));
} else if (!ToscaType.isSimple(propertyType)) {
if (ToscaType.LIST.equals(propertyType) || ToscaType.MAP.equals(propertyType)) {
PropertyDefinition entrySchema = propertyDefinition.getEntrySchema();
if (entrySchema == null) {
Node node = ParsingContextExecution.getObjectToNodeMap().get(propertyDefinition);
ParsingContextExecution.getParsingErrors().add(new ParsingError(ErrorCode.VALIDATION_ERROR, "ToscaPropertyType", node.getStartMark(),
"Type " + propertyType + " must define entry schema", node.getEndMark(), "type"));
} else {
validateType(entrySchema);
}
} else {
// It's data type
ArchiveRoot archiveRoot = ParsingContextExecution.getRootObj();
if (!archiveRoot.getDataTypes().containsKey(propertyType)) {
DataType dataType = ToscaContext.get(DataType.class, propertyType);
if (dataType == null) {
Node node = ParsingContextExecution.getObjectToNodeMap().get(propertyType);
ParsingContextExecution.getParsingErrors().add(new ParsingError(ErrorCode.TYPE_NOT_FOUND, "ToscaPropertyType", node.getStartMark(),
"Type " + propertyType + " is not found.", node.getEndMark(), "type"));
}
}
}
}
}
private void validate(PropertyDefinition propertyDefinition, PropertyConstraint constraint) {
IPropertyType<?> toscaType = ToscaType.fromYamlTypeName(propertyDefinition.getType());
if (toscaType == null) {
Node node = ParsingContextExecution.getObjectToNodeMap().get(propertyDefinition.getType());
ParsingContextExecution.getParsingErrors().add(new ParsingError(ParsingErrorLevel.ERROR, ErrorCode.INVALID_CONSTRAINT, "Constraint parsing issue",
node.getStartMark(), "Limitation - Constraint cannot be used for type " + propertyDefinition.getType(), node.getEndMark(), "constraint"));
} else {
try {
constraint.initialize(toscaType);
} catch (ConstraintValueDoNotMatchPropertyTypeException e) {
Node node = ParsingContextExecution.getObjectToNodeMap().get(constraint);
ParsingContextExecution.getParsingErrors().add(new ParsingError(ParsingErrorLevel.ERROR, ErrorCode.VALIDATION_ERROR, "ToscaPropertyConstraint",
node.getStartMark(), e.getMessage(), node.getEndMark(), "constraint"));
}
}
}
}