All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.swisscom.cloud.sb.broker.servicedefinition.ServiceDefinitionProcessor.groovy Maven / Gradle / Ivy

There is a newer version: 6.21.7
Show newest version
/*
 * Copyright (c) 2018 Swisscom (Switzerland) Ltd.
 *
 * 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 com.swisscom.cloud.sb.broker.servicedefinition

import com.fasterxml.jackson.databind.ObjectMapper
import com.swisscom.cloud.sb.broker.cfapi.dto.SchemasDto
import com.swisscom.cloud.sb.broker.error.ErrorCode
import com.swisscom.cloud.sb.broker.metrics.PlanMetricService
import com.swisscom.cloud.sb.broker.model.*
import com.swisscom.cloud.sb.broker.repository.*
import com.swisscom.cloud.sb.broker.servicedefinition.dto.ServiceDto
import com.swisscom.cloud.sb.broker.util.JsonHelper
import com.swisscom.cloud.sb.broker.util.JsonSchemaHelper
import org.slf4j.Logger
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.stereotype.Component
import org.springframework.transaction.annotation.Transactional

@Component
@Transactional
class ServiceDefinitionProcessor {
    private static final Logger LOGGER = LoggerFactory.getLogger(ServiceDefinitionProcessor.class)

    @Autowired
    private CFServiceRepository cfServiceRepository
    @Autowired
    private PlanRepository planRepository
    @Autowired
    private ServiceInstanceRepository serviceInstanceRepository
    @Autowired
    private TagRepository tagRepository
    @Autowired
    private PlanMetadataRepository planMetadataRepository
    @Autowired
    private CFServiceMetaDataRepository cfServiceMetaDataRepository
    @Autowired
    private CFServicePermissionRepository servicePermissionRepository
    @Autowired
    private ParameterRepository parameterRepository

    @Autowired
    ObjectMapper objectMapper

    @Autowired
    private List planBasedMetricServices

    def createOrUpdateServiceDefinitionFromYaml(ServiceDto serviceDto) {
        CFService service = processServiceBasicDefiniton(serviceDto)
        processServiceTags(service, serviceDto)
        processServiceMetadata(service, serviceDto)
        processServicePermissions(service, serviceDto)
        processPlans(service, serviceDto)
    }

    private CFService processServiceBasicDefiniton(serviceJson) {
        CFService service = findOrCreateService(serviceJson)
        service.name = serviceJson.name
        service.description = serviceJson.description
        service.bindable = serviceJson.bindable
        service.active = serviceJson.active ?: true
        service.internalName = serviceJson.internalName
        service.serviceProviderClass = serviceJson.serviceProviderClass
        service.displayIndex = serviceJson.displayIndex
        service.asyncRequired = serviceJson.asyncRequired
        service.plan_updateable = serviceJson.plan_updateable
        service.instancesRetrievable = serviceJson.instancesRetrievable
        service.bindingsRetrievable = serviceJson.bindingsRetrievable
        // dashboard items
        if (serviceJson.dashboard_client) {
            service.dashboardClientId = serviceJson.dashboard_client.id
            service.dashboardClientSecret = serviceJson.dashboard_client.secret
            service.dashboardClientRedirectUri = serviceJson.dashboard_client.redirect_uri
        } else {
            service.dashboardClientId = null
            service.dashboardClientSecret = null
            service.dashboardClientRedirectUri = null
        }
        return cfServiceRepository.saveAndFlush(service)
    }

    private CFService findOrCreateService(serviceJson) {
        CFService service = cfServiceRepository.findByGuid(serviceJson.guid)
        if (!service) {
            service = new CFService()
            service.guid = serviceJson.guid
            return cfServiceRepository.saveAndFlush(service)
        }
        return service
    }

    private def processServiceTags(CFService service, serviceJson) {
        removeServiceTags(service)
        addNewServiceTagsFromJson(serviceJson, service)
    }

    private void addNewServiceTagsFromJson(serviceJson, CFService service) {
        serviceJson.tags.each {
            def tag = new Tag(tag: it)
            tagRepository.save(tag)
            service.tags.add(tag)
        }
        cfServiceRepository.save(service)
    }

    private void removeServiceTags(CFService service) {
        if (service.tags) {
            copyOf(service.tags).each {
                service.tags.remove(it)
                tagRepository.delete(it)
            }
            cfServiceRepository.saveAndFlush(service)
        }
    }

    private void processServiceMetadata(CFService service, serviceJson) {
        removeServiceMetadata(service)
        addNewServiceMetadataFromJson(serviceJson, service)
    }

    private void removeServiceMetadata(CFService service) {
        if (service.metadata) {
            copyOf(service.metadata).each {
                service.metadata.remove(it)
                cfServiceMetaDataRepository.delete(it)
            }
            cfServiceRepository.saveAndFlush(service)
        }
    }

    private def demapify(Object value) {
        if (value instanceof LinkedHashMap) {
            return ((LinkedHashMap) value).values().toArray()
        }

        return value;
    }

    private void addNewServiceMetadataFromJson(serviceJson, CFService service) {
        serviceJson.metadata.each {
            k, v ->
                def value = demapify(v)
                // Do not use ObjectMapper for String values because it will add quotes
                def serializedObject = (value.getClass() == String.class) ? value : objectMapper.writeValueAsString(
                        value)
                def serviceMetadata = new CFServiceMetadata(key: k,
                                                            value: serializedObject,
                                                            type: value.getClass().name)
                cfServiceMetaDataRepository.saveAndFlush(serviceMetadata)
                service.metadata.add(serviceMetadata)
                cfServiceRepository.saveAndFlush(service)
        }
    }

    private processServicePermissions(CFService service, serviceJson) {
        removeServicePermissions(service)
        addNewServicePermissionsFromJson(serviceJson, service)
    }

    private void removeServicePermissions(CFService service) {
        if (service.permissions) {
            copyOf(service.permissions).each {
                service.permissions.remove(it)
                servicePermissionRepository.delete(it)
            }
            cfServiceRepository.saveAndFlush(service)
        }
    }

    private void addNewServicePermissionsFromJson(serviceJson, CFService service) {
        serviceJson.requires.each {
            def permission = new CFServicePermission(permission: it)
            servicePermissionRepository.saveAndFlush(permission)
            service.permissions.add(permission)
        }
        cfServiceRepository.saveAndFlush(service)
    }

    private void processPlans(CFService service, serviceJson) {
        processExistingPlans(service, serviceJson)
        processNewPlansFromJson(serviceJson, service)
    }

    private void processExistingPlans(CFService service, serviceJson) {
        copyOf(service.plans).each {
            Plan plan ->
                removePlanIfNotContainedInJsonAndNotInUse(plan, service, serviceJson)
        }
    }

    private void removePlanIfNotContainedInJsonAndNotInUse(Plan plan, CFService service, serviceJson) {
        if (isPlanIncludedInJson(serviceJson, plan)) {
            return
        }

        if (!isPlanInUse(plan)) {
            LOGGER.warn("Plan:${plan.guid} will be removed(there are no service instances with this plan.")
            service.plans.remove(plan)
            cfServiceRepository.saveAndFlush(service)
            planRepository.delete(plan)
        } else {
            plan.active = false
            planRepository.saveAndFlush(plan)
        }
    }

    private static boolean isPlanIncludedInJson(serviceJson, Plan plan) {
        return serviceJson.plans.find {plan.guid == it.guid}
    }


    private boolean isPlanInUse(Plan plan) {
        return !serviceInstanceRepository.findByPlan(plan).isEmpty()
    }

    private Object processNewPlansFromJson(serviceJson, CFService service) {
        return serviceJson.plans.each {
            Object planJson ->
                Plan plan = processPlanBasicDefinition(service, planJson)
                processPlanParameters(plan, planJson)
                processPlanMetadata(plan, planJson)
                planBasedMetricServices.each { mS -> mS.bindMetricsPerPlan(plan) }
        }
    }

    private Plan processPlanBasicDefinition(CFService service, planJson) {
        Plan plan = createPlanIfDoesNotExist(planJson, service)
        plan.service = service
        plan.name = planJson.name
        plan.description = planJson.description
        plan.templateUniqueIdentifier = planJson.templateId
        plan.templateVersion = planJson.templateVersion
        plan.active = planJson.active ?: true
        plan.free = planJson.free
        plan.displayIndex = planJson.displayIndex
        plan.internalName = planJson.internalName
        plan.serviceProviderClass = planJson.serviceProviderClass
        plan.asyncRequired = planJson.asyncRequired
        plan.maxBackups = planJson.maxBackups

        def om = new ObjectMapper()
        if (planJson.schemas != null) {
            SchemasDto schemaDto = om.readValue(om.writeValueAsString(planJson.schemas), SchemasDto.class)

            validateJsonSchema(schemaDto.serviceInstanceSchema?.createMethodSchema?.configParametersSchema,
                               'ServiceInstanceCreate')
            validateJsonSchema(schemaDto.serviceInstanceSchema?.updateMethodSchema?.configParametersSchema,
                               'ServiceInstanceUpdate')
            validateJsonSchema(schemaDto.serviceBindingSchema?.createMethodSchema?.configParametersSchema,
                               'ServiceBindingCreate')

            plan.serviceInstanceCreateSchema =  JsonHelper.
                    toJsonString(schemaDto.serviceInstanceSchema?.createMethodSchema?.configParametersSchema)
            plan.serviceInstanceUpdateSchema = JsonHelper.
                    toJsonString(schemaDto.serviceInstanceSchema?.updateMethodSchema?.configParametersSchema)
            plan.serviceBindingCreateSchema = JsonHelper.
                    toJsonString(schemaDto.serviceBindingSchema?.createMethodSchema?.configParametersSchema)
        }

        return planRepository.saveAndFlush(plan)
    }

    private def validateJsonSchema(Object o, String schemaName) {
        def json = JsonHelper.toJsonString(o)
        if (json) {
            def validationMessages = JsonSchemaHelper.validateJson(json)
            if (!validationMessages.isEmpty()) {
                LOGGER.error("Invalid schema for ${schemaName}: " + JsonHelper.toJsonString(validationMessages))
                ErrorCode.INVALID_PLAN_SCHEMAS.throwNew()
            }
        }
    }

    private Plan createPlanIfDoesNotExist(planJson, CFService service) {
        Plan plan = planRepository.findByGuid(planJson.guid)
        if (!plan) {
            plan = new Plan(guid: planJson.guid)
            planRepository.saveAndFlush(plan)
            service.plans.add(plan)
            cfServiceRepository.saveAndFlush(service)
        }
        return plan
    }

    private void processPlanParameters(Plan plan, planJson) {
        removeExistingPlanParameters(plan)
        addPlanParametersFromJson(planJson, plan)
    }

    private void removeExistingPlanParameters(Plan plan) {
        if (plan.parameters) {
            copyOf(plan.parameters).each {
                plan.parameters.remove(it)
                planRepository.saveAndFlush(plan)
                parameterRepository.delete(it)
            }
        }
    }

    private void addPlanParametersFromJson(planJson, Plan plan) {
        def params = []
        if (planJson.containerParams) {
            params.addAll(planJson.containerParams)
        }
        if (planJson.parameters) {
            params.addAll(planJson.parameters)
        }

        params.each {
            def param ->
                Parameter parameter = new Parameter(template: param.template,
                                                    name: param.name,
                                                    value: param.value)
                parameterRepository.saveAndFlush(parameter)
                plan.parameters.add(parameter)
        }
        planRepository.saveAndFlush(plan)
    }

    private void processPlanMetadata(Plan plan, planJson) {
        removeExistingPlanMetadata(plan)
        addPlanMetadataFromJson(planJson, plan)
    }

    private void removeExistingPlanMetadata(Plan plan) {
        if (plan.metadata) {
            copyOf(plan.metadata).each {
                plan.metadata.remove(it)
                planRepository.saveAndFlush(plan)
                planMetadataRepository.delete(it)
            }
        }
    }

    private void addPlanMetadataFromJson(planJson, Plan plan) {
        planJson.metadata.each {
            k, v ->
                def value = demapify(v)
                // Do not use ObjectMapper for String values because it will add quotes
                def serializedObject = (value.getClass() == String.class) ? value : objectMapper.writeValueAsString(
                        value)
                def planMetadata = new PlanMetadata(key: k, value: serializedObject, type: value.getClass().name)
                planMetadataRepository.saveAndFlush(planMetadata)
                plan.metadata.add(planMetadata)
        }
        planRepository.saveAndFlush(plan)
    }


    private static HashSet copyOf(collection2copy) {
        new HashSet<>(collection2copy)
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy