001/**
002 * Copyright (C) 2006-2024 Talend Inc. - www.talend.com
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.talend.sdk.component.tools.validator;
017
018import java.io.BufferedInputStream;
019import java.io.File;
020import java.io.FileInputStream;
021import java.io.IOException;
022import java.io.InputStream;
023import java.util.List;
024import java.util.Locale;
025import java.util.Objects;
026import java.util.Optional;
027import java.util.Properties;
028import java.util.stream.Stream;
029
030import org.apache.xbean.finder.AnnotationFinder;
031import org.talend.sdk.component.api.configuration.ui.DefaultValue;
032import org.talend.sdk.component.tools.ComponentValidator.Configuration;
033import org.talend.sdk.component.tools.validator.Validators.ValidatorHelper;
034
035public class LocalConfigurationValidator implements Validator {
036
037    private final Validators.ValidatorHelper helper;
038
039    private final Configuration configuration;
040
041    public LocalConfigurationValidator(final ValidatorHelper helper, final Configuration configuration) {
042        this.helper = helper;
043        this.configuration = configuration;
044    }
045
046    @Override
047    public Stream<String> validate(final AnnotationFinder finder, final List<Class<?>> components) {
048        final String pluginId = this.pluginId();
049        Stream<String> errStart = helper
050                .componentClassFiles() //
051                .map(root -> new File(root, "TALEND-INF/local-configuration.properties")) //
052                .filter(File::exists) //
053                .flatMap(props -> {
054                    final Properties properties = new Properties();
055                    try (final InputStream stream = new BufferedInputStream(new FileInputStream(props))) {
056                        properties.load(stream);
057                    } catch (final IOException e) {
058                        throw new IllegalStateException(e);
059                    }
060                    return properties
061                            .stringPropertyNames()
062                            .stream()
063                            .filter(it -> !it.toLowerCase(Locale.ROOT).startsWith(pluginId + "."))
064                            .map(it -> "'" + it + "' does not start with '" + pluginId + "', "
065                                    + "it is recommended to prefix all keys by the family");
066                });
067
068        // then check the @DefaultValue annotation
069        Stream<String> familyName = Stream
070                .concat(finder.findAnnotatedFields(DefaultValue.class).stream(),
071                        finder.findAnnotatedConstructorParameters(DefaultValue.class).stream())
072                .map(d -> {
073                    final DefaultValue annotation = d.getAnnotation(DefaultValue.class);
074                    if (annotation.value().startsWith("local_configuration:") && !annotation
075                            .value()
076                            .toLowerCase(Locale.ROOT)
077                            .startsWith("local_configuration:" + pluginId + ".")) {
078                        return d + " does not start with family name (followed by a dot): '" + pluginId + "'";
079                    }
080                    return null;
081                })
082                .filter(Objects::nonNull);
083
084        return Stream.concat(errStart, familyName);
085    }
086
087    private String pluginId() {
088        return Optional.of(configuration).map(Configuration::getPluginId).orElseGet(this::guessPluginId);
089    }
090
091    private String guessPluginId() { // assume folder name == module id
092        return this.helper.componentClassFiles().map(f -> {
093            if (!f.isDirectory()) {
094                return null;
095            }
096            File current = f;
097            int iteration = 5;
098            while (iteration-- > 0 && current != null) {
099                final File currentRef = current;
100                if (Stream
101                        .of("classes", "target", "main", "java", "build")
102                        .anyMatch(it -> it.equals(currentRef.getName()))) {
103                    current = current.getParentFile();
104                } else {
105                    return current.getName();
106                }
107            }
108            return null;
109        }) //
110                .filter(Objects::nonNull)
111                .findFirst() //
112                .orElseThrow(() -> new IllegalArgumentException("No pluginId set"));
113    }
114}