001/**
002 * Copyright (C) 2006-2025 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.lang.reflect.Method;
019import java.util.Arrays;
020import java.util.Collections;
021import java.util.HashMap;
022import java.util.HashSet;
023import java.util.List;
024import java.util.Map;
025import java.util.Set;
026import java.util.stream.Collectors;
027import java.util.stream.Stream;
028
029import javax.annotation.PostConstruct;
030
031import org.apache.xbean.finder.AnnotationFinder;
032import org.talend.sdk.component.api.configuration.Option;
033import org.talend.sdk.component.api.input.Emitter;
034import org.talend.sdk.component.api.input.PartitionMapper;
035
036/**
037 * Check option parameters on {@link PostConstruct} methods in {@link Emitter} class.
038 */
039public class OptionParameterValidator implements Validator {
040
041    private static final Map<String, Set<Class<?>>> ALLOWED_OPTION_PARAMETERS = new HashMap<>();
042
043    static {
044        ALLOWED_OPTION_PARAMETERS.put(Option.MAX_DURATION_PARAMETER,
045                new HashSet<>(Arrays.asList(int.class, Integer.class, long.class, Long.class)));
046        ALLOWED_OPTION_PARAMETERS.put(Option.MAX_RECORDS_PARAMETER,
047                new HashSet<>(Arrays.asList(int.class, Integer.class, long.class, Long.class)));
048    }
049
050    @Override
051    public Stream<String> validate(final AnnotationFinder finder, final List<Class<?>> components) {
052        final Set<Class<?>> emitterClassesOfPartition = finder.findAnnotatedMethods(Emitter.class)
053                .stream()
054                .filter(m -> m.getDeclaringClass().isAnnotationPresent(PartitionMapper.class))
055                .map(Method::getReturnType)
056                .collect(Collectors.toSet());
057
058        return finder.findAnnotatedMethods(PostConstruct.class)
059                .stream()
060                .filter(m -> m.getParameterCount() != 0)
061                .filter(m -> emitterClassesOfPartition.contains(m.getDeclaringClass())
062                        || m.getDeclaringClass().isAnnotationPresent(Emitter.class))
063                .flatMap(m -> Stream.concat(
064                        // check that the parameter has Option annotation
065                        Arrays.stream(m.getParameters())
066                                .filter(p -> !p.isAnnotationPresent(Option.class))
067                                .map(p -> "Parameter '" + p.getName()
068                                        + "' should be either annotated with @Option or removed"),
069                        Stream.concat(
070                                // check option value name
071                                Arrays.stream(m.getParameters())
072                                        .filter(p -> p.isAnnotationPresent(Option.class))
073                                        .filter(p -> !ALLOWED_OPTION_PARAMETERS
074                                                .containsKey(p.getAnnotation(Option.class).value()))
075                                        .map(p -> "Option value on the parameter '" + p.getName()
076                                                + "' is not acceptable. "
077                                                + "Acceptable values: " + acceptableOptionValues()),
078                                // check option parameters' type
079                                Arrays.stream(m.getParameters())
080                                        .filter(p -> p.isAnnotationPresent(Option.class))
081                                        .filter(p -> ALLOWED_OPTION_PARAMETERS
082                                                .containsKey(p.getAnnotation(Option.class).value()))
083                                        .filter(p -> !ALLOWED_OPTION_PARAMETERS
084                                                .get(p.getAnnotation(Option.class).value())
085                                                .contains(p.getType()))
086                                        .map(p -> "The '" + p.getName() + "' parameter's type is not acceptable. "
087                                                + "Acceptable types: "
088                                                + acceptableTypeValues(p.getAnnotation(Option.class).value())))))
089                .sorted();
090    }
091
092    private static String acceptableOptionValues() {
093        return ALLOWED_OPTION_PARAMETERS.keySet()
094                .stream()
095                .sorted()
096                .collect(Collectors.joining(",", "[", "]"));
097    }
098
099    private static String acceptableTypeValues(final String optionValue) {
100        return ALLOWED_OPTION_PARAMETERS.getOrDefault(optionValue, Collections.emptySet())
101                .stream()
102                .map(Class::getSimpleName)
103                .sorted()
104                .collect(Collectors.joining(",", "[", "]"));
105    }
106}