/* * Copyright © 2016 Cask Data, Inc. * * 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 co.cask.cdap.etl.common; import co.cask.cdap.etl.common.guice.TypeResolver; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; import com.google.common.reflect.TypeToken; import java.lang.reflect.GenericArrayType; import java.lang.reflect.Type; import java.lang.reflect.TypeVariable; import java.util.List; /** * Validates types passed between pipeline stages. * * TODO : CDAP-4387 Validate Stages has been removed due to DAG implementation, have to be refactored */ public class PipelineTypeValidator { /** * Takes in an unresolved type list and resolves the types and verifies if the types are assignable. * Ex: An unresolved type could be : String, T, List<T>, List<String> * The above will resolve to : String, String, List<String>, List<String> * And the assignability will be checked : String --> String && List<String> --> List<String> * which is true in the case above. */ @VisibleForTesting static void validateTypes(List<Type> unresTypeList) { Preconditions.checkArgument(unresTypeList.size() % 2 == 0, "ETL Stages validation expects even number of types"); List<Type> resTypeList = Lists.newArrayListWithCapacity(unresTypeList.size()); // Add the source output to resolved type list as the first resolved type. resTypeList.add(unresTypeList.get(0)); try { // Resolve the second type using just the first resolved type. Type nType = (new TypeResolver()).where(unresTypeList.get(1), resTypeList.get(0)).resolveType( unresTypeList.get(1)); resTypeList.add(nType); } catch (IllegalArgumentException e) { // If unable to resolve type, add the second type as is, to the resolved list. resTypeList.add(unresTypeList.get(1)); } for (int i = 2; i < unresTypeList.size(); i++) { // ActualType is previous resolved type; FormalType is previous unresolved type; // ToResolveType is current unresolved type; // Ex: Actual = String; Formal = T; ToResolve = List<T>; ==> newType = List<String> Type actualType = resTypeList.get(i - 1); Type formalType = unresTypeList.get(i - 1); Type toResolveType = unresTypeList.get(i); try { Type newType; // If the toResolveType is a TypeVariable or a Generic Array, then try to resolve // using just the previous resolved type. // Ex: Actual = List<String> ; Formal = List<T> ; ToResolve = T ==> newType = String which is not correct; // newType should be List<String>. Hence resolve only from the previous resolved type (Actual) if ((toResolveType instanceof TypeVariable) || (toResolveType instanceof GenericArrayType)) { newType = (new TypeResolver()).where(toResolveType, actualType).resolveType(toResolveType); } else { newType = (new TypeResolver()).where(formalType, actualType).resolveType(toResolveType); } resTypeList.add(newType); } catch (IllegalArgumentException e) { // If resolution failed, add the type as is to the resolved list. resTypeList.add(toResolveType); } } // Check isAssignable on the resolved list for every paired elements. 0 --> 1 | 2 --> 3 | 4 --> 5 where | is a // transform (which takes in type on its left and emits the type on its right). for (int i = 0; i < resTypeList.size(); i += 2) { Type firstType = resTypeList.get(i); Type secondType = resTypeList.get(i + 1); // Check if secondType can accept firstType Preconditions.checkArgument(TypeToken.of(secondType).isAssignableFrom(firstType), "Types between stages didn't match. Mismatch between {} -> {}", firstType, secondType); } } }