org.metaeffekt.dcc.commons.spring.xml.DCCConfigurationBeanDefinitionParser.java Source code

Java tutorial

Introduction

Here is the source code for org.metaeffekt.dcc.commons.spring.xml.DCCConfigurationBeanDefinitionParser.java

Source

/**
 * Copyright 2009-2017 the original author or authors.
 *
 * 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.metaeffekt.dcc.commons.spring.xml;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Properties;
import java.util.Set;
import java.util.UUID;

import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.BeanCreationException;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.RuntimeBeanReference;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.beans.factory.support.ManagedList;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.AbstractBeanDefinitionParser;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.core.io.ResourceLoader;
import org.springframework.util.xml.DomUtils;
import org.w3c.dom.Element;

import org.metaeffekt.dcc.commons.DccConstants;
import org.metaeffekt.dcc.commons.DccProperties;
import org.metaeffekt.dcc.commons.ant.PropertyUtils;
import org.metaeffekt.dcc.commons.commands.Commands;
import org.metaeffekt.dcc.commons.domain.Id;
import org.metaeffekt.dcc.commons.domain.Type.CapabilityId;
import org.metaeffekt.dcc.commons.domain.Type.PackageId;
import org.metaeffekt.dcc.commons.mapping.Attribute;
import org.metaeffekt.dcc.commons.mapping.Attribute.AttributeType;
import org.metaeffekt.dcc.commons.mapping.AttributeKey;
import org.metaeffekt.dcc.commons.mapping.AttributeMapper;
import org.metaeffekt.dcc.commons.mapping.Capability;
import org.metaeffekt.dcc.commons.mapping.CapabilityDefinition;
import org.metaeffekt.dcc.commons.mapping.CapabilityDefinitionExtensionReference;
import org.metaeffekt.dcc.commons.mapping.CapabilityDefinitionReference;
import org.metaeffekt.dcc.commons.mapping.CommandDefinition;
import org.metaeffekt.dcc.commons.mapping.ConfigurationUnit;
import org.metaeffekt.dcc.commons.mapping.ExpressionAttributeMapper;
import org.metaeffekt.dcc.commons.mapping.HostRestriction;
import org.metaeffekt.dcc.commons.mapping.IsTrueAssert;
import org.metaeffekt.dcc.commons.mapping.Mapping;
import org.metaeffekt.dcc.commons.mapping.PrerequisiteAssert;
import org.metaeffekt.dcc.commons.mapping.Profile;
import org.metaeffekt.dcc.commons.mapping.Provision;
import org.metaeffekt.dcc.commons.mapping.ProvisionRestriction;
import org.metaeffekt.dcc.commons.mapping.RequiredCapability;
import org.metaeffekt.dcc.commons.mapping.SourceToTargetCapabilityAttributeMapper;
import org.metaeffekt.dcc.commons.mapping.UniqueAssert;
import org.metaeffekt.dcc.commons.mapping.UnitToCapabilityAttributeMapper;

public class DCCConfigurationBeanDefinitionParser extends AbstractBeanDefinitionParser {

    static final String THE_ONE_TRUE_PROFILE_BEAN_NAME = Profile.class.getName();

    static final String ELEMENT_ID = "id";
    static final String ELEMENT_DEPLOYMENT_ID = "deployment-id";
    static final String ELEMENT_TYPE = "type";
    static final String ELEMENT_DESCRIPTION = "description";
    static final String ELEMENT_UNIT_ATTRIBUTES_ATTRIBUTE = "attribute";
    static final String ELEMENT_UNIT_ATTRIBUTES = "attributes";
    static final String ELEMENT_PROPERTIES = "properties";
    static final String ELEMENT_PROPS_LOCATION_ATTRIBUTE = "location";

    // XML elements
    static final String ELEMENT_IF = "if";
    static final String ELEMENT_IMPORT = "import";
    static final String ELEMENT_PROFILE = "profile";
    static final String ELEMENT_AUTO_BIND = "auto-bind";
    static final String ELEMENT_CAPABILITY_DEF = "capability-definition";
    static final String ELEMENT_INHERIT_DEF = "inherit";

    static final String ELEMENT_ATTRIBUTE_KEY = "attribute-key";
    static final String ELEMENT_UNIT = "unit";
    static final String ELEMENT_UNIT_REQUIRED_CAPABILITY = "required-capability";
    static final String ELEMENT_UNIT_PROVIDED_CAPABILITY = "provided-capability";
    static final String ELEMENT_UNIT_COMMAND = "command";
    static final String ELEMENT_ASSERTS = "asserts";

    static final String ELEMENT_UNIT_MAPPINGS = "mappings";
    static final String ELEMENT_BINDING = "binding";
    static final String ELEMENT_BINDING_TARGET = "target";

    static final String ELEMENT_BINDING_SOURCE = "source";
    // XML attributes
    static final String ATTRIBUTE_KEY_OPTIONAL = "optional";
    static final String ATTRIBUTE_KEY_DEFAULT = "default";
    static final String ATTRIBUTE_KEY_MULTIPLE_BINDINGS = "multipleBindingsAllowed";
    static final String ATTRIBUTE_KEY_IDENTIFIES_HOST = "identifiesHost";
    static final String ATTRIBUTE_IDENTIFIES_HOST = "identifiesHost";

    static final String ATTRIBUTE_ATTRIBUTE_KEY_KEY = "key";
    static final String ATTRIBUTE_UNIT_EXTENDS = "extends";
    static final String ATTRIBUTE_UNIT_ABSTRACT = "abstract";
    static final String ATTRIBUTE_UNIT_TYPE = "type";
    static final String ATTRIBUTE_CAPABILITY_DEFINITION_REF = "definitionRef";

    static final String ATTRIBUTE_PREFIX = "prefix";

    // Bean/Java properties
    static final String PROPERTY_ORIGIN = "origin";
    static final String PROPERTY_UNIT_PARENT_ID = "parentId";
    static final String PROPERTY_UNIT_ABSTRACT = "abstract";
    static final String PROPERTY_UNIT_PROVIDED_CAPABILITIES = "providedCapabilities";
    static final String PROPERTY_UNIT_REQUIRED_CAPABILITIES = "requiredCapabilities";
    static final String PROPERTY_CAPABILITY_DEF_ATTRIBUTE_KEYS = "attributeKeys";
    static final String PROPERTY_CAPABILITY_DEF_ANCESTORS = "ancestors";
    static final String PROPERTY_UNIT_MAPPINGS = ELEMENT_UNIT_MAPPINGS;
    static final String PROPERTY_UNIT_COMMANDS = "commands";
    static final String PROPERTY_UNIT_ASSERTS = "asserts";
    static final String PROPERTY_UNIT_ATTRIBUTES = ELEMENT_UNIT_ATTRIBUTES;
    static final String PROPERTY_DESCRIPTION = "description";
    static final String PROPERTY_CONTRIBUTIONS = "contributions";
    static final String PROPERTY_CAPABILITIES = "capabilities";
    static final String PROPERTY_REQUISITIONS = "requisitions";
    static final String PROPERTY_PROVISIONS = "provisions";
    static final String PROPERTY_DEPLOYMENT_PROPERTIES_PATH = "deploymentPropertiesPath";
    static final String PROPERTY_SOLUTION_PROPERTIES_PATH = "solutionPropertiesPath";
    static final String PROPERTY_SOLUTION_DIR = "solutionDir";

    static final String CAPABILITY_DEFINITION_NAME_PREFIX = "capabilityDefinition#";

    public static final IfBeanDefinitionParser IF_BEAN_DEFINITION_PARSER = new IfBeanDefinitionParser();
    public static final ImportBeanDefinitionParser IMPORT_BEAN_DEFINITION_PARSER = new ImportBeanDefinitionParser();

    @Override
    protected AbstractBeanDefinition parseInternal(Element profileElement, ParserContext parserContext) {

        BeanDefinitionRegistry registry = parserContext.getRegistry();

        registerAutoBindBeanFactoryPostProcessorIfRequired(profileElement, registry);
        registerDependencyGraphCalculatingBeanFactoryPostProcessor(registry);
        registerProfileValidationBeanFactoryPostProcessor(registry);
        registerCapabilityInheritancePostProcessor(registry);

        String profileElementId = extractProfileId(profileElement);
        String deploymentId = extractDeploymentId(profileElement, parserContext);
        String profileElementDescription = extractDescription(profileElement, registry);
        String solutionPropertiesPath = extractSolutionPropertiesPath(profileElement, parserContext);
        String deploymentPropertiesPath = extractDeploymentPropertiesPath(profileElement, parserContext);
        File origin = determineOrigin(profileElementId, parserContext);
        String profileType = extractType(profileElement);

        if ("deployment".equalsIgnoreCase(profileType)) {
            if (deploymentId == null) {
                // fallback to profile id
                deploymentId = profileElementId;
            }
        }

        // evaluate path
        if (solutionPropertiesPath != null || deploymentPropertiesPath != null) {
            try {
                // derive path from currently parsed file
                File resourceFile = parserContext.getReaderContext().getResource().getFile();
                File parentFile = resourceFile.getParentFile();

                // apply to solution properties if required
                if (!StringUtils.isEmpty(solutionPropertiesPath)) {
                    solutionPropertiesPath = new File(parentFile, solutionPropertiesPath).getPath();
                }
                if (!StringUtils.isEmpty(deploymentPropertiesPath)) {
                    deploymentPropertiesPath = new File(parentFile, deploymentPropertiesPath).getPath();
                }
            } catch (Exception e) {
                throw new IllegalStateException("Cannot resolve path for profile properties.", e);
            }
        }

        deploymentId = overwriteDeploymentIdFromDeploymentProperties(deploymentPropertiesPath, deploymentId);

        registerProfileFactoryBeanIfRequired(profileElementId, deploymentId, profileType, profileElementDescription,
                origin, registry, solutionPropertiesPath, deploymentPropertiesPath, parserContext);

        parseImportsAndIfs(profileElement, parserContext);
        parseAndRegisterCapabilityDefinitions(profileElement, origin, registry, parserContext);

        parseAndRegisterUnits(profileElement, origin, registry, parserContext);
        parseAndRegisterBindings(profileElement, origin, registry, parserContext);
        parseAndRegisterGlobalAsserts(profileElement, registry);

        return null;
    }

    private String overwriteDeploymentIdFromDeploymentProperties(String deploymentPropertiesPath,
            String deploymentId) {
        if (deploymentPropertiesPath != null) {
            File deploymentPropertiesFile = new File(deploymentPropertiesPath);
            if (deploymentPropertiesFile.exists()) {
                Properties p = PropertyUtils.loadPropertyFile(deploymentPropertiesFile);
                deploymentId = p.getProperty(DccProperties.DCC_DEPLOYMENT_ID, deploymentId);
            }
        }
        return deploymentId;
    }

    private void parseAndRegisterGlobalAsserts(Element profileElement, BeanDefinitionRegistry registry) {
        final ManagedList<AbstractBeanDefinition> asserts = parseAsserts(profileElement, registry);
        for (AbstractBeanDefinition def : asserts) {
            //TODO generate unique name
            registry.registerBeanDefinition(UUID.randomUUID().toString(), def);
        }
    }

    private boolean deploymentProfile(Element profileElement) {
        return Profile.Type.DEPLOYMENT.name().equalsIgnoreCase(extractType(profileElement));
    }

    private boolean autoBindIsEnabled(Element profileElement) {
        Element autoBindElement = DomUtils.getChildElementByTagName(profileElement, ELEMENT_AUTO_BIND);
        return (autoBindElement != null);
    }

    private String extractType(Element profileElement) {
        Element concreteTypeElement = profileTypeElement(profileElement);
        if (concreteTypeElement != null) {
            return concreteTypeElement.getNodeName();
        }
        return Profile.Type.BASE.name();
    }

    private String extractProfileId(Element profileElement) {
        String profileId = DomUtils.getChildElementValueByTagName(profileElement, ELEMENT_ID);
        if (StringUtils.isEmpty(profileId)) {
            throw new BeanCreationException("A profile must have an <id> element with a profile id as value");
        }
        return profileId;
    }

    private String extractDeploymentPropertiesPath(Element profileElement, ParserContext parserContext) {
        if (Profile.Type.DEPLOYMENT.name().equalsIgnoreCase(extractType(profileElement))) {
            Element profileTypeElement = profileTypeElement(profileElement);
            return extractAttributeFromOptionalElement(profileTypeElement, parserContext, ELEMENT_PROPERTIES,
                    ELEMENT_PROPS_LOCATION_ATTRIBUTE);
        }
        return null;
    }

    private Element profileTypeElement(Element profileElement) {
        Element profileTypeElement = DomUtils.getChildElementByTagName(profileElement, ELEMENT_TYPE);
        if (profileTypeElement != null) {
            Element concreteTypeElement = DomUtils.getChildElements(profileTypeElement).get(0);
            return concreteTypeElement;
        }
        return null;
    }

    private String extractSolutionPropertiesPath(Element profileElement, ParserContext parserContext) {
        if (Profile.Type.SOLUTION.name().equalsIgnoreCase(extractType(profileElement))) {
            Element profileTypeElement = profileTypeElement(profileElement);
            return extractAttributeFromOptionalElement(profileTypeElement, parserContext, ELEMENT_PROPERTIES,
                    ELEMENT_PROPS_LOCATION_ATTRIBUTE);
        }
        return null;
    }

    private String extractDeploymentId(Element profileElement, ParserContext parserContext) {
        Element element = profileTypeElement(profileElement);
        if (element != null) {
            final Element deploymentIdElem = DomUtils.getChildElementByTagName(element, ELEMENT_ID);
            if (deploymentIdElem != null) {
                String value = deploymentIdElem.getTextContent();
                if (!StringUtils.isBlank(value)) {
                    return value;
                }
            }
        }
        return null;
    }

    private String extractAttributeFromOptionalElement(Element parentElement, ParserContext parserContext,
            String elementName, String attributeName) {
        Element childElement = DomUtils.getChildElementByTagName(parentElement, elementName);
        return childElement != null ? parseString(childElement, attributeName, null, parserContext.getRegistry())
                : StringUtils.EMPTY;
    }

    private String extractDescription(Element element, BeanDefinitionRegistry registry) {
        String description = DomUtils.getChildElementValueByTagName(element, ELEMENT_DESCRIPTION);
        description = description != null ? description : "";
        return replaceVariables(registry, description);
    }

    private void registerDependencyGraphCalculatingBeanFactoryPostProcessor(BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(DependencyGraphCalculatingBeanFactoryPostProcessor.class);
        registry.registerBeanDefinition("dependencyGraphCalculatingBeanFactoryPostProcessor",
                builder.getBeanDefinition());
    }

    private void registerProfileValidationBeanFactoryPostProcessor(BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(ProfileValidationBeanFactoryPostProcessor.class);
        registry.registerBeanDefinition("profileValidationBeanFactoryPostProcessor", builder.getBeanDefinition());
    }

    private void registerAutoBindBeanFactoryPostProcessorIfRequired(Element profileElement,
            BeanDefinitionRegistry registry) {

        if (deploymentProfile(profileElement) && autoBindIsEnabled(profileElement)) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder
                    .genericBeanDefinition(AutoBindFactoryPostProcessor.class);
            registry.registerBeanDefinition("autoBindBeanFactoryPostProcessor", builder.getBeanDefinition());
        }
    }

    private void registerCapabilityInheritancePostProcessor(BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder
                .genericBeanDefinition(CapabilityInheritanceBeanDefinitionPostProcessor.class);
        registry.registerBeanDefinition("capabilityInheritanceBeanDefinitionPostProcessor",
                builder.getBeanDefinition());
    }

    private void registerProfileFactoryBeanIfRequired(String profileId, String deploymentId, String profileType,
            String profileDescription, File origin, BeanDefinitionRegistry registry, String solutionPropertiesPath,
            String deploymentPropertiesPath, ParserContext parserContext) {
        if (!registry.containsBeanDefinition(THE_ONE_TRUE_PROFILE_BEAN_NAME)) {
            BeanDefinitionBuilder profileFactoryBuilder = BeanDefinitionBuilder
                    .rootBeanDefinition(ProfileFactoryBean.class);
            profileFactoryBuilder.addConstructorArgValue(Id.createProfileId(profileId));
            profileFactoryBuilder.addConstructorArgValue(Id.createDeploymentId(deploymentId));
            profileFactoryBuilder.addConstructorArgValue(profileType);
            profileFactoryBuilder.addConstructorArgValue(profileDescription);
            profileFactoryBuilder.addConstructorArgValue(origin);
            profileFactoryBuilder.addPropertyValue(PROPERTY_SOLUTION_PROPERTIES_PATH, solutionPropertiesPath);
            profileFactoryBuilder.addPropertyValue(PROPERTY_DEPLOYMENT_PROPERTIES_PATH, deploymentPropertiesPath);

            final ResourceLoader resourceLoader = parserContext.getReaderContext().getResourceLoader();
            if (resourceLoader instanceof ProfileApplicationContext) {
                File solutionDir = ((ProfileApplicationContext) resourceLoader).getSolutionDir();
                profileFactoryBuilder.addPropertyValue(PROPERTY_SOLUTION_DIR, solutionDir);
            }

            registry.registerBeanDefinition(THE_ONE_TRUE_PROFILE_BEAN_NAME,
                    profileFactoryBuilder.getBeanDefinition());

            RootBeanDefinition factoryBean = (RootBeanDefinition) registry
                    .getBeanDefinition(THE_ONE_TRUE_PROFILE_BEAN_NAME);
            factoryBean.setAttribute("builder", profileFactoryBuilder);
        } else {
            RootBeanDefinition factoryBean = (RootBeanDefinition) registry
                    .getBeanDefinition(THE_ONE_TRUE_PROFILE_BEAN_NAME);
            BeanDefinitionBuilder builder = (BeanDefinitionBuilder) factoryBean.getAttribute("builder");
            if (solutionPropertiesPath != null) {
                builder.addPropertyValue(PROPERTY_SOLUTION_PROPERTIES_PATH, solutionPropertiesPath);
            }
            if (deploymentPropertiesPath != null) {
                builder.addPropertyValue(PROPERTY_DEPLOYMENT_PROPERTIES_PATH, deploymentPropertiesPath);
            }
        }
    }

    private void parseImportsAndIfs(Element profileElement, ParserContext parserContext) {
        List<Element> importElements = DomUtils.getChildElementsByTagName(profileElement, ELEMENT_IMPORT);
        for (Element singleImportElement : importElements) {
            IMPORT_BEAN_DEFINITION_PARSER.parse(singleImportElement, parserContext);
        }

        List<Element> ifElements = DomUtils.getChildElementsByTagName(profileElement, ELEMENT_IF);
        for (Element ifElement : ifElements) {
            IF_BEAN_DEFINITION_PARSER.parse(ifElement, parserContext);
        }
    }

    private void parseAndRegisterCapabilityDefinitions(Element profileElement, File origin,
            BeanDefinitionRegistry registry, ParserContext parserContext) {
        List<Element> capabilityDefinitionElements = DomUtils.getChildElementsByTagName(profileElement,
                ELEMENT_CAPABILITY_DEF);
        for (Element capabilityDefElement : capabilityDefinitionElements) {
            parseAndRegisterSingleCapabilityDefinition(capabilityDefElement, origin, parserContext, registry);
        }
    }

    private void parseAndRegisterSingleCapabilityDefinition(Element capabilityDefElement, File origin,
            ParserContext parserContext, BeanDefinitionRegistry registry) {

        String capabilityDefId = extractMandatoryIdAttributeFromElement(capabilityDefElement,
                "CapabilityDefinition", registry);

        String description = extractDescription(capabilityDefElement, registry);

        String abstractString = capabilityDefElement.getAttribute(PROPERTY_UNIT_ABSTRACT);
        boolean _abstract = StringUtils.equals("true", abstractString) ? true : false;

        List<CapabilityDefinitionReference> ancestors = parseInheritDefinitionElements(capabilityDefElement,
                registry);
        ManagedList<AttributeKey> attributeKeysList = parseAttributeKeyElements(capabilityDefElement,
                parserContext);

        BeanDefinitionBuilder capabilityBeanDefBuilder = BeanDefinitionBuilder
                .rootBeanDefinition(CapabilityDefinition.class);
        capabilityBeanDefBuilder.addConstructorArgValue(capabilityDefId);
        capabilityBeanDefBuilder.addPropertyValue(PROPERTY_ORIGIN, origin);
        capabilityBeanDefBuilder.addPropertyValue(PROPERTY_DESCRIPTION, description);
        capabilityBeanDefBuilder.addPropertyValue(PROPERTY_CAPABILITY_DEF_ANCESTORS, ancestors);
        capabilityBeanDefBuilder.addPropertyValue(PROPERTY_CAPABILITY_DEF_ATTRIBUTE_KEYS, attributeKeysList);
        capabilityBeanDefBuilder.addPropertyValue(PROPERTY_UNIT_ABSTRACT, _abstract);

        registry.registerBeanDefinition(createCapabilityDefBeanName(capabilityDefId),
                capabilityBeanDefBuilder.getBeanDefinition());
    }

    private String extractMandatoryIdAttributeFromElement(Element element, String elementName,
            BeanDefinitionRegistry registry) {
        String idAttribute = element.getAttribute(ID_ATTRIBUTE);
        if (StringUtils.isBlank(idAttribute)) {
            throw new BeanCreationException(String.format("%s is defined with a blank ID.", elementName));
        }
        return replaceVariables(registry, idAttribute);
    }

    private String replaceVariables(BeanDefinitionRegistry registry, String string) {
        if (string != null && string.contains("${")) {
            final Properties properties = ImportBeanDefinitionParser.getParsingContextProperties(registry);
            final Set<String> names = properties.stringPropertyNames();
            for (String key : names) {
                string = string.replaceAll("\\$\\{" + key + "\\}", properties.getProperty(key));
            }
        }
        return string;
    }

    private List<CapabilityDefinitionReference> parseInheritDefinitionElements(Element capabilityDefElement,
            BeanDefinitionRegistry registry) {

        Set<CapabilityDefinitionReference> distinctAncestors = new HashSet<>();

        List<Element> inheritElements = DomUtils.getChildElementsByTagName(capabilityDefElement,
                ELEMENT_INHERIT_DEF);
        for (Element inheritElement : inheritElements) {
            String referencedCapabilityId = inheritElement.getAttribute(ATTRIBUTE_CAPABILITY_DEFINITION_REF);
            String prefix = parseString(inheritElement, ATTRIBUTE_PREFIX, null, registry);

            CapabilityDefinitionReference capRef = new CapabilityDefinitionReference(referencedCapabilityId,
                    prefix);
            distinctAncestors.add(capRef);
        }

        List<CapabilityDefinitionReference> ancestors = new ArrayList<>(distinctAncestors.size());
        ancestors.addAll(distinctAncestors);
        return ancestors;
    }

    private ManagedList<AttributeKey> parseAttributeKeyElements(Element capabilityDefElement,
            ParserContext parserContext) {

        Set<AttributeKey> attributeKeys = new HashSet<>();

        List<Element> attributeKeyElements = DomUtils.getChildElementsByTagName(capabilityDefElement,
                ELEMENT_ATTRIBUTE_KEY);
        for (Element attributeKey : attributeKeyElements) {
            String key = attributeKey.getAttribute(ATTRIBUTE_ATTRIBUTE_KEY_KEY);
            File origin = determineOrigin(key, parserContext);
            String description = extractDescription(attributeKey, parserContext.getRegistry());
            String defaultValue = parseString(attributeKey, ATTRIBUTE_KEY_DEFAULT, null,
                    parserContext.getRegistry());
            boolean optional = Boolean.valueOf(attributeKey.getAttribute(ATTRIBUTE_KEY_OPTIONAL));
            if (StringUtils.isNotBlank(key)) {
                attributeKeys.add(new AttributeKey(key, origin, description, optional, defaultValue));
            }
        }
        ManagedList<AttributeKey> attributeKeysList = new ManagedList<>(attributeKeys.size());
        attributeKeysList.addAll(attributeKeys);
        return attributeKeysList;
    }

    static String createCapabilityDefBeanName(String capabilityDefId) {
        return CAPABILITY_DEFINITION_NAME_PREFIX + capabilityDefId;
    }

    private void parseAndRegisterUnits(Element profileElement, File origin, BeanDefinitionRegistry registry,
            ParserContext parserContext) {

        List<Element> unitElements = DomUtils.getChildElementsByTagName(profileElement, ELEMENT_UNIT);
        for (Element unitElement : unitElements) {
            // first retrieve all XML attributes of the Unit element
            String unitId = extractMandatoryIdAttributeFromElement(unitElement, "Unit", registry);
            // TODO: currently not used as there is nowhere to set this on a ConfigurationUnit
            @SuppressWarnings("unused")
            String unitType = unitElement.getAttribute(ATTRIBUTE_UNIT_TYPE);
            boolean unitIsAbstract = Boolean.valueOf(unitElement.getAttribute(ATTRIBUTE_UNIT_ABSTRACT));
            String unitExtends = parseString(unitElement, ATTRIBUTE_UNIT_EXTENDS, null, registry);

            ManagedList<AbstractBeanDefinition> requiredCapabilityReferences = parseRequiredCapabilities(
                    unitElement, unitId, registry);
            ManagedList<AbstractBeanDefinition> providedCapabilityReferences = parseProvidedCapabilities(
                    unitElement, unitId, registry);
            ManagedList<Attribute> attributes = parseAttributesElement(unitElement, parserContext);
            ManagedList<AbstractBeanDefinition> mappings = parseMappingsElement(unitElement, unitId, registry);
            ManagedList<AbstractBeanDefinition> commands = parseCommands(unitElement, unitId, registry);
            ManagedList<AbstractBeanDefinition> asserts = parseAsserts(unitElement, registry);

            BeanDefinitionBuilder unitBeanDefBuilder = null;
            if (StringUtils.isNotBlank(unitExtends)) {
                unitBeanDefBuilder = BeanDefinitionBuilder.childBeanDefinition(unitExtends);
                unitBeanDefBuilder.getRawBeanDefinition().setBeanClass(ConfigurationUnit.class);
                requiredCapabilityReferences.setMergeEnabled(true);
                providedCapabilityReferences.setMergeEnabled(true);
                attributes.setMergeEnabled(true);
                mappings.setMergeEnabled(true);
                commands.setMergeEnabled(true);
                asserts.setMergeEnabled(true);

                unitBeanDefBuilder.addPropertyValue(PROPERTY_UNIT_PARENT_ID, unitExtends);
            } else {
                unitBeanDefBuilder = BeanDefinitionBuilder.rootBeanDefinition(ConfigurationUnit.class);
            }

            String description = extractDescription(unitElement, registry);

            unitBeanDefBuilder.setAbstract(false);
            unitBeanDefBuilder.addPropertyValue(PROPERTY_ORIGIN, origin);
            unitBeanDefBuilder.addPropertyValue(PROPERTY_DESCRIPTION, description);
            unitBeanDefBuilder.addPropertyValue(PROPERTY_UNIT_ABSTRACT, unitIsAbstract);
            unitBeanDefBuilder.addPropertyValue(PROPERTY_UNIT_PROVIDED_CAPABILITIES, providedCapabilityReferences);
            unitBeanDefBuilder.addPropertyValue(PROPERTY_UNIT_REQUIRED_CAPABILITIES, requiredCapabilityReferences);
            unitBeanDefBuilder.addPropertyValue(PROPERTY_UNIT_ATTRIBUTES, attributes);
            unitBeanDefBuilder.addPropertyValue(PROPERTY_UNIT_MAPPINGS, mappings);
            unitBeanDefBuilder.addPropertyValue(PROPERTY_UNIT_COMMANDS, commands);
            unitBeanDefBuilder.addPropertyValue(PROPERTY_UNIT_ASSERTS, asserts);

            unitBeanDefBuilder.addConstructorArgValue(unitId);
            RuntimeBeanReference unitBeanRef = new RuntimeBeanReference(unitId);
            unitBeanRef.setSource(parserContext.getReaderContext().extractSource(unitElement));

            registry.registerBeanDefinition(unitId, unitBeanDefBuilder.getBeanDefinition());
        }

    }

    private ManagedList<AbstractBeanDefinition> parseAsserts(Element parentElement,
            BeanDefinitionRegistry registry) {
        final ManagedList<AbstractBeanDefinition> asserts = new ManagedList<>();
        final List<Element> assertsElements = DomUtils.getChildElementsByTagName(parentElement, ELEMENT_ASSERTS);

        for (Element assertsElement : assertsElements) {
            parseUniqueAsserts(asserts, assertsElement, registry);
            parsePrerequisitesAsserts(asserts, assertsElement, registry);
            parseIsTrueAsserts(asserts, assertsElement, registry);
        }

        return asserts;
    }

    private void parsePrerequisitesAsserts(final ManagedList<AbstractBeanDefinition> asserts,
            Element assertsElement, BeanDefinitionRegistry registry) {
        final List<Element> uniqueAsserts = DomUtils.getChildElementsByTagName(assertsElement, "prerequisite");

        for (Element uniqueAssert : uniqueAsserts) {
            final BeanDefinitionBuilder assertBeanDefBuilder = BeanDefinitionBuilder
                    .genericBeanDefinition(PrerequisiteAssert.class);
            assertBeanDefBuilder.addConstructorArgValue(uniqueAssert.getAttribute("key"));
            assertBeanDefBuilder.addConstructorArgValue(parseString(uniqueAssert, "value", null, registry));
            asserts.add(assertBeanDefBuilder.getBeanDefinition());
        }
    }

    private void parseUniqueAsserts(final ManagedList<AbstractBeanDefinition> asserts, Element assertsElement,
            BeanDefinitionRegistry registry) {
        final List<Element> uniqueAsserts = DomUtils.getChildElementsByTagName(assertsElement, "unique");

        for (Element uniqueAssert : uniqueAsserts) {
            final BeanDefinitionBuilder assertBeanDefBuilder = BeanDefinitionBuilder
                    .genericBeanDefinition(UniqueAssert.class);
            assertBeanDefBuilder.addConstructorArgValue(parseString(uniqueAssert, "value", null, registry));
            assertBeanDefBuilder.addConstructorArgValue(parseString(uniqueAssert, "enabled", null, registry));
            assertBeanDefBuilder.addConstructorArgValue(parseString(uniqueAssert, "message", null, registry));
            asserts.add(assertBeanDefBuilder.getBeanDefinition());
        }
    }

    private void parseIsTrueAsserts(final ManagedList<AbstractBeanDefinition> asserts, Element assertsElement,
            BeanDefinitionRegistry registry) {
        final List<Element> uniqueAsserts = DomUtils.getChildElementsByTagName(assertsElement, "is-true");

        for (Element uniqueAssert : uniqueAsserts) {
            final BeanDefinitionBuilder assertBeanDefBuilder = BeanDefinitionBuilder
                    .genericBeanDefinition(IsTrueAssert.class);
            assertBeanDefBuilder.addConstructorArgValue(parseString(uniqueAssert, "value", null, registry));
            assertBeanDefBuilder.addConstructorArgValue(parseString(uniqueAssert, "message", null, registry));
            asserts.add(assertBeanDefBuilder.getBeanDefinition());
        }
    }

    private ManagedList<AbstractBeanDefinition> parseCommands(Element unitElement, String unitId,
            BeanDefinitionRegistry registry) {
        ManagedList<AbstractBeanDefinition> commands = new ManagedList<>();
        List<Element> commandElements = DomUtils.getChildElementsByTagName(unitElement, ELEMENT_UNIT_COMMAND);

        if (!commandElements.isEmpty()) {

            String defaultPackageRef = unitElement.getAttribute(DccConstants.PACKAGE);
            Id<PackageId> defaultPackageId = null;
            if (StringUtils.isNotEmpty(defaultPackageRef)) {
                defaultPackageId = Id.createPackageId(defaultPackageRef);
            }

            for (Element commandElement : commandElements) {
                final String commandTypeRef = commandElement.getAttribute(ATTRIBUTE_UNIT_TYPE);
                final Commands commandType = Commands.parseConfigurableCommand(commandTypeRef);
                if (commandType == null) {
                    throw new BeanCreationException(
                            String.format("Unsupported command type [%s].", commandTypeRef));
                }

                final Id<PackageId> packageId = parsePackageReferenceForCommand(defaultPackageId, commandElement,
                        unitId);

                final BeanDefinitionBuilder commandBeanDefBuilder = BeanDefinitionBuilder
                        .genericBeanDefinition(CommandDefinition.class);
                commandBeanDefBuilder.addConstructorArgValue(commandType);
                commandBeanDefBuilder.addConstructorArgValue(packageId);

                final List<CapabilityDefinitionReference> capabilities = parseCommandCapabilities(commandElement,
                        registry);
                final List<CapabilityDefinitionExtensionReference> contributions = parseCommandContributions(
                        commandElement, registry);
                final List<CapabilityDefinitionExtensionReference> requisitions = parseCommandRequisitions(
                        commandElement, registry);
                final List<Provision> provisions = parseCommandProvisions(commandElement, registry);

                commandBeanDefBuilder.addPropertyValue(PROPERTY_CAPABILITIES, capabilities);
                commandBeanDefBuilder.addPropertyValue(PROPERTY_CONTRIBUTIONS, contributions);
                commandBeanDefBuilder.addPropertyValue(PROPERTY_REQUISITIONS, requisitions);
                commandBeanDefBuilder.addPropertyValue(PROPERTY_PROVISIONS, provisions);

                commands.add(commandBeanDefBuilder.getBeanDefinition());
            }
        }

        return commands;
    }

    private Id<PackageId> parsePackageReferenceForCommand(Id<PackageId> defaultPackageId, Element commandElement,
            String unitId) {
        final Element packageElement = DomUtils.getChildElementByTagName(commandElement, DccConstants.PACKAGE);
        final String textContent = packageElement != null ? packageElement.getTextContent() : null;
        final String attributeContent = commandElement.getAttribute("package");
        final boolean isTextContentEmpty = StringUtils.isEmpty(textContent);
        final boolean isAttributeEmpty = StringUtils.isEmpty(attributeContent);

        if (!isTextContentEmpty && !isAttributeEmpty) {
            throw new BeanCreationException(String.format(
                    "Command should either specify sub element package [%s] or attribute package [%s].",
                    textContent, attributeContent));
        }

        Id<PackageId> packageId = defaultPackageId;
        if (!isAttributeEmpty) {
            packageId = Id.createPackageId(attributeContent);
        } else if (!isTextContentEmpty) {
            packageId = Id.createPackageId(textContent);
        }

        if (packageId == null) {
            throw new BeanCreationException(
                    String.format("No package reference defined for command [%s] of unit [%s]",
                            commandElement.getAttribute("type"), unitId));
        }

        return packageId;
    }

    private List<CapabilityDefinitionReference> parseCommandCapabilities(Element commandElement,
            BeanDefinitionRegistry registry) {
        final List<CapabilityDefinitionReference> parsedReferences = new ArrayList<>();

        final List<Element> matchedElements = DomUtils.getChildElementsByTagName(commandElement,
                DccConstants.CAPABILITY);
        for (Element element : matchedElements) {
            final CapabilityDefinitionReference ref = parseCapabilityDefinitionReferenceFromElement(
                    new CapabilityDefinitionReference(), element, "id", DccConstants.CAPABILITY, registry);
            if (ref != null) {
                parsedReferences.add(ref);
            }
        }

        return parsedReferences;
    }

    private <T extends CapabilityDefinitionReference> T parseCapabilityDefinitionReferenceFromElement(T template,
            Element element, String idColumnName, String elementName, BeanDefinitionRegistry registry) {
        final String textContent = element.getTextContent();
        final String id = parseString(element, idColumnName, null, registry);
        final boolean isTextContentEmpty = StringUtils.isEmpty(textContent);
        final boolean isIdAttributeEmpty = StringUtils.isEmpty(id);

        if (!isTextContentEmpty && !isIdAttributeEmpty) {
            throw new BeanCreationException(
                    String.format("%s should either specify text content [%s] or attribute %s [%s].", elementName,
                            textContent, idColumnName, id));
        } else if (isTextContentEmpty && isIdAttributeEmpty) {
            return null;
        }

        final String capabilityRef = isIdAttributeEmpty ? textContent : id;

        final String prefixText = parseString(element, "prefix", null, registry);
        final String prefix = StringUtils.isEmpty(prefixText) ? null : prefixText;

        template.setPrefix(prefix);
        template.setReferencedCapabilityDefId(capabilityRef);

        return template;
    }

    private List<CapabilityDefinitionExtensionReference> parseCommandContributions(Element commandElement,
            BeanDefinitionRegistry registry) {
        final List<CapabilityDefinitionExtensionReference> parsedReferences = new ArrayList<>();

        final List<Element> matchedElements = DomUtils.getChildElementsByTagName(commandElement,
                DccConstants.CONTRIBUTION);
        for (Element element : matchedElements) {
            final CapabilityDefinitionExtensionReference ref = parseCapabilityDefinitionExtensionReferences(element,
                    DccConstants.CONTRIBUTION, registry);
            if (ref != null) {
                parsedReferences.add(ref);
            }
        }

        return parsedReferences;
    }

    private List<Provision> parseCommandProvisions(Element commandElement, BeanDefinitionRegistry registry) {
        final List<Provision> parsedReferences = new ArrayList<>();

        final List<Element> matchedElements = DomUtils.getChildElementsByTagName(commandElement,
                DccConstants.PROVISION);
        for (Element element : matchedElements) {
            final CapabilityDefinitionExtensionReference ref = parseCapabilityDefinitionReferenceFromElement(
                    new CapabilityDefinitionExtensionReference(), element, "capabilityId", DccConstants.PROVISION,
                    registry);

            final List<ProvisionRestriction> restrictions = parseProvisionRestrictions(element);

            if (ref != null) {
                parsedReferences.add(new Provision(ref, restrictions));
            }
        }

        return parsedReferences;
    }

    private List<ProvisionRestriction> parseProvisionRestrictions(Element element) {
        final List<ProvisionRestriction> restrictions = new ArrayList<>();
        final Element restrictionsElement = DomUtils.getChildElementByTagName(element, "restrictions");
        if (restrictionsElement != null) {
            final Element hostBoundRestriction = DomUtils.getChildElementByTagName(restrictionsElement,
                    "host-bound");
            if (hostBoundRestriction != null) {
                restrictions.add(new HostRestriction());
            }
            //TODO add additional restrictions
        }

        return restrictions;
    }

    private List<CapabilityDefinitionExtensionReference> parseCommandRequisitions(Element commandElement,
            BeanDefinitionRegistry registry) {
        final List<CapabilityDefinitionExtensionReference> parsedReferences = new ArrayList<>();

        final List<Element> matchedElements = DomUtils.getChildElementsByTagName(commandElement,
                DccConstants.REQUISITION);
        for (Element element : matchedElements) {
            final CapabilityDefinitionExtensionReference ref = parseCapabilityDefinitionExtensionReferences(element,
                    DccConstants.REQUISITION, registry);
            if (ref != null) {
                if (StringUtils.isEmpty(ref.getBoundToCapabilityId())) {
                    // Only valid for requisitions
                    ref.setBoundToCapabilityId(ref.getReferencedCapabilityDefId());
                }

                parsedReferences.add(ref);
            }
        }

        return parsedReferences;
    }

    private CapabilityDefinitionExtensionReference parseCapabilityDefinitionExtensionReferences(Element element,
            String elementName, BeanDefinitionRegistry registry) {
        final CapabilityDefinitionExtensionReference ref = parseCapabilityDefinitionReferenceFromElement(
                new CapabilityDefinitionExtensionReference(), element, "capabilityId", elementName, registry);

        if (ref == null) {
            return null;
        }

        final String boundToCapabilityRefText = parseString(element, "boundTo", null, registry);
        final String boundToCapabilityRef = StringUtils.isEmpty(boundToCapabilityRefText) ? null
                : boundToCapabilityRefText;

        ref.setBoundToCapabilityId(boundToCapabilityRef);

        return ref;
    }

    private ManagedList<AbstractBeanDefinition> parseMappingsElement(Element unitElement, String unitId,
            BeanDefinitionRegistry registry) {
        ManagedList<AbstractBeanDefinition> mappings = new ManagedList<>();
        Element mappingsElement = DomUtils.getChildElementByTagName(unitElement, ELEMENT_UNIT_MAPPINGS);
        if (mappingsElement != null) {
            List<Element> mappingElements = DomUtils.getChildElementsByTagName(mappingsElement, "mapping");
            for (Element mappingElement : mappingElements) {
                String targetCapabilityId = parseString(mappingElement, "targetCapabilityId", null, registry);

                BeanDefinitionBuilder mappingBeanDefBuilder = BeanDefinitionBuilder
                        .genericBeanDefinition(Mapping.class);
                mappingBeanDefBuilder.addConstructorArgValue(Id.createCapabilityId(targetCapabilityId));

                ManagedList<AttributeMapper> mappers = new ManagedList<>();

                Element mapUnitAttributesElement = DomUtils.getChildElementByTagName(mappingElement,
                        "map-unit-attributes");
                if (mapUnitAttributesElement != null) {
                    String sourcePrefix = parseString(mapUnitAttributesElement, "sourcePrefix", null, registry);
                    String targetPrefix = parseString(mapUnitAttributesElement, "targetPrefix", null, registry);
                    UnitToCapabilityAttributeMapper mapper = new UnitToCapabilityAttributeMapper(sourcePrefix,
                            targetPrefix);
                    mappers.add(mapper);
                }

                // extract map alls
                List<Element> mapAllElements = DomUtils.getChildElementsByTagName(mappingElement, "map-all");
                for (Element mapAllElement : mapAllElements) {
                    Id<CapabilityId> sourceCapabilityId = Id
                            .createCapabilityId(parseString(mapAllElement, "sourceCapabilityId", null, registry));
                    String sourcePrefix = parseString(mapAllElement, "sourcePrefix", null, registry);
                    String targetPrefix = parseString(mapAllElement, "targetPrefix", null, registry);
                    SourceToTargetCapabilityAttributeMapper mapper = new SourceToTargetCapabilityAttributeMapper(
                            sourceCapabilityId, sourcePrefix, targetPrefix);
                    mappers.add(mapper);
                }

                // extract expressions
                List<Element> expressionElements = DomUtils.getChildElementsByTagName(mappingElement, "expression");
                for (Element expressionElement : expressionElements) {
                    String attributeKey = expressionElement.getAttribute("attributeKey");
                    String value = parseString(expressionElement, "value", null, registry);
                    ExpressionAttributeMapper mapper = new ExpressionAttributeMapper(attributeKey, value);
                    mappers.add(mapper);
                }

                mappingBeanDefBuilder.addPropertyValue("attributeMappers", mappers);

                mappingBeanDefBuilder.setScope(BeanDefinition.SCOPE_PROTOTYPE);

                mappings.add(mappingBeanDefBuilder.getBeanDefinition().cloneBeanDefinition());
            }
        }
        return mappings;
    }

    private ManagedList<Attribute> parseAttributesElement(Element unitElement, ParserContext parserContext) {
        ManagedList<Attribute> attributes = new ManagedList<>();
        Element attributesElement = DomUtils.getChildElementByTagName(unitElement, ELEMENT_UNIT_ATTRIBUTES);
        final BeanDefinitionRegistry registry = parserContext.getRegistry();
        if (attributesElement != null) {
            List<Element> attributeElements = DomUtils.getChildElementsByTagName(attributesElement,
                    ELEMENT_UNIT_ATTRIBUTES_ATTRIBUTE);
            for (Element attributeElement : attributeElements) {
                String key = attributeElement.getAttribute("key");
                String description = parseString(attributeElement, "description", null, registry);
                String typeString = attributeElement.getAttribute("type");
                AttributeType type = null;
                if (!StringUtils.isEmpty(typeString)) {
                    type = AttributeType.valueOf(typeString.toUpperCase(Locale.ENGLISH));
                }
                String value = parseString(attributeElement, "value", null, registry);
                File origin = determineOrigin(key, parserContext);

                Attribute att = new Attribute(key, value, origin);
                att.setType(type);
                att.setDescription(description);
                attributes.add(att);
            }
        }
        return attributes;
    }

    private String parseString(Element element, String name, String defaultValue, BeanDefinitionRegistry registry) {
        String result = defaultValue;
        if (element.hasAttribute(name)) {
            result = element.getAttribute(name);

            // if the value does not exist of blanks (intentionally)
            if (!StringUtils.isBlank(result)) {
                // we trim...
                result = result.trim();

                // FIXME: what if this is international
                // in any case we remove multiple spaces
                result = result.replaceAll("  *", " ");
            }
        }

        return replaceVariables(registry, result);
    }

    private ManagedList<AbstractBeanDefinition> parseRequiredCapabilities(Element unitElement, String unitId,
            BeanDefinitionRegistry registry) {
        ManagedList<AbstractBeanDefinition> reqCapReferences = new ManagedList<>();
        List<Element> reqCapElements = DomUtils.getChildElementsByTagName(unitElement,
                ELEMENT_UNIT_REQUIRED_CAPABILITY);

        for (Element reqCapElement : reqCapElements) {
            String capabilityId = extractMandatoryIdAttributeFromElement(reqCapElement, "Capability", registry);
            boolean optionalFlag = Boolean.valueOf(reqCapElement.getAttribute(ATTRIBUTE_KEY_OPTIONAL));
            boolean multipleBindingsAllowedFlag = Boolean
                    .valueOf(reqCapElement.getAttribute(ATTRIBUTE_KEY_MULTIPLE_BINDINGS));
            boolean identifiesHost = Boolean.valueOf(reqCapElement.getAttribute(ATTRIBUTE_KEY_IDENTIFIES_HOST));

            BeanDefinitionBuilder builder = createGenericCapabilityBuilder(capabilityId, reqCapElement,
                    RequiredCapability.class);
            builder.addPropertyValue(ATTRIBUTE_KEY_OPTIONAL, optionalFlag);
            builder.addPropertyValue(ATTRIBUTE_KEY_MULTIPLE_BINDINGS, multipleBindingsAllowedFlag);
            builder.addPropertyValue(ATTRIBUTE_IDENTIFIES_HOST, identifiesHost);

            String capabilityBeanName = generateCapabilityBeanName(unitId, capabilityId);
            registry.registerBeanDefinition(capabilityBeanName, builder.getBeanDefinition());

            reqCapReferences.add(builder.getBeanDefinition());
        }
        return reqCapReferences;
    }

    private ManagedList<AbstractBeanDefinition> parseProvidedCapabilities(Element unitElement, String unitId,
            BeanDefinitionRegistry registry) {

        ManagedList<AbstractBeanDefinition> provCapReferences = new ManagedList<>();
        List<Element> provCapElements = DomUtils.getChildElementsByTagName(unitElement,
                ELEMENT_UNIT_PROVIDED_CAPABILITY);

        for (Element provCapElement : provCapElements) {
            String capabilityId = extractMandatoryIdAttributeFromElement(provCapElement, "Capability", registry);
            BeanDefinitionBuilder capabilityBeanDefBuilder = createGenericCapabilityBuilder(capabilityId,
                    provCapElement, Capability.class);
            String capabilityBeanName = generateCapabilityBeanName(unitId, capabilityId);
            registry.registerBeanDefinition(capabilityBeanName, capabilityBeanDefBuilder.getBeanDefinition());
            provCapReferences.add(capabilityBeanDefBuilder.getBeanDefinition());
        }
        return provCapReferences;
    }

    private BeanDefinitionBuilder createGenericCapabilityBuilder(String capabilityId, Element capElement,
            Class<? extends Capability> type) {

        String capabilityDefinitionRef = capElement.getAttribute(ATTRIBUTE_CAPABILITY_DEFINITION_REF);

        // convention: if the capabilityDefinitionRef matches the capabilityId it can be omitted
        if (StringUtils.isEmpty(capabilityDefinitionRef)) {
            capabilityDefinitionRef = capabilityId;
        }
        String capabilityDefRef = createCapabilityDefBeanName(capabilityDefinitionRef);

        BeanDefinitionBuilder capabilityBeanDefBuilder = BeanDefinitionBuilder.genericBeanDefinition(type);
        capabilityBeanDefBuilder.addConstructorArgValue(Id.createCapabilityId(capabilityId));
        capabilityBeanDefBuilder.addConstructorArgReference(capabilityDefRef);
        capabilityBeanDefBuilder.addConstructorArgValue(null);

        // force spring to create several instances to not use shared capabilities across different units
        capabilityBeanDefBuilder.setScope(BeanDefinition.SCOPE_PROTOTYPE);

        return capabilityBeanDefBuilder;
    }

    private String generateCapabilityBeanName(String unitId, String capabilityId) {
        return String.format("%s#%s", unitId, capabilityId);
    }

    private void parseAndRegisterBindings(Element profileElement, File origin, BeanDefinitionRegistry registry,
            ParserContext parserContext) {

        List<Element> bindingElements = DomUtils.getChildElementsByTagName(profileElement, ELEMENT_BINDING);
        for (Element bindingElement : bindingElements) {
            Element sourceElement = DomUtils.getChildElementByTagName(bindingElement, ELEMENT_BINDING_SOURCE);
            Element targetElement = DomUtils.getChildElementByTagName(bindingElement, ELEMENT_BINDING_TARGET);

            String sourceUnitRef = parseString(sourceElement, "unitRef", null, registry);
            String sourceCapabilityId = parseString(sourceElement, "capabilityId", null, registry);
            String targetUnitRef = parseString(targetElement, "unitRef", null, registry);
            String targetCapabilityId = parseString(targetElement, "capabilityId", null, registry);

            // convention: if target capabilityId is omitted then use the source capability id
            if (StringUtils.isEmpty(targetCapabilityId)) {
                targetCapabilityId = sourceCapabilityId;
            }

            String description = extractDescription(bindingElement, registry);

            BeanDefinitionBuilder bindingBeanBuilder = BeanDefinitionBuilder
                    .genericBeanDefinition(BindingFactoryBean.class);
            bindingBeanBuilder.addPropertyValue(PROPERTY_ORIGIN, origin);
            bindingBeanBuilder.addPropertyValue(PROPERTY_DESCRIPTION, description);
            bindingBeanBuilder.addPropertyValue("sourceCapabilityId", Id.createCapabilityId(sourceCapabilityId));
            bindingBeanBuilder.addPropertyReference("sourceUnit", sourceUnitRef);
            bindingBeanBuilder.addPropertyValue("targetCapabilityId", Id.createCapabilityId(targetCapabilityId));
            bindingBeanBuilder.addPropertyReference("targetUnit", targetUnitRef);

            String bindingBeanName = parserContext.getReaderContext()
                    .generateBeanName(bindingBeanBuilder.getBeanDefinition());
            registry.registerBeanDefinition(bindingBeanName, bindingBeanBuilder.getBeanDefinition());

            RuntimeBeanReference beanRef = new RuntimeBeanReference(bindingBeanName);
            beanRef.setSource(parserContext.extractSource(bindingElement));

        }
    }

    private File determineOrigin(String elementId, ParserContext parserContext) {
        try {
            return parserContext.getReaderContext().getResource().getFile();
        } catch (IOException e) {
            throw new BeanCreationException(String.format("Cannot determine origin of [%s].", elementId), e);
        }
    }
}