diff --git a/pom.xml b/pom.xml index 48220dd..b9f4f05 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ - 0.0.1 + 0.0.2 19.0 4.2.3 diff --git a/tef-impl/src/main/java/flipkart/tef/exceptions/UnableToResolveDataFromAdapterRuntimeException.java b/tef-impl/src/main/java/flipkart/tef/exceptions/UnableToResolveDataFromAdapterRuntimeException.java new file mode 100644 index 0000000..916d468 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/exceptions/UnableToResolveDataFromAdapterRuntimeException.java @@ -0,0 +1,40 @@ +/* + * Copyright [2021] [The Original Author] + * + * 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 flipkart.tef.exceptions; + +import flipkart.tef.bizlogics.DataAdapterBizlogic; + +import java.util.List; +import java.util.stream.Collectors; + +/** + * This exception is thrown when Tef cannot figure the return type of data adapter. + * Date: 29/03/22 + * Time: 8:28 AM + */ +public class UnableToResolveDataFromAdapterRuntimeException extends RuntimeException { + + private static final String MESSAGE_FORMAT = "Unable to resolve data from Data Adapter Class Hierarchy %s"; + + public UnableToResolveDataFromAdapterRuntimeException(List>> classHierarchy) { + super(String.format(MESSAGE_FORMAT, + classHierarchy.stream().map(Class::getSimpleName) + .collect(Collectors.joining(" -> ")) + ) + ); + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/execution/FlowBuilder.java b/tef-impl/src/main/java/flipkart/tef/execution/FlowBuilder.java index 7811460..105876c 100644 --- a/tef-impl/src/main/java/flipkart/tef/execution/FlowBuilder.java +++ b/tef-impl/src/main/java/flipkart/tef/execution/FlowBuilder.java @@ -28,13 +28,12 @@ import flipkart.tef.bizlogics.DataAdapterKey; import flipkart.tef.bizlogics.IBizlogic; import flipkart.tef.bizlogics.IDataBizlogic; -import flipkart.tef.bizlogics.TefContext; import flipkart.tef.capability.AdapterConflictRuntimeException; +import flipkart.tef.exceptions.UnableToResolveDataFromAdapterRuntimeException; import flipkart.tef.flow.SimpleFlow; import sun.reflect.generics.reflectiveObjects.ParameterizedTypeImpl; import java.lang.reflect.Field; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -226,30 +225,15 @@ private void populateDataAdapterMap(Class bizlogic) { Class returnType = null; - try { - /** - * Using Sun implementation to get generic parameter type. Other considered options were - * 1. Have an interface return the class type - This would require us to instantiate the class - * 2. Have a Type Parameter via Guava - This would require us to instantiate the class - * 3. Get the return type of a known interface method ( `adapt` ) . That is present as fallback for - * cases where DataAdapterBizlogic is not parameterized. - * 4. Using sun's implementation to get the type param. - */ - if (dataAdapterBizLogic.getGenericSuperclass() instanceof ParameterizedTypeImpl) { - ParameterizedTypeImpl genericSuperClass = (ParameterizedTypeImpl) dataAdapterBizLogic.getGenericSuperclass(); - if (genericSuperClass.getActualTypeArguments()[0] instanceof Class) { - returnType = (Class) genericSuperClass.getActualTypeArguments()[0]; - } else if (genericSuperClass.getActualTypeArguments()[0] instanceof ParameterizedTypeImpl) { - // The type itself is parameterized - returnType = ((ParameterizedTypeImpl) genericSuperClass.getActualTypeArguments()[0]).getRawType(); - } - } - } finally { - if (returnType == null) { - Method adaptMethod = bizlogic.getMethod("adapt", TefContext.class); - returnType = adaptMethod.getReturnType(); - } - } + /* + * Using Sun implementation to get generic parameter type. Other considered options were + * 1. Have an interface return the class type - This would require us to instantiate the class + * 2. Have a Type Parameter via Guava - This would require us to instantiate the class + * 3. Get the return type of known interface method ( `adapt` ) - this will not work for cases when + * adapt method is overridden to provide custom context as inputs + * 4. Using sun's implementation to get the type param. - Using this approach + */ + returnType = getReturnTypeFromBizlogicUsingSunApi(dataAdapterBizLogic, new ArrayList<>()); DataAdapterKey key = new DataAdapterKey<>( DataAdapterBizlogic.getEmittedDataName(dataAdapterBizLogic), returnType); @@ -260,11 +244,35 @@ private void populateDataAdapterMap(Class bizlogic) { throw new AdapterConflictRuntimeException(returnType, bizlogic, dataAdapterMap.get(key)); } } - } catch (NoSuchMethodException e) { + } catch (AdapterConflictRuntimeException|UnableToResolveDataFromAdapterRuntimeException e) { + throw e; + } catch (Exception e) { throw new RuntimeException(e); } } + @SuppressWarnings("unchecked") + private Class getReturnTypeFromBizlogicUsingSunApi(Class> dataAdapterBizLogic, List>> classHierarchy) { + classHierarchy.add(dataAdapterBizLogic); + if (dataAdapterBizLogic.getGenericSuperclass() instanceof ParameterizedTypeImpl) { + ParameterizedTypeImpl genericSuperClass = (ParameterizedTypeImpl) dataAdapterBizLogic.getGenericSuperclass(); + if (genericSuperClass.getActualTypeArguments()[0] instanceof Class) { + return (Class) genericSuperClass.getActualTypeArguments()[0]; + } else if (genericSuperClass.getActualTypeArguments()[0] instanceof ParameterizedTypeImpl) { + // The type itself is parameterized + return ((ParameterizedTypeImpl) genericSuperClass.getActualTypeArguments()[0]).getRawType(); + } + } else { + // This could be a case of a data adapter being a subclass of another + // data adapter where type parameter is specified on the superclass + if(DataAdapterBizlogic.class.isAssignableFrom(dataAdapterBizLogic.getSuperclass())) { + return getReturnTypeFromBizlogicUsingSunApi((Class>)dataAdapterBizLogic.getSuperclass(), classHierarchy); + } + } + + throw new UnableToResolveDataFromAdapterRuntimeException(classHierarchy); + } + static class Messages { public static final String CYCLIC_GRAPHS_ARE_NOT_SUPPORTED = "Cyclic Graphs are not supported"; public static final String A_BIZLOGIC_CANNOT_DEPEND_ON_SELF = "A bizlogic cannot depend on Self"; diff --git a/tef-impl/src/test/java/flipkart/tef/execution/FlowBuilderTest.java b/tef-impl/src/test/java/flipkart/tef/execution/FlowBuilderTest.java index 9acc0df..8f4e179 100644 --- a/tef-impl/src/test/java/flipkart/tef/execution/FlowBuilderTest.java +++ b/tef-impl/src/test/java/flipkart/tef/execution/FlowBuilderTest.java @@ -24,6 +24,8 @@ import flipkart.tef.bizlogics.TefContext; import flipkart.tef.capability.AdapterConflictRuntimeException; import flipkart.tef.capability.model.MapBaseData; +import flipkart.tef.exception.TefExecutionException; +import flipkart.tef.exceptions.UnableToResolveDataFromAdapterRuntimeException; import flipkart.tef.flow.SimpleFlow; import org.junit.Test; @@ -34,6 +36,7 @@ import static org.junit.Assert.assertTrue; import static org.junit.Assert.fail; +@SuppressWarnings("unchecked") public class FlowBuilderTest { /** @@ -331,6 +334,40 @@ public void testComplexDataAdapter() { assertTrue(simpleFlow.getBizlogics().contains(DataAdapter3.class)); } + @SuppressWarnings("OptionalGetWithoutIsPresent") + @Test + public void testDataAdapterExtension() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SubDataAdapter.class); + SimpleFlow simpleFlow = flowBuilder.build(); + assertEquals(1, simpleFlow.getBizlogics().size()); + assertTrue(simpleFlow.getBizlogics().contains(SubDataAdapter.class)); + assertEquals(SimpleData.class, simpleFlow.getDataAdapterMap().keySet().stream().findFirst().get().getResultClass()); + } + + @SuppressWarnings("OptionalGetWithoutIsPresent") + @Test + public void testDataAdapterExtension2() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SubSubDataAdapter.class); + SimpleFlow simpleFlow = flowBuilder.build(); + assertEquals(1, simpleFlow.getBizlogics().size()); + assertTrue(simpleFlow.getBizlogics().contains(SubSubDataAdapter.class)); + assertEquals(SimpleData.class, simpleFlow.getDataAdapterMap().keySet().stream().findFirst().get().getResultClass()); + } + + @Test + public void testDataAdapterExtensionWithoutTypeParam() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(DataAdapterWithoutTypeParam.class); + try { + flowBuilder.build(); + fail("UnableToResolveDataFromAdapterRuntimeException was expected"); + } catch (UnableToResolveDataFromAdapterRuntimeException e) { + // No-op + } + } + class SimpleData extends MapBaseData { @@ -458,4 +495,38 @@ public Map adapt(TefContext tefContext) { } } + class OtherContext { + + } + + abstract class SuperDataAdapter extends DataAdapterBizlogic { + + @Override + public T adapt(TefContext tefContext) { + return adapt(tefContext.getAdditionalContext("other", OtherContext.class)); + } + + public abstract T adapt(OtherContext otherContext); + + } + + abstract class SubDataAdapter extends SuperDataAdapter { + + } + + class SubSubDataAdapter extends SubDataAdapter { + @Override + public SimpleData adapt(OtherContext otherContext) { + return new SimpleData(); + } + } + + class DataAdapterWithoutTypeParam extends DataAdapterBizlogic { + + @Override + public Object adapt(TefContext tefContext) throws TefExecutionException { + return null; + } + } + } \ No newline at end of file diff --git a/tef-impl/src/test/java/flipkart/tef/execution/FluentCapabilityBuilderTest.java b/tef-impl/src/test/java/flipkart/tef/execution/FluentCapabilityBuilderTest.java index 624b0de..176db97 100644 --- a/tef-impl/src/test/java/flipkart/tef/execution/FluentCapabilityBuilderTest.java +++ b/tef-impl/src/test/java/flipkart/tef/execution/FluentCapabilityBuilderTest.java @@ -194,7 +194,7 @@ public Object getTarget() { } } - class DataAdapterBizlogic1 extends DataAdapterBizlogic { + class DataAdapterBizlogic1 extends DataAdapterBizlogic { @Override public Object adapt(TefContext tefContext) {