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

org.talend.sdk.component.tools.ComponentValidator Maven / Gradle / Ivy

/**
 * Copyright (C) 2006-2024 Talend Inc. - www.talend.com
 *
 * 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.talend.sdk.component.tools;

import static java.util.Collections.emptyList;
import static java.util.Optional.ofNullable;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toCollection;
import static java.util.stream.Collectors.toList;
import static java.util.stream.Stream.of;
import static org.talend.sdk.component.runtime.manager.reflect.Constructors.findConstructor;

import java.io.File;
import java.lang.reflect.Parameter;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.ResourceBundle;
import java.util.ServiceLoader;
import java.util.Set;
import java.util.TreeSet;
import java.util.stream.Collector;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;

import org.apache.xbean.finder.AnnotationFinder;
import org.talend.sdk.component.api.component.Components;
import org.talend.sdk.component.api.component.Icon;
import org.talend.sdk.component.runtime.manager.ParameterMeta;
import org.talend.sdk.component.runtime.manager.reflect.ParameterModelService;
import org.talend.sdk.component.runtime.manager.reflect.parameterenricher.BaseParameterEnricher;
import org.talend.sdk.component.runtime.manager.service.LocalConfigurationService;
import org.talend.sdk.component.runtime.manager.xbean.registry.EnrichedPropertyEditorRegistry;
import org.talend.sdk.component.tools.spi.ValidationExtension;
import org.talend.sdk.component.tools.validator.Validators;
import org.talend.sdk.component.tools.validator.Validators.ValidatorHelper;

import lombok.Data;

// IMPORTANT: this class is used by reflection in gradle integration, don't break signatures without checking it
public class ComponentValidator extends BaseTask {

    private final Configuration configuration;

    private final Log log;

    private final ParameterModelService parameterModelService =
            new ParameterModelService(new EnrichedPropertyEditorRegistry());

    private final SvgValidator validator = new SvgValidator();

    private final Map, List> parametersCache = new HashMap<>();

    private final List extensions;

    public ComponentValidator(final Configuration configuration, final File[] classes, final Object log) {
        super(classes);
        this.configuration = configuration;

        try {
            this.log = Log.class.isInstance(log) ? Log.class.cast(log) : new ReflectiveLog(log);
        } catch (final NoSuchMethodException e) {
            throw new IllegalArgumentException(e);
        }
        this.extensions = StreamSupport
                .stream(ServiceLoader.load(ValidationExtension.class).spliterator(), false)
                .collect(toList());
    }

    @Override
    public void run() {
        final AnnotationFinder finder = newFinder();
        final List> components = ComponentHelper
                .componentMarkers()
                .flatMap(a -> finder.findAnnotatedClasses(a).stream())
                .collect(toList());
        components.forEach(c -> log.debug("Found component: " + c));

        final Set errors = new LinkedHashSet<>();
        final Validators.ValidatorHelper helper = new ValidatorHelper() {

            @Override
            public boolean isService(final Parameter parameter) {
                return ComponentValidator.this.parameterModelService
                        .isService(new ParameterModelService.Param(parameter));
            }

            @Override
            public ResourceBundle findResourceBundle(final Class component) {
                return ComponentValidator.this.findResourceBundle(component);
            }

            @Override
            public String validateFamilyI18nKey(final Class clazz, final String... keys) {
                return ComponentValidator.this.validateFamilyI18nKey(clazz, keys);
            }

            @Override
            public List buildOrGetParameters(final Class c) {
                return ComponentValidator.this.buildOrGetParameters(c);
            }

            @Override
            public String validateIcon(final Icon annotation, final Collection errors) {
                return ComponentValidator.this.validateIcon(annotation, errors);
            }

            @Override
            public ParameterModelService getParameterModelService() {
                return ComponentValidator.this.parameterModelService;
            }

            @Override
            public Stream componentClassFiles() {
                if (ComponentValidator.this.classes == null) {
                    return Stream.empty();
                }
                return Stream.of(ComponentValidator.this.classes);
            }
        };

        final Validators validators = Validators.build(configuration, helper, extensions);
        final Set errorsFromValidator = validators.validate(finder, components);
        errors.addAll(errorsFromValidator);

        if (!errors.isEmpty()) {
            final List preparedErrors =
                    errors.stream().map(it -> it.replace("java.lang.", "").replace("java.util.", "")).collect(toList());
            preparedErrors.forEach(log::error);
            throw new IllegalStateException(
                    "Some error were detected:" + preparedErrors.stream().collect(joining("\n- ", "\n- ", "")));
        }

        log.info("Validated components: " + components.stream().map(Class::getSimpleName).collect(joining(", ")));
    }

    private String validateIcon(final Icon annotation, final Collection errors) {
        if (classes.length == 0) {
            return null;
        }

        if (annotation.value() == Icon.IconType.CUSTOM) {
            final String icon = annotation.custom();
            final Set svgs =
                    of(classes).map(it -> new File(it, "icons/" + icon + ".svg")).filter(File::exists).collect(toSet());
            if (svgs.isEmpty()) {
                log.error("No 'icons/" + icon + ".svg' found, this will run in degraded mode in Talend Cloud");
            } else {
                if (configuration.isValidateSvg()) {
                    errors.addAll(svgs.stream().flatMap(this::validateSvg).collect(toSet()));
                }
            }
            if (Stream.of(classes).map(it -> new File(it, "icons/" + icon + "_icon32.png")).noneMatch(File::exists)) {
                return "No icon: '" + icon + "' found, did you create - or generated with svg2png - 'icons/" + icon
                        + "_icon32.png' in resources?";
            }
        }
        return null;
    }

    private Stream validateSvg(final File file) {
        return validator.validate(file.toPath());
    }

    private List buildOrGetParameters(final Class c) {
        return parametersCache
                .computeIfAbsent(c,
                        k -> parameterModelService
                                .buildParameterMetas(findConstructor(c),
                                        ofNullable(c.getPackage()).map(Package::getName).orElse(""),
                                        new BaseParameterEnricher.Context(
                                                new LocalConfigurationService(emptyList(), "tools"))));
    }

    private String validateFamilyI18nKey(final Class clazz, final String... keys) {
        final Class pck =
                ComponentHelper.findPackageOrFail(clazz, apiTester(Components.class), Components.class.getName());
        final String family = pck.getAnnotation(Components.class).family();
        final String baseName = ofNullable(pck.getPackage()).map(p -> p.getName() + ".").orElse("") + "Messages";
        final ResourceBundle bundle = findResourceBundle(pck);
        if (bundle == null) {
            return "No resource bundle for " + clazz.getName() + " translations, you should create a "
                    + baseName.replace('.', '/') + ".properties at least.";
        }

        final Collection missingKeys = of(keys)
                .map(key -> key.replace("${family}", family))
                .filter(k -> !bundle.containsKey(k))
                .collect(toList());
        if (!missingKeys.isEmpty()) {
            return baseName + " is missing the key(s): " + String.join("\n", missingKeys);
        }
        return null;
    }

    private static  Collector> toSet() {
        return toCollection(TreeSet::new);
    }

    @Data
    public static class Configuration {

        private boolean validateFamily;

        private boolean validateSerializable;

        private boolean validateInternationalization;

        private boolean validateHttpClient;

        private boolean validateModel;

        private boolean validateMetadata;

        private boolean validateComponent;

        private boolean validateDataStore;

        private boolean validateDataSet;

        private boolean validateActions;

        private boolean validateDocumentation;

        private boolean validateWording;

        private boolean validateLayout;

        private boolean validateOptionNames;

        private boolean validateLocalConfiguration;

        private boolean validateOutputConnection;

        private boolean validatePlaceholder;

        private boolean validateSvg;

        private boolean validateNoFinalOption;

        private String pluginId;

        private boolean validateExceptions;

        private boolean failOnValidateExceptions;

        private boolean validateRecord;

        private boolean validateSchema;
    }
}




© 2015 - 2026 Weber Informatics LLC | Privacy Policy