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 java.lang.String.format;
019import static java.lang.String.join;
020import static java.util.Arrays.asList;
021import static org.talend.sdk.component.tools.validator.RecordValidator.getFullName;
022import static org.talend.sdk.component.tools.validator.RecordValidator.isProducing;
023
024import java.lang.reflect.Method;
025import java.util.ArrayList;
026import java.util.Arrays;
027import java.util.HashSet;
028import java.util.List;
029import java.util.Set;
030import java.util.stream.Stream;
031
032import org.apache.xbean.finder.AnnotationFinder;
033import org.talend.sdk.component.api.record.Schema;
034
035public class SchemaValidator implements Validator {
036
037    @Override
038    public Stream<String> validate(final AnnotationFinder finder, final List<Class<?>> components) {
039        final List<String> messages = new ArrayList<>();
040        components.stream()
041                .forEach(component -> {
042                    messages.addAll(this.validateMethods(component.getDeclaredMethods(), Schema.Builder.class));
043                    messages.addAll(this.validateMethods(component.getDeclaredMethods(), Schema.Entry.Builder.class));
044                });
045        return messages.stream();
046    }
047
048    private List<String> validateMethods(final Method[] methods, final Class checkClass) {
049        final List<String> messages = new ArrayList<>();
050
051        Arrays.stream(methods)
052                .forEach(method -> {
053                    if (isProducing(method, checkClass) && !isSafeEntryBuilderProvider(method)) {
054                        final String errorMessage = join("\n", asList(
055                                format("Method %s calls unsafe Builder creator. This either means:",
056                                        getFullName(method)),
057                                "  * That the TCK method is safe and should belong to WHITE_LIST_TCK_SCHEMA_BUILDER_PROVIDER"));
058                        System.err.println("--" + getFullName(method));
059                        messages.add(errorMessage);
060                    }
061                });
062        return messages;
063    }
064
065    private static boolean isSafeEntryBuilderProvider(final Method method) {
066        return WHITE_LIST_TCK_SCHEMA_BUILDER_PROVIDER.contains(getFullName(method));
067    }
068
069    private static final Set<String> WHITE_LIST_TCK_SCHEMA_BUILDER_PROVIDER = new HashSet<>(asList(
070            "org.talend.sdk.component.runtime.record.SchemaImpl.toBuilder()",
071            "org.talend.sdk.component.runtime.record.Schemas.moveBefore(java.lang.String, java.lang.String)",
072            "org.talend.sdk.component.runtime.record.Schemas.withType(org.talend.sdk.component.api.record.Schema$Type)",
073            "org.talend.sdk.component.runtime.record.Schemas.withEntry(org.talend.sdk.component.api.record.Schema$Entry)",
074            "org.talend.sdk.component.runtime.record.Schemas.withElementSchema(org.talend.sdk.component.api.record.Schema)",
075            "org.talend.sdk.component.api.record.Schema$Entry.toBuilder()",
076            "org.talend.sdk.component.api.record.Schema$Entry$Builder.withNullable(boolean)",
077            "org.talend.sdk.component.api.record.Schema$Entry$Builder.withMetadata(boolean)",
078            "org.talend.sdk.component.api.record.Schema$Entry$Builder.withProps(java.util.Map)",
079            "org.talend.sdk.component.api.record.Schema$Entry$Builder.withName(java.lang.String)",
080            "org.talend.sdk.component.api.record.Schema$Entry$Builder.withRawName(java.lang.String)",
081            "org.talend.sdk.component.api.record.Schema$Entry$Builder.withType(org.talend.sdk.component.api.record.Schema$Type)",
082            "org.talend.sdk.component.api.record.Schema$Builder.remove(java.lang.String)",
083            "org.talend.sdk.component.api.record.Schema$Builder.remove(org.talend.sdk.component.api.record.Schema$Entry)",
084            "org.talend.sdk.component.api.record.Schema$Builder.swap(java.lang.String, java.lang.String)",
085            "org.talend.sdk.component.api.record.Schema$Builder.withEntryBefore(java.lang.String, org.talend.sdk.component.api.record.Schema$Entry)",
086            "org.talend.sdk.component.api.record.Schema$Builder.moveBefore(java.lang.String, java.lang.String)",
087            "org.talend.sdk.component.api.record.Schema$Builder.moveAfter(java.lang.String, java.lang.String)",
088            "org.talend.sdk.component.api.record.Schema$Builder.withEntryAfter(java.lang.String, org.talend.sdk.component.api.record.Schema$Entry)",
089            "org.talend.sdk.component.api.record.Schema$Builder.withType(org.talend.sdk.component.api.record.Schema$Type)",
090            "org.talend.sdk.component.api.record.Schema$Builder.withProp(java.lang.String, java.lang.String)",
091            "org.talend.sdk.component.api.record.Schema$Builder.withEntry(org.talend.sdk.component.api.record.Schema$Entry)",
092            "org.talend.sdk.component.api.record.Schema$Builder.withProps(java.util.Map)",
093            "org.talend.sdk.component.api.record.Schema$Builder.withElementSchema(org.talend.sdk.component.api.record.Schema)",
094            "org.talend.sdk.component.api.service.schema.Schema$Entry.toBuilder()",
095            "org.talend.sdk.component.runtime.record.SchemaImpl$EntryImpl.toBuilder()",
096            "org.talend.sdk.component.runtime.record.SchemaImpl$EntryImpl$BuilderImpl.withNullable(boolean)",
097            "org.talend.sdk.component.runtime.record.SchemaImpl$EntryImpl$BuilderImpl.withMetadata(boolean)",
098            "org.talend.sdk.component.runtime.record.SchemaImpl$EntryImpl$BuilderImpl.withProps(java.util.Map)",
099            "org.talend.sdk.component.runtime.record.SchemaImpl$EntryImpl$BuilderImpl.withName(java.lang.String)",
100            "org.talend.sdk.component.runtime.record.SchemaImpl$EntryImpl$BuilderImpl.withRawName(java.lang.String)",
101            "org.talend.sdk.component.runtime.record.SchemaImpl$EntryImpl$BuilderImpl.withComment(java.lang.String)",
102            "org.talend.sdk.component.runtime.record.SchemaImpl$EntryImpl$BuilderImpl.withDefaultValue(java.lang.Object)",
103            "org.talend.sdk.component.runtime.record.SchemaImpl$EntryImpl$BuilderImpl.withProp(java.lang.String, java.lang.String)",
104            "org.talend.sdk.component.runtime.record.SchemaImpl$EntryImpl$BuilderImpl.withType(org.talend.sdk.component.api.record.Schema$Type)",
105            "org.talend.sdk.component.runtime.record.SchemaImpl$EntryImpl$BuilderImpl.withElementSchema(org.talend.sdk.component.api.record.Schema)"));
106
107}