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

Java tutorial

Introduction

Here is the source code for org.metaeffekt.dcc.commons.spring.xml.ProfileValidationBeanFactoryPostProcessor.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 static org.metaeffekt.dcc.commons.mapping.BindingRegistry.generateUniqueId;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Properties;
import java.util.Set;
import java.util.regex.Pattern;

import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.support.BeanDefinitionValidationException;

import org.metaeffekt.dcc.commons.DccProperties;
import org.metaeffekt.dcc.commons.DccUtils;
import org.metaeffekt.dcc.commons.mapping.Attribute;
import org.metaeffekt.dcc.commons.mapping.AttributeKey;
import org.metaeffekt.dcc.commons.mapping.Binding;
import org.metaeffekt.dcc.commons.mapping.BindingRegistry;
import org.metaeffekt.dcc.commons.mapping.Capability;
import org.metaeffekt.dcc.commons.mapping.ConfigurationUnit;
import org.metaeffekt.dcc.commons.mapping.IsTrueAssert;
import org.metaeffekt.dcc.commons.mapping.Profile;
import org.metaeffekt.dcc.commons.mapping.PropertiesHolder;
import org.metaeffekt.dcc.commons.mapping.RequiredCapability;
import org.metaeffekt.dcc.commons.mapping.UniqueAssert;

/**
 * Proudly crafted by i001450 on 18.07.14.
 */
public class ProfileValidationBeanFactoryPostProcessor implements BeanFactoryPostProcessor {

    private static final String INDENT = System.lineSeparator() + "    ";

    private static final Logger LOG = LoggerFactory.getLogger(ProfileValidationBeanFactoryPostProcessor.class);

    private static final Pattern PREDEFINED_PROPERTIES_REGEX = Pattern.compile("\\$\\{dcc\\..+\\}");

    private BindingRegistry bindingRegistry;

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        Profile profile = (Profile) beanFactory
                .getBean(DCCConfigurationBeanDefinitionParser.THE_ONE_TRUE_PROFILE_BEAN_NAME);

        if (Profile.Type.DEPLOYMENT == profile.getType()) {

            // evaluate the profile (including deployment properties)
            final PropertiesHolder propertiesHolder = profile.createPropertiesHolder(true);
            profile.evaluate(propertiesHolder);

            this.bindingRegistry = BindingRegistry.getInstance(beanFactory);

            List<String> messages = new LinkedList<>();

            for (ConfigurationUnit unit : profile.getUnits()) {
                // TODO validate if all mappings are well-formed
                validateProvidedCapabilities(unit, profile, propertiesHolder, messages);
                validateRequiredCapabilities(unit, profile, messages);
            }

            messages.addAll(checkAsserts(profile, propertiesHolder));

            // checking for unused attributes and properties (only when validation is on)
            if (Boolean.parseBoolean(
                    System.getProperty(DccProperties.DCC_SYSTEM_PROPERTY_PROFILE_VALIDATION, "true"))) {

                boolean hasUnusedAttributes = false;
                for (ConfigurationUnit unit : profile.getUnits(false)) {
                    for (Attribute attribute : unit.getAttributes()) {
                        String attributeId = DccUtils.deriveAttributeIdentifier(unit, attribute.getKey());
                        if (!propertiesHolder.isPropertyRelevant(attributeId)) {
                            LOG.warn("Unused attribute [{}] in unit [{}].", attribute.getKey(), unit.getId());
                            hasUnusedAttributes = true;
                        }
                    }
                }
                if (hasUnusedAttributes) {
                    LOG.warn(
                            "Unused attributes are not evaluated and may indicate issues within the profile definition.");
                    LOG.warn("Please review the profile with id '{}'.", profile.getId());
                }

                // check on solution property usage
                checkForUnusedProperties("solution", profile.getSolutionProperties(), propertiesHolder,
                        DccProperties.DCC_SOLUTION_PROPERTIES_WHITELIST, profile.getSolutionPropertiesFile(),
                        profile);
                checkForUnusedProperties("deployment", profile.getDeploymentProperties(), propertiesHolder,
                        DccProperties.DCC_DEPLOYMENT_PROPERTIES_WHITELIST, profile.getDeploymentPropertiesFile(),
                        profile);

                checkForUnresolvedVariables(profile, propertiesHolder);
            }

            Collections.sort(messages);
            StringBuilder messageBuilder = new StringBuilder();
            int i = 1;
            for (String string : messages) {
                messageBuilder.append(System.lineSeparator());
                messageBuilder.append(System.lineSeparator());

                messageBuilder.append(i++);
                messageBuilder.append(") ");

                messageBuilder.append(string.replace("${INDENT}", INDENT));
            }
            messageBuilder.append(System.lineSeparator());

            if (messages.size() > 0) {
                String errorMessage = String.format("%d validation error(s) occurred: %s", messages.size(),
                        messageBuilder.toString());
                LOG.error(errorMessage);
                if (Boolean.parseBoolean(
                        System.getProperty(DccProperties.DCC_SYSTEM_PROPERTY_PROFILE_VALIDATION, "true"))) {
                    throw new BeanDefinitionValidationException(
                            "Stopping profile evaluation due to validation errors.");
                }
            }
        }

    }

    private boolean checkForUnusedProperties(String context, Properties referenceProperties,
            PropertiesHolder propertiesHolder, String[] propertiesWhitelist, File propertiesFile, Profile profile) {
        boolean hasUnusedProperties = false;
        if (referenceProperties != null) {
            for (Object key : referenceProperties.keySet()) {
                if (Arrays.binarySearch(propertiesWhitelist, key) < 0) {
                    if (!propertiesHolder.isPropertyRelevant(key.toString())) {
                        LOG.warn("Unused [{}] property: [{}].", context, key);
                        hasUnusedProperties = true;
                    }
                }
            }
        }

        if (hasUnusedProperties) {
            LOG.warn("Unused properties may indicate issues within the configuration.");
            LOG.warn("Please review the {} properties in file '{}'.", context,
                    propertiesFile != null ? profile.getRelativePath(propertiesFile) : "<no file available>");
        }

        return hasUnusedProperties;
    }

    private boolean checkForUnresolvedVariables(Profile profile, PropertiesHolder propertiesHolder) {
        boolean hasUnresolvedVariables = false;

        // check the properties for a unit/capability does not contain any foreign unresolved placeholders
        for (ConfigurationUnit unit : profile.getUnits(false)) {
            Properties p = propertiesHolder.getProperties(unit);
            hasUnresolvedVariables |= checkUnresolvedVariables(String.format("unit [%s]", unit.getId()), p);
            for (Capability capability : unit.getProvidedCapabilities()) {
                p = propertiesHolder.getProperties(capability);
                hasUnresolvedVariables |= checkUnresolvedVariables(
                        String.format("provided capability [%s] in unit [%s]", capability.getId(), unit.getId()),
                        p);
            }
        }

        if (hasUnresolvedVariables) {
            LOG.warn("Unresolved variables may indicate issues within the configuration.");
            LOG.warn("Please review the profile with id '{}'.", profile.getId());
        }

        return hasUnresolvedVariables;
    }

    protected boolean checkUnresolvedVariables(String context, Properties p) {
        Enumeration<?> enumeration = p.propertyNames();
        boolean hasUnresolvedVariables = false;
        while (enumeration.hasMoreElements()) {
            Object key = enumeration.nextElement();
            String value = p.getProperty(key.toString());
            value = value.replace("${dcc.", "{");
            if (value.contains("${")) {
                if (!hasUnresolvedVariables) {
                    LOG.warn("The properties derived for {} contains unresolved variables:", context);
                    hasUnresolvedVariables = true;
                }
                LOG.warn("  Value of key [{}]: {}", key, value);
            }
        }
        return hasUnresolvedVariables;
    }

    private List<String> checkAsserts(final Profile profile, final PropertiesHolder propertiesHolder) {
        final List<String> messages = new ArrayList<>();

        checkIsTrueAsserts(profile, messages, propertiesHolder);
        checkUniqueAsserts(profile, messages, propertiesHolder);

        return messages;
    }

    private void checkIsTrueAsserts(Profile profile, final List<String> messages,
            final PropertiesHolder propertiesHolder) {
        for (IsTrueAssert isTrueAssert : profile.getAsserts(IsTrueAssert.class)) {
            final Boolean result = isTrueAssert.evaluate(propertiesHolder, profile);
            if (result == null) {
                continue;
            }
            if (!Boolean.TRUE.equals(result)) {
                final StringBuilder errorMessage = new StringBuilder();
                if (!StringUtils.isEmpty(isTrueAssert.getMessage())) {
                    errorMessage.append(isTrueAssert.getMessage());
                    errorMessage.append(" ");
                }
                errorMessage.append("IsTrue assertion failed, value expression evaluated to false: [%s]"
                        + "${INDENT} Unit [%s].");
                messages.add(
                        String.format(errorMessage.toString(), isTrueAssert.getValue(), isTrueAssert.getUnit()));
            }
        }
    }

    private void checkUniqueAsserts(Profile profile, final List<String> messages,
            final PropertiesHolder propertiesHolder) {
        final Map<String, UniqueAssert> uniqueAsserts = new HashMap<>();
        for (UniqueAssert uniqueAssert : profile.getAsserts(UniqueAssert.class)) {
            final String value = uniqueAssert.evaluate(propertiesHolder, profile);

            // Asserts which are evaluated to empty string or null are ignored
            if (StringUtils.isEmpty(value)) {
                continue;
            }

            final String key = value.toLowerCase(Locale.ENGLISH);
            final UniqueAssert matchedAssert = uniqueAsserts.get(key);
            if (matchedAssert != null) {
                final StringBuilder errorMessage = new StringBuilder();
                if (!StringUtils.isEmpty(uniqueAssert.getMessage())) {
                    errorMessage.append(uniqueAssert.getMessage());
                }
                if (!StringUtils.isEmpty(matchedAssert.getMessage())
                        && !matchedAssert.getMessage().equals(uniqueAssert.getMessage())) {
                    errorMessage.append(matchedAssert.getMessage());
                }
                if (errorMessage.length() > 0) {
                    errorMessage.append(" ");
                }
                errorMessage.append("Unique assertion failed. [%s] is not unique."
                        + "${INDENT} Unique assertion: [%s]" + "${INDENT} Matched assertion: [%s].");
                messages.add(String.format(errorMessage.toString(), value, uniqueAssert, matchedAssert));
            } else {
                uniqueAsserts.put(key, uniqueAssert);
            }
        }
    }

    private void validateProvidedCapabilities(ConfigurationUnit unit, Profile profile,
            PropertiesHolder propertiesHolder, List<String> messages) {
        for (Capability provCab : unit.getProvidedCapabilities()) {

            if (provCab.getCapabilityDefinition().isAbstract()) {
                messages.add(String.format(
                        "Provided capability [%s] references an abstract capability definition."
                                + "${INDENT} Capability definition: [%s]" + "${INDENT} Profile: [%s]",
                        provCab.getUniqueId(), provCab.getCapabilityDefinition().getId(),
                        profile.getRelativePath(provCab.getCapabilityDefinition())));
            } else {
                for (AttributeKey attributeKey : provCab.getCapabilityDefinition().getAttributeKeys()) {
                    if (!attributeKey.isOptional()) {
                        String value = propertiesHolder.getProperty(provCab, attributeKey.getKey());

                        // the value may contain a non-resolved variable.
                        if (value != null && !StringUtils.isBlank(value)) {
                            if (!PREDEFINED_PROPERTIES_REGEX.matcher(value).matches()) {
                                value = value.replaceAll("\\$\\{.*\\}", "");
                                if (StringUtils.isBlank(value)) {
                                    value = null;
                                }
                            }
                        }

                        if (value == null) {
                            messages.add(String.format(
                                    "No value defined for mandatory attribute [%s]"
                                            + "${INDENT} Provided capability: [%s]" + "${INDENT} Profile: [%s]",
                                    attributeKey.getKey(), provCab.getUniqueId(), profile.getRelativePath(unit)));
                        }
                    }
                }
            }
        }
    }

    private void validateRequiredCapabilities(ConfigurationUnit unit, Profile profile, List<String> messages) {

        for (Capability cap : unit.getRequiredCapabilities()) {

            RequiredCapability reqCap = (RequiredCapability) cap;

            if (reqCap.getCapabilityDefinition().isAbstract()) {
                messages.add(String.format(
                        "Required capability [%s] references an abstract capability definition."
                                + "${INDENT} Capability definition: [%s]" + "${INDENT} Profile: [%s].",
                        reqCap.getUniqueId(), reqCap.getCapabilityDefinition().getId(),
                        profile.getRelativePath(reqCap.getCapabilityDefinition())));
            }

            if (!reqCap.isMultipleBindingsAllowed()) {
                Set<String> provCapsBoundToThis = bindingRegistry
                        .providedCapabilitiesBoundTo(generateUniqueId(unit.getId(), reqCap.getId()));
                if (provCapsBoundToThis.size() > 1) {
                    messages.add(String.format("Multiple bindings are not allowed for required capability [%s].",
                            reqCap.getUniqueId()));
                }
            }

            if (!reqCap.isOptional()) {

                if (!isBound(reqCap, profile)) {
                    messages.add(String.format("No binding definition for required capability [%s].",
                            reqCap.getUniqueId()));
                }
            }
        }
    }

    private boolean isBound(Capability reqCab, Profile profile) {

        for (Binding binding : profile.getBindings()) {

            if (reqCab.getUniqueId().equals(binding.getTargetCapability().getUniqueId())) {
                return true;
            }
        }
        return false;
    }
}