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.219.04.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 extends IBizlogic> 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 extends IBizlogic> 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 extends DataAdapterBizlogic>> 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 extends DataAdapterBizlogic>>)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