Skip to content

Commit

Permalink
Merge pull request #4 from flipkart-incubator/fix-data-adapter
Browse files Browse the repository at this point in the history
Support for subclassed data adapters
  • Loading branch information
bageshwar authored Apr 11, 2022
2 parents 402529c + cbea1a1 commit 45896a7
Show file tree
Hide file tree
Showing 5 changed files with 148 additions and 29 deletions.
2 changes: 1 addition & 1 deletion pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@
</build>

<properties>
<revision>0.0.1</revision>
<revision>0.0.2</revision>
<guava.version>19.0</guava.version>
<guice.version>4.2.3</guice.version>
</properties>
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Class<? extends DataAdapterBizlogic<?>>> classHierarchy) {
super(String.format(MESSAGE_FORMAT,
classHierarchy.stream().map(Class::getSimpleName)
.collect(Collectors.joining(" -> "))
)
);
}
}
62 changes: 35 additions & 27 deletions tef-impl/src/main/java/flipkart/tef/execution/FlowBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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<Class<? extends DataAdapterBizlogic<?>>> 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";
Expand Down
71 changes: 71 additions & 0 deletions tef-impl/src/test/java/flipkart/tef/execution/FlowBuilderTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -34,6 +36,7 @@
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;

@SuppressWarnings("unchecked")
public class FlowBuilderTest {

/**
Expand Down Expand Up @@ -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 {

Expand Down Expand Up @@ -458,4 +495,38 @@ public Map<String, String> adapt(TefContext tefContext) {
}
}

class OtherContext {

}

abstract class SuperDataAdapter<T> extends DataAdapterBizlogic<T> {

@Override
public T adapt(TefContext tefContext) {
return adapt(tefContext.getAdditionalContext("other", OtherContext.class));
}

public abstract T adapt(OtherContext otherContext);

}

abstract class SubDataAdapter extends SuperDataAdapter<SimpleData> {

}

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;
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -194,7 +194,7 @@ public Object getTarget() {
}
}

class DataAdapterBizlogic1 extends DataAdapterBizlogic {
class DataAdapterBizlogic1 extends DataAdapterBizlogic<Object> {

@Override
public Object adapt(TefContext tefContext) {
Expand Down

0 comments on commit 45896a7

Please sign in to comment.