From aedf6bcfddfc7758796d8ac3556b8bef5be698ce Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Tue, 29 Mar 2022 08:15:22 +0530 Subject: [PATCH 1/4] Support for subclassed data adapters --- pom.xml | 2 +- ...esolveDataFromAdapterRuntimeException.java | 40 +++++++++++ .../flipkart/tef/execution/FlowBuilder.java | 62 +++++++++------- .../tef/execution/FlowBuilderTest.java | 71 +++++++++++++++++++ .../FluentCapabilityBuilderTest.java | 2 +- 5 files changed, 148 insertions(+), 29 deletions(-) create mode 100644 tef-impl/src/main/java/flipkart/tef/exceptions/UnableToResolveDataFromAdapterRuntimeException.java diff --git a/pom.xml b/pom.xml index 48220dd..5185095 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ - 0.0.1 + 0.0.2-SNAPSHOT 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..884e463 --- /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; + +/** + * + * Date: 02/07/20 + * Time: 2:58 PM + */ +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) { From f0da7b49e4763224b61083170de1c4e64445a400 Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Tue, 29 Mar 2022 08:34:44 +0530 Subject: [PATCH 2/4] Update time stamp --- .../UnableToResolveDataFromAdapterRuntimeException.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tef-impl/src/main/java/flipkart/tef/exceptions/UnableToResolveDataFromAdapterRuntimeException.java b/tef-impl/src/main/java/flipkart/tef/exceptions/UnableToResolveDataFromAdapterRuntimeException.java index 884e463..334161e 100644 --- a/tef-impl/src/main/java/flipkart/tef/exceptions/UnableToResolveDataFromAdapterRuntimeException.java +++ b/tef-impl/src/main/java/flipkart/tef/exceptions/UnableToResolveDataFromAdapterRuntimeException.java @@ -23,8 +23,8 @@ /** * - * Date: 02/07/20 - * Time: 2:58 PM + * Date: 29/03/22 + * Time: 8:28 AM */ public class UnableToResolveDataFromAdapterRuntimeException extends RuntimeException { From 687710fd9b5e9647a9ce8bb1c40f948f466b3b61 Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Mon, 11 Apr 2022 14:21:04 +0530 Subject: [PATCH 3/4] Release version --- pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pom.xml b/pom.xml index 5185095..b9f4f05 100644 --- a/pom.xml +++ b/pom.xml @@ -66,7 +66,7 @@ - 0.0.2-SNAPSHOT + 0.0.2 19.0 4.2.3 From cbea1a19ba85289e16dd79dea2e9470943b9f43f Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Mon, 11 Apr 2022 14:22:15 +0530 Subject: [PATCH 4/4] Comment on exception --- .../UnableToResolveDataFromAdapterRuntimeException.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tef-impl/src/main/java/flipkart/tef/exceptions/UnableToResolveDataFromAdapterRuntimeException.java b/tef-impl/src/main/java/flipkart/tef/exceptions/UnableToResolveDataFromAdapterRuntimeException.java index 334161e..916d468 100644 --- a/tef-impl/src/main/java/flipkart/tef/exceptions/UnableToResolveDataFromAdapterRuntimeException.java +++ b/tef-impl/src/main/java/flipkart/tef/exceptions/UnableToResolveDataFromAdapterRuntimeException.java @@ -22,7 +22,7 @@ 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 */