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

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

There is a newer version: 2.0.0
Show 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 java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import org.apache.nifi.controller.ControllerService;

/**
 * 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 not this property supports the Attribute Expression
     * Language
     */
    private final boolean expressionLanguageSupported;

    /**
     * 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;

    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;
        this.required = builder.required;
        this.sensitive = builder.sensitive;
        this.dynamic = builder.dynamic;
        this.expressionLanguageSupported = builder.expressionLanguageSupported;
        this.controllerServiceDefinition = builder.controllerServiceDefinition;
        this.validators = new ArrayList<>(builder.validators);
    }

    @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;
            }
        }

        // if the property descriptor identifies a Controller Service, validate that the ControllerService exists, is of the correct type, and is valid
        if (controllerServiceDefinition != null) {
            final Set validIdentifiers = context.getControllerServiceLookup().getControllerServiceIdentifiers(controllerServiceDefinition);
            if (validIdentifiers != null && validIdentifiers.contains(input)) {
                final ControllerService controllerService = context.getControllerServiceLookup().getControllerService(input);
                if (!context.isValidationRequired(controllerService)) {
                    return new ValidationResult.Builder()
                            .input(input)
                            .subject(getName())
                            .valid(true)
                            .build();
                }

                final String serviceId = controllerService.getIdentifier();
                if (!isDependentServiceEnableable(context, serviceId)) {
                    return new ValidationResult.Builder()
                            .input(context.getControllerServiceLookup().getControllerServiceName(serviceId))
                            .subject(getName())
                            .valid(false)
                            .explanation("Controller Service " + controllerService + " is disabled")
                            .build();
                }

                final Collection validationResults = controllerService.validate(context.getControllerServiceValidationContext(controllerService));
                final List invalidResults = new ArrayList<>();
                for (final ValidationResult result : validationResults) {
                    if (!result.isValid()) {
                        invalidResults.add(result);
                    }
                }
                if (!invalidResults.isEmpty()) {
                    return new ValidationResult.Builder()
                        .input(input)
                        .subject(getName())
                        .valid(false)
                        .explanation("Controller Service is not valid: " + (invalidResults.size() > 1 ? invalidResults : invalidResults.get(0)))
                        .build();
                }

                return new ValidationResult.Builder()
                        .input(input)
                        .subject(getName())
                        .valid(true)
                        .build();
            } else {
                return new ValidationResult.Builder()
                        .input(input)
                        .subject(getName())
                        .valid(false)
                        .explanation("Invalid Controller Service: " + input + " is not a valid Controller Service Identifier or does not reference the correct type of Controller Service")
                        .build();
            }
        }

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

    /**
     * Will validate if the dependent service (service identified with the
     * 'serviceId') is 'enableable' which means that the dependent service is
     * either in ENABLING or ENABLED state. The important issue here is to
     * understand the order in which states are assigned:
     *
     * - Upon the initialization of the service its state is set to ENABLING.
     *
     * - Transition to ENABLED will happen asynchronously.
     *
     * So we check first for ENABLING state and if it succeeds we skip the check
     * for ENABLED state even though by the time this method returns the
     * dependent service's state could be fully ENABLED.
     */
    private boolean isDependentServiceEnableable(final ValidationContext context, final String serviceId) {
        boolean enableable = context.getControllerServiceLookup().isControllerServiceEnabling(serviceId);
        if (!enableable) {
            enableable = context.getControllerServiceLookup().isControllerServiceEnabled(serviceId);
        }
        return enableable;
    }

    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 boolean required = false;
        private boolean sensitive = false;
        private boolean expressionLanguageSupported = false;
        private boolean dynamic = false;
        private Class controllerServiceDefinition;
        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.expressionLanguageSupported = specDescriptor.expressionLanguageSupported;
            this.controllerServiceDefinition = specDescriptor.getControllerServiceDefinition();
            this.validators = new ArrayList<>(specDescriptor.validators);
            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 value indicating whether or not this Property will support
         * the Attribute Expression Language.
         *
         * @param supported true if yes; false otherwise
         * @return the builder
         */
        public Builder expressionLanguageSupported(final boolean supported) {
            this.expressionLanguageSupported = supported;
            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(AllowableValue...)}) and this value is not
         * one of those Allowable Values and Exception will be thrown. If the
         * Allowable Values have been set using the
         * {@link #allowableValues(AllowableValue...)} method, the default value
         * should be set to the "Value" of the {@link AllowableValue} object
         * (see {@link AllowableValue#getValue()}).
         *
         * @param value default value
         * @return the builder
         */
        public Builder defaultValue(final String value) {
            if (null != value) {
                this.defaultValue = value;
            }
            return this;
        }

        public Builder dynamic(final boolean dynamic) {
            this.dynamic = dynamic;
            return this;
        }

        /**
         * @param values contrained set of values
         * @return the builder
         */
        public Builder allowableValues(final Set values) {
            if (null != values) {
                this.allowableValues = new ArrayList<>();

                for (final String value : values) {
                    this.allowableValues.add(new AllowableValue(value, value));
                }
            }
            return this;
        }

        public > Builder allowableValues(final E[] values) {
            if (null != values) {
                this.allowableValues = new ArrayList<>();
                for (final E value : values) {
                    allowableValues.add(new AllowableValue(value.name(), value.name()));
                }
            }
            return this;
        }

        /**
         * @param values constrained set of values
         * @return the builder
         */
        public Builder allowableValues(final String... values) {
            if (null != values) {
                this.allowableValues = new ArrayList<>();
                for (final String value : values) {
                    allowableValues.add(new AllowableValue(value, value));
                }
            }
            return this;
        }

        /**
         * Sets the Allowable Values for this Property
         *
         * @param values contrained set of values
         * @return the builder
         */
        public Builder allowableValues(final AllowableValue... values) {
            if (null != values) {
                this.allowableValues = Arrays.asList(values);
            }
            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;
        }

        /**
         * 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;
        }

        /**
         * @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 expressionLanguageSupported;
    }

    public Class getControllerServiceDefinition() {
        return controllerServiceDefinition;
    }

    public List getValidators() {
        return Collections.unmodifiableList(validators);
    }

    public List getAllowableValues() {
        return allowableValues == null ? null : Collections.unmodifiableList(allowableValues);
    }

    @Override
    public boolean equals(final Object other) {
        if (other == null) {
            return false;
        }
        if (!(other instanceof PropertyDescriptor)) {
            return false;
        }
        if (this == other) {
            return true;
        }

        final PropertyDescriptor desc = (PropertyDescriptor) other;
        return this.name.equals(desc.name);
    }

    @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_DEMARCATOR = ", ";
        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) {
            String validVals = "";
            if (!validValues.isEmpty()) {
                final StringBuilder valuesBuilder = new StringBuilder();
                for (final AllowableValue value : validValues) {
                    valuesBuilder.append(value).append(VALUE_DEMARCATOR);
                }
                validVals = valuesBuilder.substring(0, valuesBuilder.length() - VALUE_DEMARCATOR.length());
            }
            validStrings = validVals;

            this.validValues = new ArrayList<>(validValues.size());
            for (final AllowableValue value : validValues) {
                this.validValues.add(value.getValue());
            }
        }

        @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();
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy