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 static org.talend.sdk.component.runtime.manager.reflect.Constructors.findConstructor; 019 020import java.lang.annotation.Annotation; 021import java.lang.reflect.Field; 022import java.lang.reflect.Parameter; 023import java.lang.reflect.ParameterizedType; 024import java.util.List; 025import java.util.Objects; 026import java.util.stream.Stream; 027 028import org.apache.xbean.finder.AnnotationFinder; 029import org.talend.sdk.component.api.configuration.ui.widget.Structure; 030import org.talend.sdk.component.api.input.Emitter; 031import org.talend.sdk.component.api.input.PartitionMapper; 032import org.talend.sdk.component.api.processor.Processor; 033import org.talend.sdk.component.api.standalone.DriverRunner; 034import org.talend.sdk.component.runtime.visitor.ModelListener; 035import org.talend.sdk.component.runtime.visitor.ModelVisitor; 036import org.talend.sdk.component.tools.validator.Validators.ValidatorHelper; 037 038public class ModelValidator implements Validator { 039 040 private final boolean validateComponent; 041 042 private final ValidatorHelper helper; 043 044 public ModelValidator(final boolean validateComponent, final ValidatorHelper helper) { 045 this.validateComponent = validateComponent; 046 this.helper = helper; 047 } 048 049 @Override 050 public Stream<String> validate(final AnnotationFinder finder, final List<Class<?>> components) { 051 final Stream<String> errorsAnnotations = components 052 .stream() 053 .filter(this::containsIncompatibleAnnotation) 054 .map(i -> i + " has conflicting component annotations, ensure it has a single one") 055 .sorted(); 056 057 final Stream<String> errorsParamConstructors = components 058 .stream() 059 .filter(c -> countParameters(findConstructor(c).getParameters()) > 1) 060 .map(c -> "Component must use a single root option. '" + c.getName() + "'") 061 .sorted(); 062 063 final ModelVisitor modelVisitor = new ModelVisitor(); 064 final ModelListener noop = new ModelListener() { 065 }; 066 067 final Stream<String> errorsConfig = components.stream().map(c -> { 068 try { 069 modelVisitor.visit(c, noop, this.validateComponent); 070 return null; 071 } catch (final RuntimeException re) { 072 return re.getMessage(); 073 } 074 }).filter(Objects::nonNull).sorted(); 075 076 // limited config types 077 final Stream<String> errorStructure = finder 078 .findAnnotatedFields(Structure.class) 079 .stream() 080 .filter(f -> !ParameterizedType.class.isInstance(f.getGenericType()) || !isListObject(f)) 081 .map(f -> f.getDeclaringClass() + "#" + f.getName() 082 + " uses @Structure but is not a List<String> nor a List<Object>") 083 .sorted(); 084 085 return Stream 086 .of(errorsAnnotations, errorsParamConstructors, errorsConfig, errorStructure) 087 .reduce(Stream::concat) 088 .orElse(Stream.empty()); 089 } 090 091 private boolean containsIncompatibleAnnotation(final Class<?> clazz) { 092 return Stream 093 .of(PartitionMapper.class, Processor.class, Emitter.class, DriverRunner.class) 094 .filter((Class<? extends Annotation> an) -> clazz.isAnnotationPresent(an)) 095 .count() > 1; 096 097 } 098 099 private int countParameters(final Parameter[] params) { 100 return (int) Stream.of(params).filter((Parameter p) -> !this.helper.isService(p)).count(); 101 } 102 103 private boolean isListObject(final Field f) { 104 final ParameterizedType pt = ParameterizedType.class.cast(f.getGenericType()); 105 return List.class == pt.getRawType() && pt.getActualTypeArguments().length == 1; 106 } 107 108}