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

org.apache.nifi.components.PropertyDescriptor Maven / Gradle / Ivy

The newest version!
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.nifi.components;

import org.apache.nifi.components.resource.ResourceCardinality;
import org.apache.nifi.components.resource.ResourceDefinition;
import org.apache.nifi.components.resource.ResourceReference;
import org.apache.nifi.components.resource.ResourceType;
import org.apache.nifi.components.resource.StandardResourceDefinition;
import org.apache.nifi.components.resource.StandardResourceReferenceFactory;
import org.apache.nifi.controller.ControllerService;
import org.apache.nifi.expression.ExpressionLanguageScope;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;

/**
 * An immutable object for holding information about a type of component
 * property.
 */
public final class PropertyDescriptor implements Comparable {

    public static final PropertyDescriptor NULL_DESCRIPTOR = new PropertyDescriptor.Builder().name("").build();

    /**
     * The proper name for the property. This is the primary mechanism of
     * comparing equality.
     */
    private final String name;

    /**
     * The name that should be displayed to user when referencing this property
     */
    private final String displayName;

    /**
     * And explanation of the meaning of the given property. This description is
     * meant to be displayed to a user or simply provide a mechanism of
     * documenting intent.
     */
    private final String description;
    /**
     * The default value for this property
     */
    private final String defaultValue;
    /**
     * The allowable values for this property. If empty then the allowable
     * values are not constrained
     */
    private final List allowableValues;
    /**
     * Determines whether the property is required for this processor
     */
    private final boolean required;
    /**
     * indicates that the value for this property should be considered sensitive
     * and protected whenever stored or represented
     */
    private final boolean sensitive;
    /**
     * indicates whether this property well-known for this processor or is
     * user-defined
     */
    private final boolean dynamic;
    /**
     * indicates whether or nor this property will evaluate expression language
     * against the flow file attributes
     */
    private final ExpressionLanguageScope expressionLanguageScope;
    /**
     * indicates whether or not this property represents resources that should be added
     * to the classpath and used for loading native libraries for this instance of the component
     */
    private final boolean dynamicallyModifiesClasspath;

    /**
     * the interface of the {@link ControllerService} that this Property refers
     * to; will be null if not identifying a ControllerService.
     */
    private final Class controllerServiceDefinition;

    /**
     * The validators that should be used whenever an attempt is made to set
     * this property value. Any allowable values specified will be checked first
     * and any validators specified will be ignored.
     */
    private final List validators;

    /**
     * The list of dependencies that this property has on other properties
     */
    private final Set dependencies;

    /**
     * The definition of the resource(s) that this property references
     */
    private final ResourceDefinition resourceDefinition;

    protected PropertyDescriptor(final Builder builder) {
        this.displayName = builder.displayName == null ? builder.name : builder.displayName;
        this.name = builder.name;
        this.description = builder.description;
        this.defaultValue = builder.defaultValue;
        this.allowableValues = builder.allowableValues == null ? null : List.copyOf(builder.allowableValues);
        this.required = builder.required;
        this.sensitive = builder.sensitive;
        this.dynamic = builder.dynamic;
        this.dynamicallyModifiesClasspath = builder.dynamicallyModifiesClasspath;
        this.expressionLanguageScope = builder.expressionLanguageScope;
        this.controllerServiceDefinition = builder.controllerServiceDefinition;
        this.validators = List.copyOf(builder.validators);
        this.dependencies = builder.dependencies == null ? Collections.emptySet() : Set.copyOf(builder.dependencies);
        this.resourceDefinition = builder.resourceDefinition;
    }

    @Override
    public int compareTo(final PropertyDescriptor o) {
        if (o == null) {
            return -1;
        }
        return getName().compareTo(o.getName());
    }

    /**
     * Validates the given input against this property descriptor's validator.
     * If this descriptor has a set of allowable values then the given value is
     * only checked against the allowable values.
     *
     * @param input   the value to validate
     * @param context the context of validation
     * @return the result of validating the input
     */
    public ValidationResult validate(final String input, final ValidationContext context) {
        ValidationResult lastResult = Validator.INVALID.validate(this.name, input, context);

        if (allowableValues != null && !allowableValues.isEmpty()) {
            final ConstrainedSetValidator csValidator = new ConstrainedSetValidator(allowableValues);
            final ValidationResult csResult = csValidator.validate(this.name, input, context);

            if (csResult.isValid()) {
                lastResult = csResult;
            } else {
                return csResult;
            }
        }

        final ResourceDefinition resourceDefinition = getResourceDefinition();
        if (resourceDefinition != null) {
            final Validator validator = new ResourceDefinitionValidator(resourceDefinition, this.expressionLanguageScope);
            final ValidationResult result = validator.validate(this.name, input, context);
            if (!result.isValid()) {
                return result;
            }

            lastResult = result;
        }

        for (final Validator validator : validators) {
            lastResult = validator.validate(this.name, input, context);
            if (!lastResult.isValid()) {
                break;
            }
        }

        if (getControllerServiceDefinition() != null) {
            final ControllerService service = context.getControllerServiceLookup().getControllerService(input);
            if (service == null) {
                return new ValidationResult.Builder()
                        .input(input)
                        .subject(getDisplayName())
                        .valid(false)
                        .explanation("Property references a Controller Service that does not exist")
                        .build();
            } else {
                return new ValidationResult.Builder()
                        .valid(true)
                        .build();
            }
        }

        return lastResult;
    }

    public static final class Builder {

        private String displayName = null;
        private String name = null;
        private String description = "";
        private String defaultValue = null;
        private List allowableValues = null;
        private Set dependencies = null;
        private boolean required = false;
        private boolean sensitive = false;
        private ExpressionLanguageScope expressionLanguageScope = ExpressionLanguageScope.NONE;
        private boolean dynamic = false;
        private boolean dynamicallyModifiesClasspath = false;
        private Class controllerServiceDefinition;
        private ResourceDefinition resourceDefinition;
        private List validators = new ArrayList<>();

        public Builder fromPropertyDescriptor(final PropertyDescriptor specDescriptor) {
            this.name = specDescriptor.name;
            this.displayName = specDescriptor.displayName;
            this.description = specDescriptor.description;
            this.defaultValue = specDescriptor.defaultValue;
            this.allowableValues = specDescriptor.allowableValues == null ? null : new ArrayList<>(specDescriptor.allowableValues);
            this.required = specDescriptor.required;
            this.sensitive = specDescriptor.sensitive;
            this.dynamic = specDescriptor.dynamic;
            this.dynamicallyModifiesClasspath = specDescriptor.dynamicallyModifiesClasspath;
            this.expressionLanguageScope = specDescriptor.expressionLanguageScope;
            this.controllerServiceDefinition = specDescriptor.getControllerServiceDefinition();
            this.validators = new ArrayList<>(specDescriptor.validators);
            this.dependencies = new HashSet<>(specDescriptor.dependencies);
            this.resourceDefinition = specDescriptor.resourceDefinition;
            return this;
        }

        /**
         * Sets a unique id for the property. This field is optional and if not
         * specified the PropertyDescriptor's name will be used as the
         * identifying attribute. However, by supplying an id, the
         * PropertyDescriptor's name can be changed without causing problems.
         * This is beneficial because it allows a User Interface to represent
         * the name differently.
         *
         * @param displayName of the property
         * @return the builder
         */
        public Builder displayName(final String displayName) {
            if (null != displayName) {
                this.displayName = displayName;
            }

            return this;
        }

        /**
         * Sets the property name.
         *
         * @param name of the property
         * @return the builder
         */
        public Builder name(final String name) {
            if (null != name) {
                this.name = name;
            }
            return this;
        }

        /**
         * Sets the scope of the expression language evaluation
         *
         * @param expressionLanguageScope scope of the expression language evaluation
         * @return the builder
         */
        public Builder expressionLanguageSupported(final ExpressionLanguageScope expressionLanguageScope) {
            this.expressionLanguageScope = expressionLanguageScope;
            return this;
        }

        /**
         * @param description of the property
         * @return the builder
         */
        public Builder description(final String description) {
            if (null != description) {
                this.description = description;
            }
            return this;
        }

        /**
         * Specifies the initial value and the default value that will be used if the user does not specify a value.
         * 

* When {@link #build()} is called, if Allowable Values have been set (see {@link #allowableValues(DescribedValue...)} and overloads) * and this value is not one of those Allowable Values, an Exception will be thrown. * If the Allowable Values have been set, the default value should be set to * the "Value" of the {@link DescribedValue} object (see {@link DescribedValue#getValue()}). * There's an overload available for this (see {@link #defaultValue(DescribedValue)}). * * @param value default value * @return the builder */ public Builder defaultValue(final String value) { if (null != value) { this.defaultValue = value; } return this; } /** * Specifies the initial value and the default value that will be used if the user does not specify a value. *

* Sets the default value to the "Value" of the {@link DescribedValue} object. * When {@link #build()} is called, if Allowable Values have been set (see {@link #allowableValues(DescribedValue...)} and overloads) * and this value is not one of those Allowable Values, an Exception will be thrown. * In case there is not a restricted set of Allowable Values {@link #defaultValue(String)} may be used. * * @param value default value holder * @return the builder */ public Builder defaultValue(final DescribedValue value) { return defaultValue(value != null ? value.getValue() : null); } /** * Clears the initial value and default value from this Property. * * @return the builder */ public Builder clearDefaultValue() { this.defaultValue = null; return this; } public Builder dynamic(final boolean dynamic) { this.dynamic = dynamic; return this; } /** * Specifies that the value of this property represents one or more resources that the * framework should add to the classpath of as well as consider when looking for native * libraries for the given component. *

* NOTE: If a component contains a PropertyDescriptor where dynamicallyModifiesClasspath is set to true, * the component may also be annotated with @RequiresInstanceClassloading, in which case every class will * be loaded by a separate InstanceClassLoader for each processor instance.
* It also allows to load native libraries from the extra classpath. *

* One can choose to omit the annotation. In this case the loading of native libraries from the extra classpath * is not supported. * Also by default, classes will be loaded by a common NarClassLoader, however it's possible to acquire an * InstanceClassLoader by calling Thread.currentThread().getContextClassLoader() which can be used manually * to load required classes on an instance-by-instance basis * (by calling {@link Class#forName(String, boolean, ClassLoader)} for example). *

* Any property descriptor that dynamically modifies the classpath should also make use of the {@link #identifiesExternalResource(ResourceCardinality, ResourceType, ResourceType...)} method * to indicate that the property descriptor references external resources and optionally restrict which types of resources and how many resources the property allows. * * @param dynamicallyModifiesClasspath whether or not this property should be used by the framework to modify the classpath * @return the builder */ public Builder dynamicallyModifiesClasspath(final boolean dynamicallyModifiesClasspath) { this.dynamicallyModifiesClasspath = dynamicallyModifiesClasspath; return this; } /** * Sets the Allowable Values for this Property. * * @param values constrained set of values * @return the builder */ public Builder allowableValues(final Set values) { if (null != values) { this.allowableValues = values.stream().map(AllowableValue::new).toList(); } return this; } /** * Sets the Allowable Values for this Property. *

* Uses the {@link Enum#name()} of each value as "Value" for the {@link AllowableValue}. * In case the enum value is a {@link DescribedValue}, uses the information provided instead * (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, {@link DescribedValue#getDescription()}). * * @param Type of Allowable Value * @param values constrained set of values * @return the builder */ public > Builder allowableValues(final E[] values) { if (null != values) { this.allowableValues = Arrays.stream(values) .map(enumValue -> enumValue instanceof DescribedValue describedValue ? AllowableValue.fromDescribedValue(describedValue) : new AllowableValue(enumValue.name())) .toList(); } return this; } /** * Sets the Allowable Values for this Property. *

* Uses the {@link Enum#name()} of each value from {@link Class#getEnumConstants()} as "Value" for the {@link AllowableValue}. * In case the enum value is a {@link DescribedValue}, uses the information provided instead * (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, {@link DescribedValue#getDescription()}). * * @param enumClass an enum class that contains a set of values and optionally implements the DescribedValue interface * @param generic parameter for an enum class, that may implement the DescribedValue interface * @return the builder */ public > Builder allowableValues(final Class enumClass) { return allowableValues(enumClass.getEnumConstants()); } /** * Sets the Allowable Values for this Property. *

* Uses the {@link Enum#name()} of each value of the {@link EnumSet} as "Value" for the {@link AllowableValue}. * In case the enum value is a {@link DescribedValue}, uses the information provided instead * (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, {@link DescribedValue#getDescription()}). * * @param enumValues an enum set that contains a set of values and optionally implements the DescribedValue interface * @param generic parameter for an enum class, that may implement the DescribedValue interface * @return the builder */ public > Builder allowableValues(final EnumSet enumValues) { if (null != enumValues) { this.allowableValues = enumValues.stream() .map(enumValue -> enumValue instanceof DescribedValue describedValue ? AllowableValue.fromDescribedValue(describedValue) : new AllowableValue(enumValue.name())) .toList(); } return this; } /** * Sets the Allowable Values for this Property. * * @param values constrained set of values * @return the builder */ public Builder allowableValues(final String... values) { if (null != values) { this.allowableValues = Arrays.stream(values).map(AllowableValue::new).toList(); } return this; } /** * Sets the Allowable Values for this Property. *

* Uses the information provided by each {@link DescribedValue} (see {@link DescribedValue#getValue()}, {@link DescribedValue#getDisplayName()}, * {@link DescribedValue#getDescription()}) to populate the {@link AllowableValue}s. * * @param values constrained set of values * @return the builder */ public Builder allowableValues(final DescribedValue... values) { if (null != values) { this.allowableValues = Arrays.stream(values).map(AllowableValue::fromDescribedValue).toList(); } return this; } /** * Clears all Allowable Values from this Property * * @return the builder */ public Builder clearAllowableValues() { this.allowableValues = null; return this; } /** * @param required true if yes; false otherwise * @return the builder */ public Builder required(final boolean required) { this.required = required; return this; } /** * @param sensitive true if sensitive; false otherwise * @return the builder */ public Builder sensitive(final boolean sensitive) { this.sensitive = sensitive; return this; } /** * @param validator for the property * @return the builder */ public Builder addValidator(final Validator validator) { if (validator != null) { validators.add(validator); } return this; } /** * Clear all Validators from this Property * * @return the builder */ public Builder clearValidators() { validators.clear(); return this; } /** * Specifies that this property provides the identifier of a Controller * Service that implements the given interface * * @param controllerServiceDefinition the interface that is implemented * by the Controller Service * @return the builder */ public Builder identifiesControllerService(final Class controllerServiceDefinition) { if (controllerServiceDefinition != null) { this.controllerServiceDefinition = controllerServiceDefinition; } return this; } private boolean isValueAllowed(final String value) { if (allowableValues == null || value == null) { return true; } for (final AllowableValue allowableValue : allowableValues) { if (allowableValue.getValue().equals(value)) { return true; } } return false; } /** * Specifies that this property references one or more resources that are external to NiFi that the component is meant to consume. * Any property descriptor that identifies an external resource will be automatically validated against the following rules: *

    *
  • If the ResourceCardinality is SINGLE, the given property value must be a file, a directory, or a URL that uses a protocol of http/https/file.
  • *
  • The given resourceTypes dictate which types of input are allowed. For example, if identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE) * is used, the input must be a regular file. If identifiesExternalResource(ResourceCardinality.SINGLE, ResourceType.FILE, ResourceType.DIRECTORY) is used, then the input * must be exactly one file OR directory. *
  • *
  • If the ResourceCardinality is MULTIPLE, the given property value may consist of one or more resources, each separted by a comma and optional white space.
  • *
*

* Generally, any property descriptor that makes use of the {@link #dynamicallyModifiesClasspath(boolean)} method to dynamically update its classpath should also * make use of this method, specifying which types of resources are allowed and how many. * * @param cardinality specifies how many resources the property should allow * @param resourceType the type of resource that is allowed * @param additionalResourceTypes if more than one type of resource is allowed, any resource type in addition to the given resource type may be provided * @return the builder */ public Builder identifiesExternalResource(final ResourceCardinality cardinality, final ResourceType resourceType, final ResourceType... additionalResourceTypes) { Objects.requireNonNull(cardinality); Objects.requireNonNull(resourceType); final Set resourceTypes = new HashSet<>(); resourceTypes.add(resourceType); resourceTypes.addAll(Arrays.asList(additionalResourceTypes)); this.resourceDefinition = new StandardResourceDefinition(cardinality, resourceTypes); return this; } /** * Establishes a relationship between this Property and the given property by declaring that this Property is only relevant if the given Property has a non-null value. * Furthermore, if one or more explicit Allowable Values are provided, this Property will not be relevant unless the given Property's value is equal to one of the given Allowable Values. * If this method is called multiple times, each with a different dependency, then a relationship is established such that this Property is relevant only if all dependencies are satisfied. *

* In the case that this property is NOT considered to be relevant (meaning that it depends on a property whose value is not specified, or whose value does not match one of the given * Allowable Values), the property will not be shown in the component's configuration in the User Interface. Additionally, this property's value will not be considered for * validation. That is, if this property is configured with an invalid value and this property depends on Property Foo, and Property Foo does not have a value set, then the component * will still be valid, because the value of this property is irrelevant. *

* If the given property is not relevant (because its dependencies are not satisfied), this property is also considered not to be valid. * * @param property the property that must be set in order for this property to become relevant * @param dependentValues the possible values for the given property for which this Property is relevant * @return the builder */ public Builder dependsOn(final PropertyDescriptor property, final AllowableValue... dependentValues) { if (dependencies == null) { dependencies = new HashSet<>(); } if (dependentValues.length == 0) { dependencies.add(new PropertyDependency(property.getName(), property.getDisplayName())); } else { final Set dependentValueSet = new HashSet<>(); for (final AllowableValue value : dependentValues) { dependentValueSet.add(value.getValue()); } dependencies.add(new PropertyDependency(property.getName(), property.getDisplayName(), dependentValueSet)); } return this; } /** * Establishes a relationship between this Property and the given property by declaring that this Property is only relevant if the given Property has a value equal to one of the given * String arguments. * If this method is called multiple times, each with a different dependency, then a relationship is established such that this Property is relevant only if all dependencies are satisfied. *

* In the case that this property is NOT considered to be relevant (meaning that it depends on a property whose value is not specified, or whose value does not match one of the given * Allowable Values), the property will not be shown in the component's configuration in the User Interface. Additionally, this property's value will not be considered for * validation. That is, if this property is configured with an invalid value and this property depends on Property Foo, and Property Foo does not have a value set, then the component * will still be valid, because the value of this property is irrelevant. *

* If the given property is not relevant (because its dependencies are not satisfied), this property is also considered not to be valid. * * @param property the property that must be set in order for this property to become relevant * @param firstDependentValue the first value for the given property for which this Property is relevant * @param additionalDependentValues any other values for the given property for which this Property is relevant * @return the builder */ public Builder dependsOn(final PropertyDescriptor property, final String firstDependentValue, final String... additionalDependentValues) { final AllowableValue[] dependentValues = new AllowableValue[additionalDependentValues.length + 1]; dependentValues[0] = new AllowableValue(firstDependentValue); int i = 1; for (final String additionalDependentValue : additionalDependentValues) { dependentValues[i++] = new AllowableValue(additionalDependentValue); } return dependsOn(property, dependentValues); } /** * Establishes a relationship between this Property and the given property by declaring that this Property is only relevant if the given Property has a value equal to one of the given * {@link DescribedValue} arguments. * If this method is called multiple times, each with a different dependency, then a relationship is established such that this Property is relevant only if all dependencies are satisfied. *

* In the case that this property is NOT considered to be relevant (meaning that it depends on a property whose value is not specified, or whose value does not match one of the given * Described Values), the property will not be shown in the component's configuration in the User Interface. Additionally, this property's value will not be considered for * validation. That is, if this property is configured with an invalid value and this property depends on Property Foo, and Property Foo does not have a value set, then the component * will still be valid, because the value of this property is irrelevant. *

* If the given property is not relevant (because its dependencies are not satisfied), this property is also considered not to be valid. * * @param property the property that must be set in order for this property to become relevant * @param firstDependentValue the first value for the given property for which this Property is relevant * @param additionalDependentValues any other values for the given property for which this Property is relevant * @return the builder */ public Builder dependsOn(final PropertyDescriptor property, final DescribedValue firstDependentValue, final DescribedValue... additionalDependentValues) { final AllowableValue[] dependentValues = new AllowableValue[additionalDependentValues.length + 1]; dependentValues[0] = AllowableValue.fromDescribedValue(firstDependentValue); int i = 1; for (final DescribedValue additionalDependentValue : additionalDependentValues) { dependentValues[i++] = AllowableValue.fromDescribedValue(additionalDependentValue); } return dependsOn(property, dependentValues); } /** * Clear all Dependencies from this Property * * @return the builder */ public Builder clearDependsOn() { this.dependencies = new HashSet<>(); return this; } /** * @return a PropertyDescriptor as configured * @throws IllegalStateException if allowable values are configured but * no default value is set, or the default value is not contained within * the allowable values. */ public PropertyDescriptor build() { if (name == null) { throw new IllegalStateException("Must specify a name"); } if (!isValueAllowed(defaultValue)) { throw new IllegalStateException("Default value [" + defaultValue + "] is not in the set of allowable values"); } return new PropertyDescriptor(this); } } public String getDisplayName() { return displayName; } public String getName() { return name; } public String getDescription() { return description; } public String getDefaultValue() { return defaultValue; } public boolean isRequired() { return required; } public boolean isSensitive() { return sensitive; } public boolean isDynamic() { return dynamic; } public boolean isExpressionLanguageSupported() { return !expressionLanguageScope.equals(ExpressionLanguageScope.NONE); } public ExpressionLanguageScope getExpressionLanguageScope() { return expressionLanguageScope; } public boolean isDynamicClasspathModifier() { return dynamicallyModifiesClasspath; } public Class getControllerServiceDefinition() { return controllerServiceDefinition; } public List getValidators() { return validators; } public List getAllowableValues() { return allowableValues; } public Set getDependencies() { return dependencies; } public ResourceDefinition getResourceDefinition() { return resourceDefinition; } @Override public boolean equals(final Object other) { if (this == other) { return true; } if (other instanceof PropertyDescriptor otherPropertyDescriptor) { return this.name.equals(otherPropertyDescriptor.name); } return false; } @Override public int hashCode() { return 287 + this.name.hashCode() * 47; } @Override public String toString() { return getClass().getSimpleName() + "[" + displayName + "]"; } private static final class ConstrainedSetValidator implements Validator { private static final String POSITIVE_EXPLANATION = "Given value found in allowed set"; private static final String NEGATIVE_EXPLANATION = "Given value not found in allowed set '%1$s'"; private static final String VALUE_DELIMITER = ", "; private final String validStrings; private final Collection validValues; /** * Constructs a validator that will check if the given value is in the * given set. * * @param validValues values which are acceptible * @throws NullPointerException if the given validValues is null */ private ConstrainedSetValidator(final Collection validValues) { this.validValues = validValues.stream().map(AllowableValue::getValue).toList(); this.validStrings = String.join(VALUE_DELIMITER, this.validValues); } @Override public ValidationResult validate(final String subject, final String input, final ValidationContext context) { final ValidationResult.Builder builder = new ValidationResult.Builder(); builder.input(input); builder.subject(subject); if (validValues.contains(input)) { builder.valid(true); builder.explanation(POSITIVE_EXPLANATION); } else { builder.valid(false); builder.explanation(String.format(NEGATIVE_EXPLANATION, validStrings)); } return builder.build(); } } private static class ResourceDefinitionValidator implements Validator { private final ResourceDefinition resourceDefinition; private final ExpressionLanguageScope expressionLanguageScope; public ResourceDefinitionValidator(final ResourceDefinition resourceDefinition, final ExpressionLanguageScope expressionLanguageScope) { this.resourceDefinition = resourceDefinition; this.expressionLanguageScope = expressionLanguageScope; } @Override public ValidationResult validate(final String subject, final String configuredInput, final ValidationContext context) { final ValidationResult.Builder resultBuilder = new ValidationResult.Builder() .input(configuredInput) .subject(subject); if (configuredInput == null) { return resultBuilder.valid(false) .explanation("No value specified") .build(); } // If Expression Language is supported and is used in the property value, we cannot perform validation against the configured // input unless the Expression Language is expressly limited to only env/syst properties variables. In that case, we can evaluate // it and then validate the value after evaluating the Expression Language. String input = configuredInput; if (context.isExpressionLanguageSupported(subject) && context.isExpressionLanguagePresent(configuredInput)) { if (expressionLanguageScope != null && expressionLanguageScope == ExpressionLanguageScope.ENVIRONMENT) { input = context.newPropertyValue(configuredInput).evaluateAttributeExpressions().getValue(); resultBuilder.input(input); } else { return resultBuilder.valid(true) .explanation("Expression Language is present, so validation of property value cannot be performed") .build(); } } // If the property can be text, then there's nothing to validate. Anything that is entered may be valid. // This will be improved in the future, by allowing the user to specify the type of resource that is being referenced. // Until then, we will simply require that the component perform any necessary validation. final boolean allowsText = resourceDefinition.getResourceTypes().contains(ResourceType.TEXT); if (allowsText) { return resultBuilder.valid(true) .explanation("Property allows for Resource Type of Text, so validation of property value cannot be performed") .build(); } final String[] splits = input.split(","); if (resourceDefinition.getCardinality() == ResourceCardinality.SINGLE && splits.length > 1) { return resultBuilder.valid(false) .explanation("Property only supports a single Resource but " + splits.length + " resources were specified") .build(); } final Set resourceTypes = resourceDefinition.getResourceTypes(); final List nonExistentResources = new ArrayList<>(); int count = 0; for (final String split : splits) { final ResourceReference resourceReference = new StandardResourceReferenceFactory().createResourceReference(split, resourceDefinition); if (resourceReference == null) { continue; } count++; final boolean accessible = resourceReference.isAccessible(); if (!accessible) { nonExistentResources.add(resourceReference.getLocation()); continue; } if (!resourceTypes.contains(resourceReference.getResourceType())) { return resultBuilder.valid(false) .explanation("Specified Resource is a " + resourceReference.getResourceType().name() + " but this property does not allow this type of resource") .build(); } } if (count == 0) { return resultBuilder.valid(false) .explanation("No resources were specified") .build(); } if (!nonExistentResources.isEmpty()) { return resultBuilder.valid(false) .explanation("The specified resource(s) do not exist or could not be accessed: " + nonExistentResources) .build(); } return resultBuilder.valid(true) .build(); } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy