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; 021 022import java.lang.reflect.Method; 023import java.lang.reflect.Parameter; 024import java.util.ArrayList; 025import java.util.Arrays; 026import java.util.HashSet; 027import java.util.List; 028import java.util.Set; 029import java.util.stream.Collectors; 030import java.util.stream.Stream; 031 032import org.apache.xbean.finder.AnnotationFinder; 033import org.talend.sdk.component.api.record.Record; 034 035public class RecordValidator 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())); 043 }); 044 return messages.stream(); 045 } 046 047 private List<String> validateMethods(final Method[] methods) { 048 final List<String> messages = new ArrayList<>(); 049 050 Arrays.stream(methods) 051 .forEach(method -> { 052 if (isProducing(method, Record.Builder.class) && !isSafeEntryBuilderProvider(method)) { 053 final String errorMessage = join("\n", asList( 054 format("Method %s calls unsafe Builder creator. This either means:", 055 getFullName(method)), 056 " * That the TCK method is safe and should belong to WHITE_LIST_TCK_RECORD_BUILDER_PROVIDER")); 057 messages.add(errorMessage); 058 } 059 }); 060 return messages; 061 } 062 063 private static boolean isSafeEntryBuilderProvider(final Method method) { 064 return WHITE_LIST_TCK_RECORD_BUILDER_PROVIDER.contains(getFullName(method)); 065 } 066 067 protected static boolean isProducing(final Method method, final Class<?> clazz) { 068 return areTheSameClass(method.getReturnType(), clazz); 069 } 070 071 private static boolean areTheSameClass(final Class javaClass, final Class<?> clazz) { 072 return javaClass.getPackage() != null && javaClass.getPackage().getName().equals(clazz.getPackage().getName()) 073 && javaClass.isAssignableFrom(clazz); 074 } 075 076 protected static String getFullName(final Method method) { 077 return formatMethod(method.getDeclaringClass().getName(), method.getName(), namesOf(method.getParameters())); 078 } 079 080 private static String formatMethod(final String ownerName, final String methodName, final String parameters) { 081 return ownerName + "." + methodName + "(" + parameters + ")"; 082 } 083 084 private static String namesOf(final Parameter[] parameters) { 085 return Arrays.stream(parameters).map(type -> type.getType().getName()).collect(Collectors.joining(", ")); 086 } 087 088 /** 089 * TCK Java methods that create a {@link Record.Builder} from another builder/record. 090 * These are safe, they preserve the props from the other builder/record. 091 */ 092 private static final Set<String> WHITE_LIST_TCK_RECORD_BUILDER_PROVIDER = new HashSet<>(asList( 093 "org.talend.sdk.component.runtime.record.RecordImpl.withNewSchema(org.talend.sdk.component.api.record.Schema)", 094 095 "org.talend.sdk.component.runtime.record.RecordImpl.BuilderImpl.before(java.lang.String)", 096 "org.talend.sdk.component.runtime.record.RecordImpl.BuilderImpl.after(java.lang.String)", 097 "org.talend.sdk.component.runtime.record.RecordImpl.BuilderImpl.withRecord(java.lang.String, org.talend.sdk.component.api.record.Record)", 098 "org.talend.sdk.component.runtime.record.RecordImpl.BuilderImpl.removeEntry(org.talend.sdk.component.api.record.Schema$Entry)", 099 "org.talend.sdk.component.runtime.record.RecordImpl.BuilderImpl.updateEntryByName(java.lang.String, org.talend.sdk.component.api.record.Schema$Entry)", 100 "org.talend.sdk.component.api.record.Record.withNewSchema(org.talend.sdk.component.api.record.Schema)", 101 "org.talend.sdk.component.api.record.Record$Builder.before(java.lang.String)", 102 "org.talend.sdk.component.api.record.Record$Builder.after(java.lang.String)", 103 "org.talend.sdk.component.api.record.Record$Builder.removeEntry(org.talend.sdk.component.api.record.Schema$Entry)", 104 "org.talend.sdk.component.api.record.Record$Builder.withInstant(org.talend.sdk.component.api.record.Schema$Entry, java.time.Instant)", 105 "org.talend.sdk.component.api.record.Record$Builder.updateEntryByName(java.lang.String, org.talend.sdk.component.api.record.Schema$Entry)", 106 "org.talend.sdk.component.api.record.Record$Builder.updateEntryByName(java.lang.String, org.talend.sdk.component.api.record.Schema$Entry, java.util.function.Function)", 107 "org.talend.sdk.component.api.record.Record$Builder.with(org.talend.sdk.component.api.record.Schema$Entry, java.lang.Object)", 108 "org.talend.sdk.component.api.record.Record$Builder.withString(java.lang.String, java.lang.String)", 109 "org.talend.sdk.component.api.record.Record$Builder.withString(org.talend.sdk.component.api.record.Schema$Entry, java.lang.String)", 110 "org.talend.sdk.component.api.record.Record$Builder.withBytes(java.lang.String, [B)", 111 "org.talend.sdk.component.api.record.Record$Builder.withBytes(org.talend.sdk.component.api.record.Schema$Entry, [B)", 112 "org.talend.sdk.component.api.record.Record$Builder.withDateTime(java.lang.String, java.util.Date)", 113 "org.talend.sdk.component.api.record.Record$Builder.withDateTime(org.talend.sdk.component.api.record.Schema$Entry, java.util.Date)", 114 "org.talend.sdk.component.api.record.Record$Builder.withDateTime(java.lang.String, java.time.ZonedDateTime)", 115 "org.talend.sdk.component.api.record.Record$Builder.withDateTime(org.talend.sdk.component.api.record.Schema$Entry, java.time.ZonedDateTime)", 116 "org.talend.sdk.component.api.record.Record$Builder.withDecimal(java.lang.String, java.math.BigDecimal)", 117 "org.talend.sdk.component.api.record.Record$Builder.withDecimal(org.talend.sdk.component.api.record.Schema$Entry, java.math.BigDecimal)", 118 "org.talend.sdk.component.api.record.Record$Builder.withTimestamp(java.lang.String, long)", 119 "org.talend.sdk.component.api.record.Record$Builder.withTimestamp(org.talend.sdk.component.api.record.Schema$Entry, long)", 120 "org.talend.sdk.component.api.record.Record$Builder.withInstant(java.lang.String, java.time.Instant)", 121 "org.talend.sdk.component.api.record.Record$Builder.withInstant(org.talend.sdk.component.api.record.Schema$Entry,java.time.Instant)", 122 "org.talend.sdk.component.api.record.Record$Builder.withInt(java.lang.String, int)", 123 "org.talend.sdk.component.api.record.Record$Builder.withInt(org.talend.sdk.component.api.record.Schema$Entry, int)", 124 "org.talend.sdk.component.api.record.Record$Builder.withLong(java.lang.String, long)", 125 "org.talend.sdk.component.api.record.Record$Builder.withLong(org.talend.sdk.component.api.record.Schema$Entry, long)", 126 "org.talend.sdk.component.api.record.Record$Builder.withFloat(java.lang.String, float)", 127 "org.talend.sdk.component.api.record.Record$Builder.withFloat(org.talend.sdk.component.api.record.Schema$Entry, float)", 128 "org.talend.sdk.component.api.record.Record$Builder.withDouble(java.lang.String, double)", 129 "org.talend.sdk.component.api.record.Record$Builder.withDouble(org.talend.sdk.component.api.record.Schema$Entry, double)", 130 "org.talend.sdk.component.api.record.Record$Builder.withBoolean(java.lang.String, boolean)", 131 "org.talend.sdk.component.api.record.Record$Builder.withBoolean(org.talend.sdk.component.api.record.Schema$Entry, boolean)", 132 "org.talend.sdk.component.api.record.Record$Builder.withRecord(java.lang.String, org.talend.sdk.component.api.record.Record)", 133 "org.talend.sdk.component.api.record.Record$Builder.withRecord(org.talend.sdk.component.api.record.Schema$Entry, org.talend.sdk.component.api.record.Record)", 134 "org.talend.sdk.component.api.record.Record$Builder.withArray(org.talend.sdk.component.api.record.Schema$Entry, java.util.Collection)", 135 136 "org.talend.sdk.component.api.record.RecordImpl.withNewSchema(org.talend.sdk.component.api.record.Schema)", 137 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.with(org.talend.sdk.component.api.record.Schema$Entry, java.lang.Object)", 138 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withString(java.lang.String, java.lang.String)", 139 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withString(org.talend.sdk.component.api.record.Schema$Entry, java.lang.String)", 140 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withBytes(java.lang.String, [B)", 141 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withBytes(org.talend.sdk.component.api.record.Schema$Entry, [B)", 142 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withDateTime(java.lang.String, java.util.Date)", 143 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withDateTime(org.talend.sdk.component.api.record.Schema$Entry, java.util.Date)", 144 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withDateTime(java.lang.String, java.time.ZonedDateTime)", 145 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withDateTime(org.talend.sdk.component.api.record.Schema$Entry, java.time.ZonedDateTime)", 146 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withDecimal(java.lang.String, java.math.BigDecimal)", 147 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withDecimal(org.talend.sdk.component.api.record.Schema$Entry, java.math.BigDecimal)", 148 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withTimestamp(java.lang.String, long)", 149 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withTimestamp(org.talend.sdk.component.api.record.Schema$Entry, long)", 150 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withInstant(java.lang.String, java.time.Instant)", 151 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withInstant(org.talend.sdk.component.api.record.Schema$Entry,java.time.Instant)", 152 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withInt(java.lang.String, int)", 153 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withInt(org.talend.sdk.component.api.record.Schema$Entry, int)", 154 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withLong(java.lang.String, long)", 155 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withLong(org.talend.sdk.component.api.record.Schema$Entry, long)", 156 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withFloat(java.lang.String, float)", 157 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withFloat(org.talend.sdk.component.api.record.Schema$Entry, float)", 158 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withDouble(java.lang.String, double)", 159 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withDouble(org.talend.sdk.component.api.record.Schema$Entry, double)", 160 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withBoolean(java.lang.String, boolean)", 161 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withBoolean(org.talend.sdk.component.api.record.Schema$Entry, boolean)", 162 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withRecord(java.lang.String, org.talend.sdk.component.api.record.Record)", 163 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withRecord(org.talend.sdk.component.api.record.Schema$Entry, org.talend.sdk.component.api.record.Record)", 164 "org.talend.sdk.component.api.record.RecordImpl.BuilderImpl.withArray(org.talend.sdk.component.api.record.Schema$Entry, java.util.Collection)")); 165 166}