/*
 * Copyright © 2017-2018 AT&T Intellectual Property.
 * Modifications Copyright © 2018 IBM.
 *
 * 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.
 */

package org.onap.ccsdk.cds.controllerblueprints.core.service

import org.slf4j.LoggerFactory
import com.fasterxml.jackson.databind.JsonNode
import com.google.common.base.Preconditions
import org.apache.commons.lang3.StringUtils
import org.onap.ccsdk.cds.controllerblueprints.core.*
import org.onap.ccsdk.cds.controllerblueprints.core.data.*
import org.onap.ccsdk.cds.controllerblueprints.core.utils.JacksonUtils
import java.io.Serializable

/**
 *
 *
 * @author Brinda Santh
 */
interface BluePrintValidatorService : Serializable {

    @Throws(BluePrintException::class)
    fun validateBlueprint(bluePrintContext: BluePrintContext, properties: MutableMap<String, Any>)

    @Throws(BluePrintException::class)
    fun validateBlueprint(serviceTemplate: ServiceTemplate, properties: MutableMap<String, Any>)
}

@Deprecated("Decomposed implementation moved to blueprint-validation module")
open class BluePrintValidatorDefaultService : BluePrintValidatorService {

    val log= LoggerFactory.getLogger(BluePrintValidatorDefaultService::class.toString())

    lateinit var bluePrintContext: BluePrintContext
    lateinit var serviceTemplate: ServiceTemplate
    lateinit var properties: MutableMap<String, Any>
    var message: StringBuilder = StringBuilder()
    private val separator: String = BluePrintConstants.PATH_DIVIDER
    var paths: MutableList<String> = arrayListOf()

    @Throws(BluePrintException::class)
    override fun validateBlueprint(bluePrintContext: BluePrintContext, properties: MutableMap<String, Any>) {
        validateBlueprint(bluePrintContext.serviceTemplate, properties)
    }

    @Throws(BluePrintException::class)
    override fun validateBlueprint(serviceTemplate: ServiceTemplate, properties: MutableMap<String, Any>) {
        this.bluePrintContext = BluePrintContext(serviceTemplate)
        this.serviceTemplate = serviceTemplate
        this.properties = properties
        try {
            message.appendln("-> Config Blueprint")
            serviceTemplate.metadata?.let { validateMetadata(serviceTemplate.metadata!!) }
            serviceTemplate.artifactTypes?.let { validateArtifactTypes(serviceTemplate.artifactTypes!!) }
            serviceTemplate.dataTypes?.let { validateDataTypes(serviceTemplate.dataTypes!!) }
            serviceTemplate.nodeTypes?.let { validateNodeTypes(serviceTemplate.nodeTypes!!) }
            serviceTemplate.topologyTemplate?.let { validateTopologyTemplate(serviceTemplate.topologyTemplate!!) }
        } catch (e: Exception) {
            log.error("validation failed in the path : {}", paths.joinToString(separator), e)
            log.error("validation trace message :{} ", message)
            throw BluePrintException(e,
                    format("failed to validate blueprint on path ({}) with message {}"
                            , paths.joinToString(separator), e.message))
        }
    }

    @Throws(BluePrintException::class)
    open fun validateMetadata(metaDataMap: MutableMap<String, String>) {
        paths.add("metadata")

        val templateName = metaDataMap[BluePrintConstants.METADATA_TEMPLATE_NAME]
        val templateVersion = metaDataMap[BluePrintConstants.METADATA_TEMPLATE_VERSION]
        val templateTags = metaDataMap[BluePrintConstants.METADATA_TEMPLATE_TAGS]
        val templateAuthor = metaDataMap[BluePrintConstants.METADATA_TEMPLATE_AUTHOR]

        Preconditions.checkArgument(StringUtils.isNotBlank(templateName), "failed to get template name metadata")
        Preconditions.checkArgument(StringUtils.isNotBlank(templateVersion), "failed to get template version metadata")
        Preconditions.checkArgument(StringUtils.isNotBlank(templateTags), "failed to get template tags metadata")
        Preconditions.checkArgument(StringUtils.isNotBlank(templateAuthor), "failed to get template author metadata")
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateArtifactTypes(artifactTypes: MutableMap<String, ArtifactType>) {
        paths.add("artifact_types")
        artifactTypes.forEach { artifactName, artifactType ->
            paths.add(artifactName)
            message.appendln("--> Artifact Type :" + paths.joinToString(separator))
            artifactType.properties?.let { validatePropertyDefinitions(artifactType.properties!!) }
            paths.removeAt(paths.lastIndex)
        }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateDataTypes(dataTypes: MutableMap<String, DataType>) {
        paths.add("dataTypes")
        dataTypes.forEach { dataTypeName, dataType ->
            paths.add(dataTypeName)
            message.appendln("--> DataType :" + paths.joinToString(separator))
            dataType.properties?.let { validatePropertyDefinitions(dataType.properties!!) }
            paths.removeAt(paths.lastIndex)
        }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateNodeTypes(nodeTypes: MutableMap<String, NodeType>) {
        paths.add("nodeTypes")
        nodeTypes.forEach { nodeTypeName, nodeType ->
            // Validate Single Node Type
            validateNodeType(nodeTypeName, nodeType)
        }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateNodeType(nodeTypeName: String, nodeType: NodeType) {
        paths.add(nodeTypeName)
        message.appendln("--> Node Type :" + paths.joinToString(separator))
        val derivedFrom: String = nodeType.derivedFrom
        //Check Derived From
        checkValidNodeTypesDerivedFrom(nodeTypeName, derivedFrom)

        if (!BluePrintTypes.rootNodeTypes().contains(derivedFrom)) {
            serviceTemplate.nodeTypes?.get(derivedFrom)
                    ?: throw BluePrintException(format("Failed to get derivedFrom NodeType({})'s for NodeType({}) ",
                            derivedFrom, nodeTypeName))
        }

        nodeType.properties?.let { validatePropertyDefinitions(nodeType.properties!!) }
        nodeType.capabilities?.let { validateCapabilityDefinitions(nodeTypeName, nodeType) }
        nodeType.requirements?.let { validateRequirementDefinitions(nodeTypeName, nodeType) }
        nodeType.interfaces?.let { validateInterfaceDefinitions(nodeType.interfaces!!) }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun checkValidNodeTypesDerivedFrom(nodeTypeName: String, derivedFrom: String) {
        check(BluePrintTypes.validNodeTypeDerivedFroms.contains(derivedFrom)) {
            throw BluePrintException(format("Failed to get node type ({})'s  derivedFrom({}) definition ", nodeTypeName, derivedFrom))
        }
    }

    @Throws(BluePrintException::class)
    open fun validateTopologyTemplate(topologyTemplate: TopologyTemplate) {
        paths.add("topology")
        message.appendln("--> Topology Template")
        topologyTemplate.inputs?.let { validateInputs(topologyTemplate.inputs!!) }
        topologyTemplate.nodeTemplates?.let { validateNodeTemplates(topologyTemplate.nodeTemplates!!) }
        topologyTemplate.workflows?.let { validateWorkFlows(topologyTemplate.workflows!!) }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateInputs(inputs: MutableMap<String, PropertyDefinition>) {
        paths.add("inputs")
        message.appendln("---> Input :" + paths.joinToString(separator))
        validatePropertyDefinitions(inputs)
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateNodeTemplates(nodeTemplates: MutableMap<String, NodeTemplate>) {
        paths.add("nodeTemplates")
        nodeTemplates.forEach { nodeTemplateName, nodeTemplate ->
            validateNodeTemplate(nodeTemplateName, nodeTemplate)
        }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateNodeTemplate(nodeTemplateName: String, nodeTemplate: NodeTemplate) {
        paths.add(nodeTemplateName)
        message.appendln("---> NodeTemplate :" + paths.joinToString(separator))
        val type: String = nodeTemplate.type

        val nodeType: NodeType = serviceTemplate.nodeTypes?.get(type)
                ?: throw BluePrintException(format("Failed to get NodeType({}) definition for NodeTemplate({})", type, nodeTemplateName))

        nodeTemplate.artifacts?.let { validateArtifactDefinitions(nodeTemplate.artifacts!!) }
        nodeTemplate.properties?.let { validatePropertyAssignments(nodeType.properties!!, nodeTemplate.properties!!) }
        nodeTemplate.capabilities?.let { validateCapabilityAssignments(nodeType, nodeTemplateName, nodeTemplate) }
        nodeTemplate.requirements?.let { validateRequirementAssignments(nodeType, nodeTemplateName, nodeTemplate) }
        nodeTemplate.interfaces?.let { validateInterfaceAssignments(nodeType, nodeTemplateName, nodeTemplate) }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateArtifactDefinitions(artifacts: MutableMap<String, ArtifactDefinition>) {
        paths.add("artifacts")
        artifacts.forEach { artifactDefinitionName, artifactDefinition ->
            paths.add(artifactDefinitionName)
            message.appendln("Validating artifact " + paths.joinToString(separator))
            val type: String = artifactDefinition.type
                    ?: throw BluePrintException(format("type is missing for ArtifactDefinition({})", artifactDefinitionName))
            // Check Artifact Type
            checkValidArtifactType(artifactDefinitionName, type)

            val file: String = artifactDefinition.file
                    ?: throw BluePrintException(format("file is missing for ArtifactDefinition({})", artifactDefinitionName))

            paths.removeAt(paths.lastIndex)
        }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateWorkFlows(workflows: MutableMap<String, Workflow>) {
        paths.add("workflows")
        workflows.forEach { workflowName, workflow ->

            // Validate Single workflow
            validateWorkFlow(workflowName, workflow)
        }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateWorkFlow(workflowName: String, workflow: Workflow) {
        paths.add(workflowName)
        message.appendln("---> Workflow :" + paths.joinToString(separator))
        // Step Validation Start
        paths.add("steps")
        workflow.steps?.forEach { stepName, _ ->
            paths.add(stepName)
            message.appendln("----> Steps :" + paths.joinToString(separator))
            paths.removeAt(paths.lastIndex)
        }
        paths.removeAt(paths.lastIndex)
        // Step Validation Ends
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validatePropertyDefinitions(properties: MutableMap<String, PropertyDefinition>) {
        paths.add("properties")
        properties.forEach { propertyName, propertyDefinition ->
            paths.add(propertyName)
            val dataType: String = propertyDefinition.type
            when {
                BluePrintTypes.validPrimitiveTypes().contains(dataType) -> {
                    // Do Nothing
                }
                BluePrintTypes.validCollectionTypes().contains(dataType) -> {
                    val entrySchemaType: String = propertyDefinition.entrySchema?.type
                            ?: throw BluePrintException(format("Entry schema for DataType ({}) for the property ({}) not found", dataType, propertyName))
                    checkPrimitiveOrComplex(entrySchemaType, propertyName)
                }
                else -> checkPropertyDataType(dataType, propertyName)
            }
            message.appendln("property " + paths.joinToString(separator) + " of type " + dataType)
            paths.removeAt(paths.lastIndex)
        }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validatePropertyAssignments(nodeTypeProperties: MutableMap<String, PropertyDefinition>,
                                         properties: MutableMap<String, JsonNode>) {
        properties.forEach { propertyName, propertyAssignment ->
            val propertyDefinition: PropertyDefinition = nodeTypeProperties[propertyName]
                    ?: throw BluePrintException(format("failed to get definition for the property ({})", propertyName))

            validatePropertyAssignment(propertyName, propertyDefinition, propertyAssignment)

        }
    }

    @Throws(BluePrintException::class)
    open fun validatePropertyAssignment(propertyName: String, propertyDefinition: PropertyDefinition,
                                        propertyAssignment: JsonNode) {
        // Check and Validate if Expression Node
        val expressionData = BluePrintExpressionService.getExpressionData(propertyAssignment)
        if (!expressionData.isExpression) {
            checkPropertyValue(propertyName, propertyDefinition, propertyAssignment)
        }
    }

    @Throws(BluePrintException::class)
    open fun validateCapabilityAssignments(nodeType: NodeType, nodeTemplateName: String, nodeTemplate: NodeTemplate) {
        val capabilities = nodeTemplate.capabilities
        paths.add("capabilities")
        capabilities?.forEach { capabilityName, capabilityAssignment ->
            paths.add(capabilityName)

            val capabilityDefinition = nodeType.capabilities?.get(capabilityName)
                    ?: throw BluePrintException(format("Failed to get NodeTemplate({}) capability definition ({}) " +
                            "from NodeType({}) ", nodeTemplateName, capabilityName, nodeTemplate.type))

            validateCapabilityAssignment(nodeTemplateName, capabilityName, capabilityDefinition, capabilityAssignment)

            paths.removeAt(paths.lastIndex)
        }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateCapabilityAssignment(nodeTemplateName: String, capabilityName: String,
                                          capabilityDefinition: CapabilityDefinition, capabilityAssignment: CapabilityAssignment) {

        capabilityAssignment.properties?.let { validatePropertyAssignments(capabilityDefinition.properties!!, capabilityAssignment.properties!!) }

    }

    @Throws(BluePrintException::class)
    open fun validateRequirementAssignments(nodeType: NodeType, nodeTemplateName: String, nodeTemplate: NodeTemplate) {
        val requirements = nodeTemplate.requirements
        paths.add("requirements")
        requirements?.forEach { requirementName, requirementAssignment ->
            paths.add(requirementName)
            val requirementDefinition = nodeType.requirements?.get(requirementName)
                    ?: throw BluePrintException(format("Failed to get NodeTemplate({}) requirement definition ({}) from" +
                            " NodeType({}) ", nodeTemplateName, requirementName, nodeTemplate.type))
            // Validate Requirement Assignment
            validateRequirementAssignment(nodeTemplateName, requirementName, requirementDefinition, requirementAssignment)
            paths.removeAt(paths.lastIndex)
        }
        paths.removeAt(paths.lastIndex)

    }

    @Throws(BluePrintException::class)
    open fun validateRequirementAssignment(nodeTemplateName: String, requirementAssignmentName: String,
                                           requirementDefinition: RequirementDefinition, requirementAssignment: RequirementAssignment) {
        log.info("Validating NodeTemplate({}) requirement assignment ({}) ", nodeTemplateName, requirementAssignmentName)
        val requirementNodeTemplateName = requirementAssignment.node!!
        val capabilityName = requirementAssignment.capability
        val relationship = requirementAssignment.relationship!!

        check(BluePrintTypes.validRelationShipDerivedFroms.contains(relationship)) {
            throw BluePrintException(format("Failed to get relationship type ({}) for NodeTemplate({})'s requirement({}) ",
                    relationship, nodeTemplateName, requirementAssignmentName))
        }

        val relationShipNodeTemplate = serviceTemplate.topologyTemplate?.nodeTemplates?.get(requirementNodeTemplateName)
                ?: throw BluePrintException(format("Failed to get requirement NodeTemplate({})'s for NodeTemplate({}) requirement({}) ",
                        requirementNodeTemplateName, nodeTemplateName, requirementAssignmentName))

        relationShipNodeTemplate.capabilities?.get(capabilityName)
                ?: throw BluePrintException(format("Failed to get requirement NodeTemplate({})'s capability({}) for NodeTemplate ({})'s requirement({}) ",
                        requirementNodeTemplateName, capabilityName, nodeTemplateName, requirementAssignmentName))


    }

    @Throws(BluePrintException::class)
    open fun validateInterfaceAssignments(nodeType: NodeType, nodeTemplateName: String, nodeTemplate: NodeTemplate) {

        val interfaces = nodeTemplate.interfaces
        paths.add("interfaces")
        interfaces?.forEach { interfaceAssignmentName, interfaceAssignment ->
            paths.add(interfaceAssignmentName)
            val interfaceDefinition = nodeType.interfaces?.get(interfaceAssignmentName)
                    ?: throw BluePrintException(format("Failed to get NodeTemplate({}) interface definition ({}) from" +
                            " NodeType({}) ", nodeTemplateName, interfaceAssignmentName, nodeTemplate.type))

            validateInterfaceAssignment(nodeTemplateName, interfaceAssignmentName, interfaceDefinition,
                    interfaceAssignment)
            paths.removeAt(paths.lastIndex)
        }
        paths.removeAt(paths.lastIndex)


    }

    @Throws(BluePrintException::class)
    open fun validateInterfaceAssignment(nodeTemplateName: String, interfaceAssignmentName: String,
                                         interfaceDefinition: InterfaceDefinition,
                                         interfaceAssignment: InterfaceAssignment) {

        val operations = interfaceAssignment.operations
        operations?.let {
            validateInterfaceOperationsAssignment(nodeTemplateName, interfaceAssignmentName, interfaceDefinition,
                    interfaceAssignment)
        }

    }

    @Throws(BluePrintException::class)
    open fun validateInterfaceOperationsAssignment(nodeTemplateName: String, interfaceAssignmentName: String,
                                                   interfaceDefinition: InterfaceDefinition,
                                                   interfaceAssignment: InterfaceAssignment) {

        val operations = interfaceAssignment.operations
        operations?.let {
            it.forEach { operationAssignmentName, operationAssignments ->

                val operationDefinition = interfaceDefinition.operations?.get(operationAssignmentName)
                        ?: throw BluePrintException(format("Failed to get NodeTemplate({}) operation definition ({}) ",
                                nodeTemplateName, operationAssignmentName))

                log.info("Validation NodeTemplate({}) Interface({}) Operation ({})", nodeTemplateName,
                        interfaceAssignmentName, operationAssignmentName)

                val inputs = operationAssignments.inputs
                val outputs = operationAssignments.outputs

                inputs?.forEach { propertyName, propertyAssignment ->
                    val propertyDefinition = operationDefinition.inputs?.get(propertyName)
                            ?: throw BluePrintException(format("Failed to get NodeTemplate({}) operation definition ({}) " +
                                    "property definition({})", nodeTemplateName, operationAssignmentName, propertyName))
                    // Check the property values with property definition
                    validatePropertyAssignment(propertyName, propertyDefinition, propertyAssignment)
                }

                outputs?.forEach { propertyName, propertyAssignment ->
                    val propertyDefinition = operationDefinition.outputs?.get(propertyName)
                            ?: throw BluePrintException(format("Failed to get NodeTemplate({}) operation definition ({}) " +
                                    "output property definition({})", nodeTemplateName, operationAssignmentName,
                                    propertyName))
                    // Check the property values with property definition
                    validatePropertyAssignment(propertyName, propertyDefinition, propertyAssignment)
                }

            }
        }

    }

    @Throws(BluePrintException::class)
    open fun validateCapabilityDefinitions(nodeTypeName: String, nodeType: NodeType) {
        val capabilities = nodeType.capabilities
        paths.add("capabilities")
        capabilities?.forEach { capabilityName, capabilityDefinition ->
            paths.add(capabilityName)

            validateCapabilityDefinition(nodeTypeName, nodeType, capabilityName, capabilityDefinition)

            paths.removeAt(paths.lastIndex)
        }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateCapabilityDefinition(nodeTypeName: String, nodeType: NodeType, capabilityName: String,
                                          capabilityDefinition: CapabilityDefinition) {
        val capabilityType = capabilityDefinition.type
        check(BluePrintTypes.validCapabilityTypes.contains(capabilityType)) {
            throw BluePrintException(format("Failed to get CapabilityType({}) for NodeType({})",
                    capabilityType, nodeTypeName))
        }
    }

    @Throws(BluePrintException::class)
    open fun validateRequirementDefinitions(nodeName: String, nodeType: NodeType) {
        paths.add("requirements")
        val requirements = nodeType.requirements

        requirements?.forEach { requirementDefinitionName, requirementDefinition ->
            paths.add(requirementDefinitionName)
            message.appendln("Validating : " + paths.joinToString(separator))
            validateRequirementDefinition(nodeName, nodeType, requirementDefinitionName, requirementDefinition)
            paths.removeAt(paths.lastIndex)
        }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateRequirementDefinition(nodeTypeName: String, nodeType: NodeType, requirementDefinitionName: String,
                                           requirementDefinition: RequirementDefinition) {

        log.info("Validating NodeType({}) RequirementDefinition ({}) ", nodeTypeName, requirementDefinitionName)
        val requirementNodeTypeName = requirementDefinition.node!!
        val capabilityName = requirementDefinition.capability
        val relationship = requirementDefinition.relationship!!

        check(BluePrintTypes.validRelationShipDerivedFroms.contains(relationship)) {
            throw BluePrintException(format("Failed to get relationship({}) for NodeType({})'s requirement({}) ",
                    relationship, nodeTypeName, requirementDefinitionName))
        }

        val relationShipNodeType = serviceTemplate.nodeTypes?.get(requirementNodeTypeName)
                ?: throw BluePrintException(format("Failed to get requirement NodeType({})'s for requirement({}) ",
                        requirementNodeTypeName, requirementDefinitionName))

        relationShipNodeType.capabilities?.get(capabilityName)
                ?: throw BluePrintException(format("Failed to get requirement NodeType({})'s capability({}) for NodeType ({})'s requirement({}) ",
                        requirementNodeTypeName, capabilityName, nodeTypeName, requirementDefinitionName))

    }


    @Throws(BluePrintException::class)
    open fun validateInterfaceDefinitions(interfaces: MutableMap<String, InterfaceDefinition>) {
        paths.add("interfaces")
        interfaces.forEach { interfaceName, interfaceDefinition ->
            paths.add(interfaceName)
            message.appendln("Validating : " + paths.joinToString(separator))
            interfaceDefinition.operations?.let { validateOperationDefinitions(interfaceDefinition.operations!!) }
            paths.removeAt(paths.lastIndex)
        }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateOperationDefinitions(operations: MutableMap<String, OperationDefinition>) {
        paths.add("operations")
        operations.forEach { opertaionName, operationDefinition ->
            paths.add(opertaionName)
            message.appendln("Validating : " + paths.joinToString(separator))
            operationDefinition.implementation?.let { validateImplementation(operationDefinition.implementation!!) }
            operationDefinition.inputs?.let { validatePropertyDefinitions(operationDefinition.inputs!!) }
            operationDefinition.outputs?.let { validatePropertyDefinitions(operationDefinition.outputs!!) }
            paths.removeAt(paths.lastIndex)
        }
        paths.removeAt(paths.lastIndex)
    }

    @Throws(BluePrintException::class)
    open fun validateImplementation(implementation: Implementation) {
        checkNotEmpty(implementation.primary) { "couldn't get implementation" }
    }

    @Throws(BluePrintException::class)
    open fun checkValidArtifactType(artifactDefinitionName: String, artifactTypeName: String) {

        val artifactType = serviceTemplate.artifactTypes?.get(artifactTypeName)
                ?: throw BluePrintException("failed to artifactType($artifactTypeName) for ArtifactDefinition($artifactDefinitionName)")

        checkValidArtifactTypeDerivedFrom(artifactTypeName, artifactType.derivedFrom)
    }

    @Throws(BluePrintException::class)
    open fun checkValidArtifactTypeDerivedFrom(artifactTypeName: String, derivedFrom: String) {
        check(BluePrintTypes.validArtifactTypeDerivedFroms.contains(derivedFrom)) {
            throw BluePrintException("failed to get artifactType($artifactTypeName)'s derivedFrom($derivedFrom) definition")
        }
    }

    @Throws(BluePrintException::class)
    open fun checkValidDataTypeDerivedFrom(dataTypeName: String, derivedFrom: String) {
        check(BluePrintTypes.validDataTypeDerivedFroms.contains(derivedFrom)) {
            throw BluePrintException(format("Failed to get DataType({})'s  derivedFrom({}) definition ", dataTypeName, derivedFrom))
        }
    }

    @Throws(BluePrintException::class)
    open fun checkValidRelationshipTypeDerivedFrom(relationshipTypeName: String, derivedFrom: String) {
        check(BluePrintTypes.validRelationShipDerivedFroms.contains(derivedFrom)) {
            throw BluePrintException(format("Failed to get relationship type ({})'s  derivedFrom({}) definition ", relationshipTypeName, derivedFrom))
        }
    }

    open fun checkPropertyValue(propertyName: String, propertyDefinition: PropertyDefinition, propertyAssignment: JsonNode) {
        val propertyType = propertyDefinition.type
        val isValid: Boolean

        if (BluePrintTypes.validPrimitiveTypes().contains(propertyType)) {
            isValid = JacksonUtils.checkJsonNodeValueOfPrimitiveType(propertyType, propertyAssignment)

        } else if (BluePrintTypes.validCollectionTypes().contains(propertyType)) {

            val entrySchemaType = propertyDefinition.entrySchema?.type
                    ?: throw BluePrintException(format("Failed to get EntrySchema type for the collection property ({})", propertyName))

            if (!BluePrintTypes.validPropertyTypes().contains(entrySchemaType)) {
                checkPropertyDataType(entrySchemaType, propertyName)
            }
            isValid = JacksonUtils.checkJsonNodeValueOfCollectionType(propertyType, propertyAssignment)
        } else {
            checkPropertyDataType(propertyType, propertyName)
            isValid = true
        }

        check(isValid) {
            throw BluePrintException(format("property({}) defined of type({}) is not comptable with the value ({})",
                    propertyName, propertyType, propertyAssignment))
        }
    }

    private fun checkPropertyDataType(dataTypeName: String, propertyName: String) {

        val dataType = serviceTemplate.dataTypes?.get(dataTypeName)
                ?: throw BluePrintException(format("DataType ({}) for the property ({}) not found", dataTypeName, propertyName))

        checkValidDataTypeDerivedFrom(propertyName, dataType.derivedFrom)

    }

    private fun checkPrimitiveOrComplex(dataType: String, propertyName: String): Boolean {
        if (BluePrintTypes.validPrimitiveTypes().contains(dataType) || checkDataType(dataType)) {
            return true
        } else {
            throw BluePrintException(format("DataType({}) for the property({}) is not valid", dataType, propertyName))
        }
    }

    private fun checkDataType(key: String): Boolean {
        return serviceTemplate.dataTypes?.containsKey(key) ?: false
    }

}