Skip to content

Commit

Permalink
Merge branch 'ypiel/TCOMP-2408_ComponentException_only_ExceptionCause…
Browse files Browse the repository at this point in the history
…' of [email protected]:Talend/component-runtime.git into ypiel/TCOMP-2408_ComponentException_only_ExceptionCause
  • Loading branch information
yyin-talend committed Jul 2, 2024
2 parents 498b27c + ea27f5b commit 7e9b3e9
Show file tree
Hide file tree
Showing 5 changed files with 265 additions and 6 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,8 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Parameter;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.List;
import java.util.Map;
Expand All @@ -40,6 +42,7 @@
import org.talend.sdk.component.api.service.ActionType;
import org.talend.sdk.component.api.service.Service;
import org.talend.sdk.component.api.service.completion.DynamicValues;
import org.talend.sdk.component.api.service.dependency.DynamicDependencies;
import org.talend.sdk.component.api.service.discovery.DiscoverDataset;
import org.talend.sdk.component.api.service.healthcheck.HealthCheck;
import org.talend.sdk.component.api.service.schema.DiscoverSchema;
Expand Down Expand Up @@ -95,6 +98,9 @@ public Stream<String> validate(final AnnotationFinder finder, final List<Class<?
// parameters for @DiscoverSchemaExtended
final Stream<String> discoverProcessor = findDiscoverSchemaExtendedErrors(finder);

// parameters for @DynamicDependencies
final Stream<String> dynamicDependencyErrors = findDynamicDependenciesErrors(finder);

// returned type for @Update, for now limit it on objects and not primitives
final Stream<String> updatesErrors = this.findUpdatesErrors(finder);

Expand Down Expand Up @@ -130,6 +136,7 @@ public Stream<String> validate(final AnnotationFinder finder, final List<Class<?
datasetDiscover, //
discover, //
discoverProcessor, //
dynamicDependencyErrors, //
updatesErrors, //
enumProposable, //
proposableWithoutDynamic) //
Expand Down Expand Up @@ -206,6 +213,37 @@ private Stream<String> findDiscoverSchemaExtendedErrors(final AnnotationFinder f
.orElseGet(Stream::empty);
}

/**
* Checks method signature for @DynamicDependencies annotation.
* Valid signatures are:
* <ul>
* <li>public List<String> getDependencies(@Option("configuration") final TheDataset dataset)</li>
* </ul>
*
* @param finder
* @return Errors on @DynamicDependencies method
*/
private Stream<String> findDynamicDependenciesErrors(final AnnotationFinder finder) {

final Stream<String> optionParameter = finder
.findAnnotatedMethods(DynamicDependencies.class)
.stream()
.filter(m -> !hasOption(m) || !hasDatasetParameter(m))
.map(m -> m + " should have a Dataset parameter marked with @Option")
.sorted();

final Stream<String> returnType = finder
.findAnnotatedMethods(DynamicDependencies.class)
.stream()
.filter(m -> !hasStringInList(m))
.map(m -> m + " should return List<String>")
.sorted();

return Stream.of(returnType, optionParameter)
.reduce(Stream::concat)
.orElseGet(Stream::empty);
}

private Stream<String> findUpdatesErrors(final AnnotationFinder finder) {
final Map<String, Method> updates = finder
.findAnnotatedMethods(Update.class)
Expand Down Expand Up @@ -284,6 +322,12 @@ private boolean hasSchemaCorrectNaming(final Method method) {
.count() == 1;
}

private boolean hasDatasetParameter(final Method method) {
return Arrays.stream(method.getParameters())
.filter(p -> p.getType().isAnnotationPresent(DataSet.class))
.count() == 1;
}

private boolean hasBranchCorrectNaming(final Method method) {
return Arrays.stream(method.getParameters())
.filter(p -> String.class.isAssignableFrom(p.getType()))
Expand All @@ -294,4 +338,15 @@ private boolean hasBranchCorrectNaming(final Method method) {
private boolean hasCorrectReturnType(final Method method) {
return Schema.class.isAssignableFrom(method.getReturnType());
}

private boolean hasStringInList(final Method method) {
if (List.class.isAssignableFrom(method.getReturnType())
&& method.getGenericReturnType() instanceof ParameterizedType) {
Type[] actualTypeArguments = ((ParameterizedType) method.getGenericReturnType()).getActualTypeArguments();
if (actualTypeArguments.length > 0) {
return "java.lang.String".equals(actualTypeArguments[0].getTypeName());
}
}
return false;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,23 +15,36 @@
*/
package org.talend.sdk.component.tools.validator;

import java.lang.reflect.Method;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import org.apache.xbean.finder.AnnotationFinder;
import org.talend.sdk.component.api.service.http.HttpClient;
import org.talend.sdk.component.api.service.http.Request;
import org.talend.sdk.component.runtime.manager.service.http.HttpClientFactoryImpl;

public class HttpValidator implements Validator {

@Override
public Stream<String> validate(final AnnotationFinder finder, final List<Class<?>> components) {
return finder
.findAnnotatedClasses(Request.class) //
// If the class extends HttpClient, it should use @Request.
List<String> classErrors = components.stream()
.filter(c -> HttpClient.class.isAssignableFrom(c) && finder.findAnnotatedMethods(Request.class).isEmpty())
.map(c -> c.getCanonicalName() + " extends HttpClient should use @Request on methods")
.collect(Collectors.toList());

List<String> methodError = finder
.findAnnotatedMethods(Request.class) //
.stream() //
.map(Class::getDeclaringClass) //
.map(Method::getDeclaringClass) //
.distinct() //
.flatMap(c -> HttpClientFactoryImpl.createErrors(c).stream()) //
.sorted();
.flatMap(c -> HttpClientFactoryImpl.createErrors(c).stream())
.sorted()
.collect(Collectors.toList());

return Stream.concat(classErrors.stream(), methodError.stream());

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import org.talend.sdk.component.api.service.asyncvalidation.AsyncValidation;
import org.talend.sdk.component.api.service.completion.DynamicValues;
import org.talend.sdk.component.api.service.completion.Suggestions;
import org.talend.sdk.component.api.service.dependency.DynamicDependencies;
import org.talend.sdk.component.api.service.healthcheck.HealthCheck;
import org.talend.sdk.component.api.service.schema.DiscoverSchema;
import org.talend.sdk.component.api.service.update.Update;
Expand Down Expand Up @@ -179,7 +180,7 @@ public static Validators build(final Configuration configuration, final Validato

public static Stream<Class<? extends Annotation>> getActionsStream() {
return of(AsyncValidation.class, DynamicValues.class, HealthCheck.class, DiscoverSchema.class,
Suggestions.class, Update.class);
Suggestions.class, Update.class, DynamicDependencies.class);
}

public static Stream<ParameterMeta> flatten(final Collection<ParameterMeta> options) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import org.talend.sdk.component.api.service.Service;
import org.talend.sdk.component.api.service.completion.DynamicValues;
import org.talend.sdk.component.api.service.completion.Values;
import org.talend.sdk.component.api.service.dependency.DynamicDependencies;
import org.talend.sdk.component.api.service.discovery.DiscoverDataset;
import org.talend.sdk.component.api.service.discovery.DiscoverDatasetResult;
import org.talend.sdk.component.api.service.discovery.DiscoverDatasetResult.DatasetDescription;
Expand Down Expand Up @@ -73,6 +74,19 @@ void validateDiscoverProcessorSchema() {
assertEquals(13, errors.count());
}

@Test
void validateDynamicDependencies() {
final ActionValidator validator = new ActionValidator(new FakeHelper());
AnnotationFinder finder = new AnnotationFinder(new ClassesArchive(ActionDynamicDependenciesOK.class));
final Stream<String> noerrors =
validator.validate(finder, Arrays.asList(ActionDynamicDependenciesOK.class));
assertEquals(0, noerrors.count());

finder = new AnnotationFinder(new ClassesArchive(ActionDynamicDependenciesKO.class));
final Stream<String> errors = validator.validate(finder, Arrays.asList(ActionDynamicDependenciesKO.class));
assertEquals(10, errors.count());
}

@Test
void validate() {
final ActionValidator validator = new ActionValidator(new FakeHelper());
Expand Down Expand Up @@ -263,4 +277,48 @@ public Record guessProcessorSchemaKo6(@Option FakeDataSet configuration, RecordB
return null;
}
}

@Service
static class ActionDynamicDependenciesOK {

@DynamicDependencies("test-all")
public List<String> getDynamicDependencies(@Option("configuration") final FakeDataSet dataset) {
return null;
}
}

@Service
static class ActionDynamicDependenciesKO {

@DynamicDependencies("error: return List<String>")
public String getDynamicDependencies(@Option("configuration") final FakeDataSet dataset) {
return null;
}

@DynamicDependencies("error-param:no Option, no Dataset")
public List<String> getDynamicDependencies2() {
return null;
}

@DynamicDependencies("error: param not dataset")
public String getDynamicDependencies3(@Option("configuration") final FakeDataStore dataset) {
return null;
}

@DynamicDependencies("error: param not option")
public List<Object> getDynamicDependencies4(final FakeDataStore dataset) {
return null;
}

@DynamicDependencies("error: List<T> T not String")
public List<Object> getDynamicDependencies5(@Option("configuration") final FakeDataSet dataset) {
return null;
}

@DynamicDependencies("error: 2 params")
public List<String> getDynamicDependencies6(@Option("configuration") final FakeDataSet dataset,
@Option("configuration") final FakeDataSet dataset2) {
return null;
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
/**
* 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.validator;

import static org.junit.jupiter.api.Assertions.assertEquals;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Stream;

import org.apache.xbean.finder.AnnotationFinder;
import org.apache.xbean.finder.archive.ClassesArchive;
import org.junit.jupiter.api.Test;
import org.talend.sdk.component.api.service.dependency.DynamicDependencies;
import org.talend.sdk.component.api.service.http.HttpClient;
import org.talend.sdk.component.api.service.http.Request;

class HttpClientValidatorTest {

@Test
void validateClassExtendWrongCLass() {
final HttpValidator validator = new HttpValidator();
AnnotationFinder finder = new AnnotationFinder(new ClassesArchive(ClientKoWrongExtends.class));
final Stream<String> errors =
validator.validate(finder, Arrays.asList(ClientKoWrongExtends.class));
assertEquals(1, errors.count());
}

@Test
void validateClassDoNotExtendHttpClient() {
final HttpValidator validator = new HttpValidator();
AnnotationFinder finder = new AnnotationFinder(new ClassesArchive(ClientKoNotExtendsAnything.class));
final Stream<String> errors =
validator.validate(finder, Arrays.asList(ClientKoNotExtendsAnything.class));
assertEquals(1, errors.count());
}

@Test
void validateClassMethodMissingRequestAnnotation() {
final HttpValidator validator = new HttpValidator();
AnnotationFinder finder = new AnnotationFinder(new ClassesArchive(WrongClientMissingOneRequest.class));
final Stream<String> errors =
validator.validate(finder, Arrays.asList(WrongClientMissingOneRequest.class));
assertEquals(1, errors.count());
}

@Test
void validateClassMethodWithOtherAnnotation() {
final HttpValidator validator = new HttpValidator();
AnnotationFinder finder = new AnnotationFinder(new ClassesArchive(WrongClientUsingOneWrongAnnotation.class));
final Stream<String> errors =
validator.validate(finder, Arrays.asList(WrongClientUsingOneWrongAnnotation.class));
assertEquals(1, errors.count());
}

@Test
void validateWrongClientNoMethodRequest() {
final HttpValidator validator = new HttpValidator();
AnnotationFinder finder = new AnnotationFinder(new ClassesArchive(WrongClientNoMethodRequest.class));
final Stream<String> errors =
validator.validate(finder, Arrays.asList(WrongClientNoMethodRequest.class));
assertEquals(1, errors.count());
}

@Test
void validateClassOK() {
final HttpValidator validator = new HttpValidator();
AnnotationFinder finder = new AnnotationFinder(new ClassesArchive(ClientCorrect.class));
final Stream<String> noerrors =
validator.validate(finder, Arrays.asList(ClientCorrect.class));
assertEquals(0, noerrors.count());
}

interface ClientCorrect extends HttpClient {

@Request(method = "POST")
String main1(String ok);
}

interface ClientKoWrongExtends extends List {

@Request
List<Object> main(String payload);
}

interface ClientKoNotExtendsAnything {

@Request
String main();
}

interface WrongClientMissingOneRequest extends HttpClient {

// It misses @Request
String queryA(String ok);

@Request(method = "POST")
String queryB(String ok);
}

interface WrongClientUsingOneWrongAnnotation extends HttpClient {

// It misses @Request
@DynamicDependencies
String queryA(String ok);

@Request(method = "POST")
String queryB(String ok);
}

interface WrongClientNoMethodRequest extends HttpClient {

// It misses @Request
String queryA(String ok);

// It misses @Request
String queryB(String ok);
}
}

0 comments on commit 7e9b3e9

Please sign in to comment.