From d59b7b28c9197fffb42e745b9741484e26a77cc2 Mon Sep 17 00:00:00 2001 From: bageshwar Date: Thu, 16 Dec 2021 12:42:39 +0530 Subject: [PATCH 1/3] Version 0.0.1 --- .gitignore | 3 + README.md | 9 +- pom.xml | 61 +++ tef-core/pom.xml | 30 ++ .../flipkart/tef/FlowExecutionListener.java | 43 ++ .../flipkart/tef/annotations/DependsOn.java | 41 ++ .../flipkart/tef/annotations/EmitData.java | 38 ++ .../flipkart/tef/annotations/InjectData.java | 70 +++ .../tef/bizlogics/DataAdapterKey.java | 63 +++ .../tef/bizlogics/DataAdapterResult.java | 52 ++ .../flipkart/tef/bizlogics/IBizlogic.java | 51 ++ .../flipkart/tef/bizlogics/IDataBizlogic.java | 55 +++ .../flipkart/tef/bizlogics/TefContext.java | 56 +++ .../flipkart/tef/exception/ErrorCode.java | 23 + .../tef/exception/TefExecutionException.java | 42 ++ .../java/flipkart/tef/flow/SimpleFlow.java | 60 +++ tef-impl/pom.xml | 46 ++ .../java/flipkart/tef/TefGuiceModule.java | 34 ++ .../bizlogics/BasicEnrichmentBizlogic.java | 57 +++ .../bizlogics/BasicValidationBizlogic.java | 48 ++ .../tef/bizlogics/DataAdapterBizlogic.java | 136 ++++++ .../AdapterConflictRuntimeException.java | 33 ++ .../tef/capability/BizlogicDependency.java | 43 ++ .../tef/capability/CapabilityDefinition.java | 78 +++ .../capability/EmptyCapabilityDefinition.java | 69 +++ .../model/EnrichmentResultData.java | 27 + .../tef/capability/model/MapBaseData.java | 164 +++++++ .../model/ValidationResultData.java | 26 + .../flipkart/tef/execution/DataContext.java | 87 ++++ .../execution/DataDependencyException.java | 28 ++ .../flipkart/tef/execution/DataInjector.java | 40 ++ .../tef/execution/DefaultDataInjector.java | 56 +++ .../flipkart/tef/execution/FlowBuilder.java | 301 ++++++++++++ .../flipkart/tef/execution/FlowExecutor.java | 189 +++++++ .../execution/FluentCapabilityBuilder.java | 167 +++++++ .../execution/InjectableValueProvider.java | 36 ++ .../tef/execution/MutationListener.java | 31 ++ .../java/flipkart/tef/TestGuiceModule.java | 33 ++ .../java/flipkart/tef/TestTefContext.java | 34 ++ .../tef/bizlogics/DataAdapterKeyTest.java | 107 ++++ .../tef/execution/ExecutionStage.java | 26 + .../flipkart/tef/execution/ExecutionStep.java | 42 ++ .../tef/execution/FlowBuilderTest.java | 461 ++++++++++++++++++ .../execution/FlowExecutorMutationTest.java | 233 +++++++++ .../tef/execution/FlowExecutorTest.java | 395 +++++++++++++++ .../FluentCapabilityBuilderTest.java | 204 ++++++++ .../execution/MyFlowExecutionListener.java | 47 ++ 47 files changed, 3974 insertions(+), 1 deletion(-) create mode 100644 .gitignore create mode 100644 pom.xml create mode 100644 tef-core/pom.xml create mode 100644 tef-core/src/main/java/flipkart/tef/FlowExecutionListener.java create mode 100644 tef-core/src/main/java/flipkart/tef/annotations/DependsOn.java create mode 100644 tef-core/src/main/java/flipkart/tef/annotations/EmitData.java create mode 100644 tef-core/src/main/java/flipkart/tef/annotations/InjectData.java create mode 100644 tef-core/src/main/java/flipkart/tef/bizlogics/DataAdapterKey.java create mode 100644 tef-core/src/main/java/flipkart/tef/bizlogics/DataAdapterResult.java create mode 100644 tef-core/src/main/java/flipkart/tef/bizlogics/IBizlogic.java create mode 100644 tef-core/src/main/java/flipkart/tef/bizlogics/IDataBizlogic.java create mode 100644 tef-core/src/main/java/flipkart/tef/bizlogics/TefContext.java create mode 100644 tef-core/src/main/java/flipkart/tef/exception/ErrorCode.java create mode 100644 tef-core/src/main/java/flipkart/tef/exception/TefExecutionException.java create mode 100644 tef-core/src/main/java/flipkart/tef/flow/SimpleFlow.java create mode 100644 tef-impl/pom.xml create mode 100644 tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java create mode 100644 tef-impl/src/main/java/flipkart/tef/bizlogics/BasicEnrichmentBizlogic.java create mode 100644 tef-impl/src/main/java/flipkart/tef/bizlogics/BasicValidationBizlogic.java create mode 100644 tef-impl/src/main/java/flipkart/tef/bizlogics/DataAdapterBizlogic.java create mode 100644 tef-impl/src/main/java/flipkart/tef/capability/AdapterConflictRuntimeException.java create mode 100644 tef-impl/src/main/java/flipkart/tef/capability/BizlogicDependency.java create mode 100644 tef-impl/src/main/java/flipkart/tef/capability/CapabilityDefinition.java create mode 100644 tef-impl/src/main/java/flipkart/tef/capability/EmptyCapabilityDefinition.java create mode 100644 tef-impl/src/main/java/flipkart/tef/capability/model/EnrichmentResultData.java create mode 100644 tef-impl/src/main/java/flipkart/tef/capability/model/MapBaseData.java create mode 100644 tef-impl/src/main/java/flipkart/tef/capability/model/ValidationResultData.java create mode 100644 tef-impl/src/main/java/flipkart/tef/execution/DataContext.java create mode 100644 tef-impl/src/main/java/flipkart/tef/execution/DataDependencyException.java create mode 100644 tef-impl/src/main/java/flipkart/tef/execution/DataInjector.java create mode 100644 tef-impl/src/main/java/flipkart/tef/execution/DefaultDataInjector.java create mode 100644 tef-impl/src/main/java/flipkart/tef/execution/FlowBuilder.java create mode 100644 tef-impl/src/main/java/flipkart/tef/execution/FlowExecutor.java create mode 100644 tef-impl/src/main/java/flipkart/tef/execution/FluentCapabilityBuilder.java create mode 100644 tef-impl/src/main/java/flipkart/tef/execution/InjectableValueProvider.java create mode 100644 tef-impl/src/main/java/flipkart/tef/execution/MutationListener.java create mode 100644 tef-impl/src/test/java/flipkart/tef/TestGuiceModule.java create mode 100644 tef-impl/src/test/java/flipkart/tef/TestTefContext.java create mode 100644 tef-impl/src/test/java/flipkart/tef/bizlogics/DataAdapterKeyTest.java create mode 100644 tef-impl/src/test/java/flipkart/tef/execution/ExecutionStage.java create mode 100644 tef-impl/src/test/java/flipkart/tef/execution/ExecutionStep.java create mode 100644 tef-impl/src/test/java/flipkart/tef/execution/FlowBuilderTest.java create mode 100644 tef-impl/src/test/java/flipkart/tef/execution/FlowExecutorMutationTest.java create mode 100644 tef-impl/src/test/java/flipkart/tef/execution/FlowExecutorTest.java create mode 100644 tef-impl/src/test/java/flipkart/tef/execution/FluentCapabilityBuilderTest.java create mode 100644 tef-impl/src/test/java/flipkart/tef/execution/MyFlowExecutionListener.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..9cdb1db --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +.idea +**/*.iml +**/target diff --git a/README.md b/README.md index 33eee17..cd52645 100644 --- a/README.md +++ b/README.md @@ -1 +1,8 @@ -# tef \ No newline at end of file +# Task Execution Framework + +A low-footprint Workflow Execution Framework to build and execute configured DAGs. + +The highlights of this framework is its ability to configure within code, with the support for Data and Control Dependencies along with packaging logical blocks as Capabilities. + +## Lead Developer +@bageshwar \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..238998a --- /dev/null +++ b/pom.xml @@ -0,0 +1,61 @@ + + + + + 4.0.0 + + flipkart.tef + tef + pom + Task Execution Framework + ${revision} + + + tef-core + tef-impl + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.6.1 + + 1.8 + 1.8 + + + + + + + 0.0.1 + 19.0 + 4.2.3 + + + + + com.google.inject + guice + ${guice.version} + + + \ No newline at end of file diff --git a/tef-core/pom.xml b/tef-core/pom.xml new file mode 100644 index 0000000..8941dca --- /dev/null +++ b/tef-core/pom.xml @@ -0,0 +1,30 @@ + + + + + + flipkart.tef + tef + 0.0.1 + + 4.0.0 + + tef-core + + \ No newline at end of file diff --git a/tef-core/src/main/java/flipkart/tef/FlowExecutionListener.java b/tef-core/src/main/java/flipkart/tef/FlowExecutionListener.java new file mode 100644 index 0000000..27fa491 --- /dev/null +++ b/tef-core/src/main/java/flipkart/tef/FlowExecutionListener.java @@ -0,0 +1,43 @@ +/* + * 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; + +import flipkart.tef.bizlogics.IBizlogic; + +/** + * Listen to events during a Flow Execution. + * + * + * Date: 22/06/20 + * Time: 10:05 AM + */ +public interface FlowExecutionListener { + + /** + * Triggered before the execution of a bizlogic + * + * @param bizlogic + */ + void pre(IBizlogic bizlogic); + + /** + * Triggered after the execution of a bizlogic + * + * @param bizlogic + */ + void post(IBizlogic bizlogic); +} diff --git a/tef-core/src/main/java/flipkart/tef/annotations/DependsOn.java b/tef-core/src/main/java/flipkart/tef/annotations/DependsOn.java new file mode 100644 index 0000000..25e0f49 --- /dev/null +++ b/tef-core/src/main/java/flipkart/tef/annotations/DependsOn.java @@ -0,0 +1,41 @@ +/* + * 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.annotations; + +import flipkart.tef.bizlogics.IBizlogic; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Inherited; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is meant to define Control Dependencies between BizLogics. + * + * + * Date: 19/06/20 + * Time: 4:30 PM + */ +@Inherited +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface DependsOn { + Class[] value() default {}; +} diff --git a/tef-core/src/main/java/flipkart/tef/annotations/EmitData.java b/tef-core/src/main/java/flipkart/tef/annotations/EmitData.java new file mode 100644 index 0000000..13ed33b --- /dev/null +++ b/tef-core/src/main/java/flipkart/tef/annotations/EmitData.java @@ -0,0 +1,38 @@ +/* + * 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.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This is an optional annotation to be put on DataAdapters. If a data-adapter does not have this annotation, + * the emitted data is named `""` . + * + * Use this annotation to name the emitted data for disambiguation. + * + * Date: 20/01/21 + */ +@Documented +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +public @interface EmitData { + String name(); +} diff --git a/tef-core/src/main/java/flipkart/tef/annotations/InjectData.java b/tef-core/src/main/java/flipkart/tef/annotations/InjectData.java new file mode 100644 index 0000000..8d9f861 --- /dev/null +++ b/tef-core/src/main/java/flipkart/tef/annotations/InjectData.java @@ -0,0 +1,70 @@ +/* + * 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.annotations; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * This annotation is used to define Data Dependencies in a BizLogic. + * The Task Executor is going to ensure that the DataBizlogic that can generate the corresponding data + * is executed before the execution of the current BizLogic. + * + * + * Date: 19/06/20 + * Time: 4:30 PM + */ +@Documented +@Target(ElementType.FIELD) +@Retention(RetentionPolicy.RUNTIME) +public @interface InjectData { + + /** + * If a DataAdapter is injecting dependent data, and if that data can change, the injection + * can be marked as mutable. In such cases, when the injected data changes, + * the injectee data adapter will get re-triggered. + * + * @return + */ + boolean mutable() default false; + + /** + * This flag should be used on the bizlogics which are ok to receive a null value for this injection. + * + * @return + */ + boolean nullable() default false; + + /** + * Name of the Injected data for specificity + * + * @return + */ + String name() default ""; + + /** + * This parameter is indicative of the fact that the particular injection is optional. + * No checks will be performed if there are no data adapters present for this injection. + * Such injections will only be served via the ImplicitBindings. + * + * @return + */ + boolean optional() default false; +} diff --git a/tef-core/src/main/java/flipkart/tef/bizlogics/DataAdapterKey.java b/tef-core/src/main/java/flipkart/tef/bizlogics/DataAdapterKey.java new file mode 100644 index 0000000..f16a12a --- /dev/null +++ b/tef-core/src/main/java/flipkart/tef/bizlogics/DataAdapterKey.java @@ -0,0 +1,63 @@ +/* + * 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.bizlogics; + +import com.google.common.base.Preconditions; + +/** + * This class represents the key that is used to stash the DataAdapter response into the Data Context + * + * Date: 19/01/21 + */ +public class DataAdapterKey { + + // The name of the data being emitted. Its allowed for the name to be empty (which is the default behavior) . + private final String name; + private final Class resultClass; + + public DataAdapterKey(String name, Class resultClass) { + Preconditions.checkArgument(name != null); + this.name = name; + this.resultClass = resultClass; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + DataAdapterKey that = (DataAdapterKey) o; + + if (!getName().equals(that.getName())) return false; + return resultClass.equals(that.resultClass); + } + + @Override + public int hashCode() { + int result = getName().hashCode(); + result = 31 * result + resultClass.hashCode(); + return result; + } + + public String getName() { + return name; + } + + public Class getResultClass() { + return resultClass; + } +} diff --git a/tef-core/src/main/java/flipkart/tef/bizlogics/DataAdapterResult.java b/tef-core/src/main/java/flipkart/tef/bizlogics/DataAdapterResult.java new file mode 100644 index 0000000..aef43be --- /dev/null +++ b/tef-core/src/main/java/flipkart/tef/bizlogics/DataAdapterResult.java @@ -0,0 +1,52 @@ +/* + * 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.bizlogics; + +import com.google.common.base.Preconditions; + +/** + * This class represents the response received from a DataAdapter Bizlogic + * + * Date: 19/01/21 + */ +public class DataAdapterResult { + private final Object result; + private DataAdapterKey key; + + public DataAdapterResult(Object result) { + this(result, "", null); + } + + public DataAdapterResult(Object result, String name, Class resultType) { + Preconditions.checkArgument(name != null); + this.result = result; + + // Result could be null in case of nullable data adapters + // This is to support that + if (result != null) { + this.key = new DataAdapterKey(name, resultType == null ? result.getClass() : resultType); + } + } + + public Object getResult() { + return result; + } + + public DataAdapterKey getKey() { + return key; + } +} diff --git a/tef-core/src/main/java/flipkart/tef/bizlogics/IBizlogic.java b/tef-core/src/main/java/flipkart/tef/bizlogics/IBizlogic.java new file mode 100644 index 0000000..94c353a --- /dev/null +++ b/tef-core/src/main/java/flipkart/tef/bizlogics/IBizlogic.java @@ -0,0 +1,51 @@ +/* + * 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.bizlogics; + +import flipkart.tef.exception.TefExecutionException; + +import java.util.Optional; +/** + * A bizlogic is a piece of arbitrary logic that can be executed as part of a `SimpleFlow` . + * Bizlogics can depend upon each other to form an Execution DAG. + * + * + * Date: 19/06/20 + * Time: 4:24 PM + */ +public interface IBizlogic { + /** + * Implement the business logic here. + * + * @param tefContext Contains request specific data + */ + + void execute(TefContext tefContext) throws TefExecutionException; + + /** + * This method will be invoked by the Flow Executor to extract the data to be made available + * wherever its being injected. + * + * @param tefContext Contains request specific data + * @return Optional.empty if no data is produced or not applicable. + */ + default Optional executeForData(TefContext tefContext) throws TefExecutionException { + execute(tefContext); + return Optional.empty(); + } + +} diff --git a/tef-core/src/main/java/flipkart/tef/bizlogics/IDataBizlogic.java b/tef-core/src/main/java/flipkart/tef/bizlogics/IDataBizlogic.java new file mode 100644 index 0000000..5160a0b --- /dev/null +++ b/tef-core/src/main/java/flipkart/tef/bizlogics/IDataBizlogic.java @@ -0,0 +1,55 @@ +/* + * 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.bizlogics; + +import flipkart.tef.exception.TefExecutionException; + +import java.util.Optional; + +/** + * IDataBizlogic is a flavour of bizlogic which should return a Data Object. + * These bizlogics aid in Data Dependency. + * + * + * Date: 11/08/20 + * Time: 2:32 PM + */ + +public interface IDataBizlogic extends IBizlogic { + + /** + * This method is unsupported for IDataBizlogic. + * + * @param tefContext + */ + @Override + default void execute(TefContext tefContext) { + throw new UnsupportedOperationException("IDataBizlogic should not invoke void execute"); + } + + /** + * This method will be invoked by the Flow Executor to extract the data to be made available + * wherever its being injected. + * + * @param tefContext + * @return + */ + Optional executeForData(TefContext tefContext) throws TefExecutionException; + + String name(); + +} diff --git a/tef-core/src/main/java/flipkart/tef/bizlogics/TefContext.java b/tef-core/src/main/java/flipkart/tef/bizlogics/TefContext.java new file mode 100644 index 0000000..0f815e2 --- /dev/null +++ b/tef-core/src/main/java/flipkart/tef/bizlogics/TefContext.java @@ -0,0 +1,56 @@ +/* + * 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.bizlogics; + +import com.google.inject.Injector; + +import java.util.HashMap; +import java.util.Map; +import java.util.function.Consumer; + +/** + * This class is used for Maintaining/passing request scoped context information. + * + * Date: 14/12/21 + */ +public class TefContext { + + // This map is to be used by clients to stash request level context info. + // This will be made available to bizlogics + private final Map extensions; + private final Injector injector; + private final Consumer exceptionLogger; + + + public TefContext(Map additionalContext, Injector injector, Consumer exceptionLogger) { + this.extensions = new HashMap<>(additionalContext); + this.injector = injector; + this.exceptionLogger = exceptionLogger; + } + + public T getAdditionalContext(String key, Class type) { + return type.cast(extensions.get(key)); + } + + public Injector getInjector() { + return injector; + } + + public Consumer getExceptionLogger() { + return exceptionLogger; + } +} diff --git a/tef-core/src/main/java/flipkart/tef/exception/ErrorCode.java b/tef-core/src/main/java/flipkart/tef/exception/ErrorCode.java new file mode 100644 index 0000000..810b8ab --- /dev/null +++ b/tef-core/src/main/java/flipkart/tef/exception/ErrorCode.java @@ -0,0 +1,23 @@ +/* + * 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.exception; + +public enum ErrorCode { + RETRYABLE, //for future iterations + NON_RETRYABLE, + TOO_MANY_REQUEST +} \ No newline at end of file diff --git a/tef-core/src/main/java/flipkart/tef/exception/TefExecutionException.java b/tef-core/src/main/java/flipkart/tef/exception/TefExecutionException.java new file mode 100644 index 0000000..aa1a724 --- /dev/null +++ b/tef-core/src/main/java/flipkart/tef/exception/TefExecutionException.java @@ -0,0 +1,42 @@ +/* + * 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.exception; + +/** + * This class is for handling exceptions being thrown from Bizlogic layer in TEF Layer and logging/propagating to upper layer + * User: shirish.jain + * Date: 15/06/2021 + */ +public class TefExecutionException extends Exception { + private final ErrorCode errorCode; + + + public TefExecutionException(String message, ErrorCode errorCode) { + super(message); + this.errorCode = errorCode; + } + + public TefExecutionException(String message, Throwable cause, ErrorCode errorCode) { + super(message, cause); + this.errorCode = errorCode; + } + + public ErrorCode getErrorCode() { + return errorCode; + } + +} diff --git a/tef-core/src/main/java/flipkart/tef/flow/SimpleFlow.java b/tef-core/src/main/java/flipkart/tef/flow/SimpleFlow.java new file mode 100644 index 0000000..e3cb0d2 --- /dev/null +++ b/tef-core/src/main/java/flipkart/tef/flow/SimpleFlow.java @@ -0,0 +1,60 @@ +/* + * 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.flow; + +import com.google.common.collect.BiMap; +import com.google.common.collect.ImmutableBiMap; +import com.google.common.collect.ImmutableList; +import flipkart.tef.bizlogics.DataAdapterKey; +import flipkart.tef.bizlogics.IBizlogic; +import flipkart.tef.bizlogics.IDataBizlogic; + +import java.util.List; + +/** + * Simple Flow Represents an Execution DAG. The list of bizlogics are in strict order of execution. + * Cycles are guaranteed to be not present. + * + * + * Date: 19/06/20 + * Time: 5:02 PM + */ +public class SimpleFlow { + /** + * A strictly ordered list of bizlogics for execution. + */ + private final List> bizlogics; + + /** + * Map keyed by the Data Object against the Data Adapter that is responsible for producing it. + */ + private final BiMap, Class>> dataAdapterMap; + + public SimpleFlow(List> bizlogics, + BiMap, Class>> dataAdapterMap) { + this.bizlogics = ImmutableList.copyOf(bizlogics); + this.dataAdapterMap = ImmutableBiMap.copyOf(dataAdapterMap); + } + + public List> getBizlogics() { + return bizlogics; + } + + public BiMap, Class>> getDataAdapterMap() { + return dataAdapterMap; + } +} diff --git a/tef-impl/pom.xml b/tef-impl/pom.xml new file mode 100644 index 0000000..616a7b2 --- /dev/null +++ b/tef-impl/pom.xml @@ -0,0 +1,46 @@ + + + + + + flipkart.tef + tef + 0.0.1 + + 4.0.0 + + tef-impl + + + + flipkart.tef + ${revision} + tef-core + + + + junit + junit + 4.12 + test + + + + + \ No newline at end of file diff --git a/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java b/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java new file mode 100644 index 0000000..82af049 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/TefGuiceModule.java @@ -0,0 +1,34 @@ +/* + * 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; + +import com.google.inject.AbstractModule; +import flipkart.tef.execution.DataInjector; +import flipkart.tef.execution.DefaultDataInjector; + +/** + * This class contains guice binding relevant for tef. + * + * Date: 01/07/21 + */ +public class TefGuiceModule extends AbstractModule { + + @Override + protected void configure() { + bind(DataInjector.class).to(DefaultDataInjector.class); + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/bizlogics/BasicEnrichmentBizlogic.java b/tef-impl/src/main/java/flipkart/tef/bizlogics/BasicEnrichmentBizlogic.java new file mode 100644 index 0000000..702963c --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/bizlogics/BasicEnrichmentBizlogic.java @@ -0,0 +1,57 @@ +/* + * 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.bizlogics; + +import flipkart.tef.capability.model.EnrichmentResultData; +import flipkart.tef.exception.TefExecutionException; + +/** + * A specialized bizlogic that is responsible for emitting data + * that is used to enrich the target as per the mapping rules. + * + * + * Date: 07/07/20 + * Time: 1:48 PM + */ +public abstract class BasicEnrichmentBizlogic implements IBizlogic { + + public final void execute(TefContext tefContext) throws TefExecutionException { + + EnrichmentResultData result = enrich(); + // TODO BP Have namespaces during mapping + map(result, getTarget()); + } + + /** + * @return Return an enriched object + */ + public abstract EnrichmentResultData enrich() throws TefExecutionException; + + /** + * This method is supposed to take the enriched data and put it at an appropriate place + * in the client model. + * + * @param enriched The enriched object returned by the `enrich` method + * @param target The target object on which the enriched data will be applied. + */ + public abstract void map(EnrichmentResultData enriched, Target target) throws TefExecutionException; + + /** + * @return The target on which the data will be enriched. + */ + public abstract Target getTarget(); +} diff --git a/tef-impl/src/main/java/flipkart/tef/bizlogics/BasicValidationBizlogic.java b/tef-impl/src/main/java/flipkart/tef/bizlogics/BasicValidationBizlogic.java new file mode 100644 index 0000000..b515239 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/bizlogics/BasicValidationBizlogic.java @@ -0,0 +1,48 @@ +/* + * 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.bizlogics; + +import flipkart.tef.capability.model.ValidationResultData; +import flipkart.tef.exception.TefExecutionException; + +/** + * A specialized bizlogic that can be used to validate items in cart. + *

+ * + * Date: 19/06/20 + * Time: 4:25 PM + */ +public abstract class BasicValidationBizlogic implements IBizlogic { + + + @Override + public final void execute(TefContext tefContext) throws TefExecutionException { + applyValidationResult(validate(), getTarget()); + } + + protected abstract void applyValidationResult(ValidationResultData result, Target target) throws TefExecutionException; + + /** + * Implement this method to run the validation logic and return the validation status + * at an item level. + * + * @return Item Level Validation Status + */ + public abstract ValidationResultData validate() throws TefExecutionException; + + public abstract Target getTarget(); +} diff --git a/tef-impl/src/main/java/flipkart/tef/bizlogics/DataAdapterBizlogic.java b/tef-impl/src/main/java/flipkart/tef/bizlogics/DataAdapterBizlogic.java new file mode 100644 index 0000000..ac4706e --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/bizlogics/DataAdapterBizlogic.java @@ -0,0 +1,136 @@ +/* + * 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.bizlogics; + +import flipkart.tef.annotations.EmitData; +import flipkart.tef.annotations.InjectData; +import flipkart.tef.exception.TefExecutionException; +import flipkart.tef.execution.MutationListener; + +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.util.HashMap; +import java.util.Map; +import java.util.Optional; + +/** + * A specialized bizlogic that is responsible for emitting data + * that can be used by other bizlogics. + * + * + * Date: 19/06/20 + * Time: 4:36 PM + */ +public abstract class DataAdapterBizlogic implements IDataBizlogic, MutationListener { + + /** + * This is a fieldCache to save cost of reflection. The fieldCache is then used to handle mutations + * on injected members. + */ + private final Map, Field> fieldCache; + private final String emittedDataName; + private final Class resultType; + + private T result; + + public DataAdapterBizlogic() { + fieldCache = buildCacheOfMutableFields(); + emittedDataName = getEmittedDataName(this.getClass()); + resultType = getResultType(); + } + + @SuppressWarnings("unchecked") + protected Class getResultType() { + try { + Method method = this.getClass().getMethod("adapt", TefContext.class); + return (Class) method.getReturnType(); + } catch (NoSuchMethodException e) { + throw new RuntimeException("Interface error in Tef - Adapt Method changed", e); + } + } + + private Map, Field> buildCacheOfMutableFields() { + + Map, Field> cache = new HashMap<>(); + + for (Field field : this.getClass().getDeclaredFields()) { + InjectData annotation = field.getAnnotation(InjectData.class); + if (annotation != null && annotation.mutable()) { + cache.put(new DataAdapterKey<>(annotation.name(), field.getType()), field); + } + } + + return cache; + } + + @SuppressWarnings("rawtypes") + public static String getEmittedDataName(Class clazz) { + EmitData emitData = clazz.getAnnotation(EmitData.class); + if (emitData != null) { + return emitData.name(); + } else { + return ""; + } + } + + @Override + public final Optional executeForData(TefContext tefContext) throws TefExecutionException { + if (result == null) { + result = adapt(tefContext); + } + return Optional.of(new DataAdapterResult(result, name(), resultType)); + } + + /** + * This method is invoked when an Injected data which is marked for mutation, changes. + * + * @param object Mutated Object + */ + @Override + public final void mutated(DataAdapterResult object) { + Field member = fieldCache.get(object.getKey()); + if (member != null) { + try { + member.setAccessible(true); + member.set(this, object.getResult()); + // invalidate the cache. + this.result = null; + } catch (IllegalAccessException e) { + throw new RuntimeException(e); + } + } + } + + /** + * This method acts a runtime lookup optimized for performance (to avoid reflection), + * exposing @EmitData.name() as a runtime attribute. + * + * This method is not intended to be overridden by subclasses. Interested parties should annotate their + * bizlogics with @EmitData and provide a name there. + */ + @Override + public final String name() { + return emittedDataName; + } + + /** + * + * @param tefContext Tef Context + * @return The emitted object. + */ + public abstract T adapt(TefContext tefContext) throws TefExecutionException; +} diff --git a/tef-impl/src/main/java/flipkart/tef/capability/AdapterConflictRuntimeException.java b/tef-impl/src/main/java/flipkart/tef/capability/AdapterConflictRuntimeException.java new file mode 100644 index 0000000..99f64b0 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/capability/AdapterConflictRuntimeException.java @@ -0,0 +1,33 @@ +/* + * 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.capability; + +import flipkart.tef.bizlogics.IBizlogic; + +/** + * + * Date: 02/07/20 + * Time: 2:58 PM + */ +public class AdapterConflictRuntimeException extends RuntimeException { + + private static final String MESSAGE_FORMAT = "Adapter conflict for data %s within %s and %s"; + + public AdapterConflictRuntimeException(Class returnType, Class b1, Class b2) { + super(String.format(MESSAGE_FORMAT, returnType.getSimpleName(), b1.getSimpleName(), b2.getSimpleName())); + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/capability/BizlogicDependency.java b/tef-impl/src/main/java/flipkart/tef/capability/BizlogicDependency.java new file mode 100644 index 0000000..d7ed9c6 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/capability/BizlogicDependency.java @@ -0,0 +1,43 @@ +/* + * 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.capability; + +import flipkart.tef.bizlogics.IBizlogic; + +/** + * This pojo holds a simple bizlogic dependency + * + * Date: 28/06/21 + */ +public class BizlogicDependency { + private final Class bizlogic; + private final Class[] dependencies; + + public BizlogicDependency(Class bizlogic, + Class[] dependencies) { + this.bizlogic = bizlogic; + this.dependencies = dependencies; + } + + public Class getBizlogic() { + return bizlogic; + } + + public Class[] getDependencies() { + return dependencies; + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/capability/CapabilityDefinition.java b/tef-impl/src/main/java/flipkart/tef/capability/CapabilityDefinition.java new file mode 100644 index 0000000..55ae111 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/capability/CapabilityDefinition.java @@ -0,0 +1,78 @@ +/* + * 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.capability; + +import flipkart.tef.bizlogics.BasicEnrichmentBizlogic; +import flipkart.tef.bizlogics.BasicValidationBizlogic; +import flipkart.tef.bizlogics.DataAdapterBizlogic; +import flipkart.tef.bizlogics.IBizlogic; + +import java.util.List; + +/** + * A capability is defined as a set of bizlogics that provide a capability/feature. + * A Capability would be made up of a set of service interactions/model enrichers/validators + * and any bizlogic exclusions. + * A capability can be dependent upon other capabilities, in which case bizlogics + * in the dependent capability will be also be incorporated. + * + * The entire list of bizlogics are then used to create an Execution DAG resolving all the + * control/data dependencies. + * + * + * Date: 10/06/20 + * Time: 8:30 AM + */ +public interface CapabilityDefinition { + + /** + * @return A unique name for this capability. + */ + String name(); + + /** + * @return A list of dependent capabilities. + */ + List dependentCapabilities(); + + /** + * @return A list of validators that are part of this capability. + */ + List> validators(); + + /** + * @return A list of enrichers that are part of this capability. + */ + List> enrichers(); + + /** + * @return A list of data adapters that are part of this capability. + */ + List> adapters(); + + /** + * This exclusion is applied after all the dependencies have been crawled transitively. + * + * @return List if bizlogics that should be excluded in this capability. + */ + List> exclusions(); + + /** + * @return A list of explicit bizlogic dependencies. + */ + List bizlogicDependencies(); +} diff --git a/tef-impl/src/main/java/flipkart/tef/capability/EmptyCapabilityDefinition.java b/tef-impl/src/main/java/flipkart/tef/capability/EmptyCapabilityDefinition.java new file mode 100644 index 0000000..b74800d --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/capability/EmptyCapabilityDefinition.java @@ -0,0 +1,69 @@ +/* + * 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.capability; + +import flipkart.tef.bizlogics.BasicEnrichmentBizlogic; +import flipkart.tef.bizlogics.BasicValidationBizlogic; +import flipkart.tef.bizlogics.DataAdapterBizlogic; +import flipkart.tef.bizlogics.IBizlogic; + +import java.util.ArrayList; +import java.util.List; + +/** + * A stub implementation of `CapabilityDefinition`. + * + * + * Date: 10/06/20 + * Time: 8:30 AM + */ +public abstract class EmptyCapabilityDefinition implements CapabilityDefinition { + + @Override + public List dependentCapabilities() { + return emptyList(); + } + + @Override + public List> validators() { + return emptyList(); + } + + @Override + public List> enrichers() { + return emptyList(); + } + + @Override + public List> adapters() { + return emptyList(); + } + + @Override + public List> exclusions() { + return emptyList(); + } + + protected final List emptyList() { + return new ArrayList<>(); + } + + @Override + public List bizlogicDependencies() { + return emptyList(); + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/capability/model/EnrichmentResultData.java b/tef-impl/src/main/java/flipkart/tef/capability/model/EnrichmentResultData.java new file mode 100644 index 0000000..c72729b --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/capability/model/EnrichmentResultData.java @@ -0,0 +1,27 @@ +/* + * 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.capability.model; + + +/** + * + * Date: 06/06/20 + * Time: 3:10 PM + */ +public class EnrichmentResultData extends MapBaseData { + +} diff --git a/tef-impl/src/main/java/flipkart/tef/capability/model/MapBaseData.java b/tef-impl/src/main/java/flipkart/tef/capability/model/MapBaseData.java new file mode 100644 index 0000000..59d5dff --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/capability/model/MapBaseData.java @@ -0,0 +1,164 @@ +/* + * 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. + */ + +/** + * + * Date: 06/06/20 + * Time: 3:07 PM + */ +package flipkart.tef.capability.model; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Map; +import java.util.Set; +import java.util.function.BiConsumer; +import java.util.function.BiFunction; +import java.util.function.Function; + +public abstract class MapBaseData implements Map { + + private final Map underlying; + + public MapBaseData() { + this.underlying = new HashMap<>(); + } + + @Override + public int size() { + return underlying.size(); + } + + @Override + public boolean isEmpty() { + return underlying.isEmpty(); + } + + @Override + public boolean containsKey(Object key) { + return underlying.containsKey(key); + } + + @Override + public boolean containsValue(Object value) { + return underlying.containsValue(value); + } + + @Override + public V get(Object key) { + return underlying.get(key); + } + + @Override + public V put(K key, V value) { + return underlying.put(key, value); + } + + @Override + public V remove(Object key) { + return underlying.remove(key); + } + + @Override + public void putAll(Map m) { + underlying.putAll(m); + } + + @Override + public void clear() { + underlying.clear(); + } + + @Override + public Set keySet() { + return underlying.keySet(); + } + + @Override + public Collection values() { + return underlying.values(); + } + + @Override + public Set> entrySet() { + return underlying.entrySet(); + } + + @Override + public boolean equals(Object o) { + return underlying.equals(o); + } + + @Override + public int hashCode() { + return underlying.hashCode(); + } + + @Override + public V getOrDefault(Object key, V defaultValue) { + return underlying.getOrDefault(key, defaultValue); + } + + @Override + public void forEach(BiConsumer action) { + underlying.forEach(action); + } + + @Override + public void replaceAll(BiFunction function) { + underlying.replaceAll(function); + } + + @Override + public V putIfAbsent(K key, V value) { + return underlying.putIfAbsent(key, value); + } + + @Override + public boolean remove(Object key, Object value) { + return underlying.remove(key, value); + } + + @Override + public boolean replace(K key, V oldValue, V newValue) { + return underlying.replace(key, oldValue, newValue); + } + + @Override + public V replace(K key, V value) { + return underlying.replace(key, value); + } + + @Override + public V computeIfAbsent(K key, Function mappingFunction) { + return underlying.computeIfAbsent(key, mappingFunction); + } + + @Override + public V computeIfPresent(K key, BiFunction remappingFunction) { + return underlying.computeIfPresent(key, remappingFunction); + } + + @Override + public V compute(K key, BiFunction remappingFunction) { + return underlying.compute(key, remappingFunction); + } + + @Override + public V merge(K key, V value, BiFunction remappingFunction) { + return underlying.merge(key, value, remappingFunction); + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/capability/model/ValidationResultData.java b/tef-impl/src/main/java/flipkart/tef/capability/model/ValidationResultData.java new file mode 100644 index 0000000..a054b98 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/capability/model/ValidationResultData.java @@ -0,0 +1,26 @@ +/* + * 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.capability.model; + +/** + * + * Date: 06/06/20 + * Time: 2:06 PM + */ +public class ValidationResultData extends MapBaseData { + +} diff --git a/tef-impl/src/main/java/flipkart/tef/execution/DataContext.java b/tef-impl/src/main/java/flipkart/tef/execution/DataContext.java new file mode 100644 index 0000000..0e25bbc --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/execution/DataContext.java @@ -0,0 +1,87 @@ +/* + * 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.execution; + +import flipkart.tef.bizlogics.DataAdapterKey; +import flipkart.tef.bizlogics.DataAdapterResult; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * DataContext is used to store the data being generated by adapters during a flow execution. + * The stored data is then used to support the injections wherever required. + * + * + * Date: 19/06/20 + * Time: 5:23 PM + */ +public class DataContext { + private final Map context; + private final List mutationListeners; + + public DataContext() { + context = new HashMap<>(); + mutationListeners = new ArrayList<>(); + } + + public void put(DataAdapterResult value) { + if (value != null && value.getResult() != null) { + if (context.containsKey(value.getKey())) { + mutationListeners.forEach(l -> l.mutated(value)); + } + context.put(value.getKey(), value.getResult()); + } + } + + // TODO streamline this interface + + /** + * Get the data from the datacontext with an empty name + * + * @param clazz + * @param + * @return + */ + public T getAnonymous(Class clazz) { + return get(clazz, ""); + } + + private T get(Class clazz, String name) { + DataAdapterKey key = new DataAdapterKey<>(name, clazz); + return get(key); + } + + public T get(DataAdapterKey key) { + Object value = context.get(key); + if (value != null) { + return key.getResultClass().cast(context.get(key)); + } else { + return null; + } + } + + public boolean addMutationListener(MutationListener listener) { + return this.mutationListeners.add(listener); + } + + public boolean removeMutationListener(MutationListener listener) { + return this.mutationListeners.remove(listener); + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/execution/DataDependencyException.java b/tef-impl/src/main/java/flipkart/tef/execution/DataDependencyException.java new file mode 100644 index 0000000..3aefcc4 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/execution/DataDependencyException.java @@ -0,0 +1,28 @@ +/* + * 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.execution; + +/** + * + * Date: 22/06/20 + * Time: 10:20 AM + */ +public class DataDependencyException extends Exception { + public DataDependencyException(String message) { + super(message); + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/execution/DataInjector.java b/tef-impl/src/main/java/flipkart/tef/execution/DataInjector.java new file mode 100644 index 0000000..e19d775 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/execution/DataInjector.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.execution; + +import flipkart.tef.exception.TefExecutionException; + +/** + * This interface is defines a method for injecting data + * into bizlogics which have the @InjectData annotation. + * + * Date: 16/04/21 + */ +public interface DataInjector { + + /** + * This method sets the values on all @InjectData annotation member variables. + * + * @param bizlogic Bizlogic instance on which the data has to be injected + * @param bizlogicClass The class of bizlogic + * @param valueProvider The value provider which is queried to get the value that will be injected. + * @throws DataDependencyException + * @throws IllegalAccessException + */ + void injectData(Object bizlogic, Class bizlogicClass, InjectableValueProvider valueProvider) + throws DataDependencyException, IllegalAccessException, TefExecutionException; +} diff --git a/tef-impl/src/main/java/flipkart/tef/execution/DefaultDataInjector.java b/tef-impl/src/main/java/flipkart/tef/execution/DefaultDataInjector.java new file mode 100644 index 0000000..cc8cac4 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/execution/DefaultDataInjector.java @@ -0,0 +1,56 @@ +/* + * 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.execution; + +import flipkart.tef.annotations.InjectData; +import flipkart.tef.exception.TefExecutionException; + +import java.lang.reflect.Field; + +/** + * This class is used for injecting data into bizlogics + * + * Date: 16/04/21 + */ +public class DefaultDataInjector implements DataInjector { + + private static final String INJECTABLE_CANNOT_BE_NULL_MESSAGE = "Injectable Data %s cannot be null in %s"; + + @Override + public void injectData(Object bizlogic, Class bizlogicClass, + InjectableValueProvider valueProvider) throws DataDependencyException, IllegalAccessException, TefExecutionException { + Field[] fields = bizlogicClass.getDeclaredFields(); + for (Field field : fields) { + InjectData injectable = field.getAnnotation(InjectData.class); + if (injectable != null) { + field.setAccessible(true); + Object valueToInject = valueProvider.getValueToInject(field.getType(), injectable.name()); + if (valueToInject == null) { + boolean canBeNull = injectable.nullable() || injectable.optional(); + if (!canBeNull) { + throw new DataDependencyException(String.format(INJECTABLE_CANNOT_BE_NULL_MESSAGE, field.getName(), bizlogicClass.getName())); + } + } + field.set(bizlogic, valueToInject); + } + } + + if (!bizlogicClass.equals(Object.class)) { + injectData(bizlogic, bizlogicClass.getSuperclass(), valueProvider); + } + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/execution/FlowBuilder.java b/tef-impl/src/main/java/flipkart/tef/execution/FlowBuilder.java new file mode 100644 index 0000000..7811460 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/execution/FlowBuilder.java @@ -0,0 +1,301 @@ +/* + * 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.execution; + +import com.google.common.annotations.VisibleForTesting; +import com.google.common.base.Preconditions; +import com.google.common.collect.ArrayListMultimap; +import com.google.common.collect.BiMap; +import com.google.common.collect.HashBiMap; +import com.google.common.collect.Multimap; +import flipkart.tef.annotations.DependsOn; +import flipkart.tef.annotations.InjectData; +import flipkart.tef.bizlogics.DataAdapterBizlogic; +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.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; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.Stack; + +// TODO Abstract out Flow Builder Implementation so that it does not have to be exposed to the client + +/** + * Flow Builder takes in the list of bizlogics with any explicit dependencies and exclusions. + * Any implicit dependencies are discovered and a Execution DAG is generated. + * + * + * Date: 19/06/20 + * Time: 5:02 PM + */ +public class FlowBuilder { + private final List> bizlogics; + private final Multimap, Class> bizlogicDependencyMap; + private final Multimap, Class> reverseBizlogicDependencyMap; + private final Multimap, DataAdapterKey> dataDependencyMap; + private final BiMap, Class>> dataAdapterMap; + private final Set> implicitDataBindings; + private final Set> excludedBizlogics; + + FlowBuilder() { + bizlogics = new ArrayList<>(); + bizlogicDependencyMap = ArrayListMultimap.create(); + reverseBizlogicDependencyMap = ArrayListMultimap.create(); + dataDependencyMap = ArrayListMultimap.create(); + dataAdapterMap = HashBiMap.create(); + implicitDataBindings = new HashSet<>(); + excludedBizlogics = new HashSet<>(); + } + + FlowBuilder add(Class bizlogic, Class... dependencies) { + + if (!bizlogics.contains(bizlogic)) { + bizlogics.add(bizlogic); + } + + if (dependencies.length != 0) { + addDependencies(bizlogic, dependencies); + // Process the dependencies too + for (Class dependency : dependencies) { + add(dependency); + } + } + + return this; + } + + @VisibleForTesting + Collection> getBizlogics() { + return bizlogics; + } + + @VisibleForTesting + Multimap, Class> getBizlogicDependencyMap() { + return bizlogicDependencyMap; + } + + @VisibleForTesting + BiMap, Class>> getDataAdapterMap() { + return dataAdapterMap; + } + + @VisibleForTesting + Multimap, DataAdapterKey> getDataDependencyMap() { + return dataDependencyMap; + } + + @VisibleForTesting + Multimap, Class> getReverseBizlogicDependencyMap() { + return reverseBizlogicDependencyMap; + } + + FlowBuilder exclude(Class bizlogic) { + excludedBizlogics.add(bizlogic); + return this; + } + + FlowBuilder withImplicitBindings(Class... bindings) { + for (Class binding : bindings) { + this.implicitDataBindings.add(new DataAdapterKey<>("", binding)); + } + return this; + } + + FlowBuilder withImplicitBindings(DataAdapterKey binding) { + this.implicitDataBindings.add(binding); + return this; + } + + /** + * Generates the Execution DAG as per the supplied bizlogics. + * + * @return + */ + SimpleFlow build() { + + for (Class e : excludedBizlogics) { + bizlogics.remove(e); + bizlogicDependencyMap.removeAll(e); + } + + int idx = 0; + while (bizlogics.size() > idx) { + Class aClass = bizlogics.get(idx++); + processBizLogic(aClass); + } + + List> bizlogicsInFlow = new ArrayList<>(); + Stack> startNodes = new Stack<>(); + + convertDataDependencyToBizLogicDependency(); + for (Class bizlogic : bizlogics) { + if (!bizlogicDependencyMap.containsKey(bizlogic)) { + startNodes.push(bizlogic); + } + } + + Preconditions.checkArgument(startNodes.size() > 0, Messages.COULD_NOT_DEDUCE_THE_STARTING_STEP); + + // Clone for Mutation + Multimap, Class> clonedBizlogicDependencyMap = ArrayListMultimap.create(bizlogicDependencyMap); + + while (!startNodes.isEmpty()) { + Class bizlogic = startNodes.pop(); + bizlogicsInFlow.add(bizlogic); + + for (Class child : reverseBizlogicDependencyMap.get(bizlogic)) { + clonedBizlogicDependencyMap.get(child).remove(bizlogic); + if (clonedBizlogicDependencyMap.get(child).size() == 0) { + startNodes.add(child); + } + } + } + + Preconditions.checkArgument(bizlogicsInFlow.size() == bizlogics.size(), Messages.CYCLIC_GRAPHS_ARE_NOT_SUPPORTED); + + return new SimpleFlow(bizlogicsInFlow, dataAdapterMap); + } + + private void processBizLogic(Class bizlogic) { + handleControlDependency(bizlogic); + handleDataDependency(bizlogic); + populateDataAdapterMap(bizlogic); + } + + private void handleControlDependency(Class bizlogic) { + DependsOn[] dependsOns = bizlogic.getAnnotationsByType(DependsOn.class); + Preconditions.checkArgument(dependsOns.length <= 1, Messages.MORE_THAN_1_DEPENDS_ON_ANNOTATIONS_FOUND); + + if (dependsOns.length > 0) { + addDependencies(bizlogic, dependsOns[0].value()); + for (Class dependency : dependsOns[0].value()) { + add(dependency); + } + } + } + + private void convertDataDependencyToBizLogicDependency() { + + for (Map.Entry, Collection>> entry : dataDependencyMap.asMap().entrySet()) { + for (DataAdapterKey injectedData : entry.getValue()) { + if (implicitDataBindings.contains(injectedData)) { + /** + * If there are implicit bindings - i.e. data that will be passed as part of the data context + * No need to validate that an adapter should be present for it. + */ + continue; + } + Class> adapter = dataAdapterMap.get(injectedData); + Preconditions.checkArgument(adapter != null, String.format(Messages.DATA_ADAPTER_NOT_RESOLVED_FOR, + injectedData.getResultClass().getCanonicalName())); + addDependencies(entry.getKey(), adapter); + } + } + } + + private void populateDataAdapterMap(Class bizlogic) { + try { + if (DataAdapterBizlogic.class.isAssignableFrom(bizlogic)) { + Class> dataAdapterBizLogic = (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(); + } + } + + DataAdapterKey key = new DataAdapterKey<>( + DataAdapterBizlogic.getEmittedDataName(dataAdapterBizLogic), returnType); + + if (!dataAdapterMap.containsKey(key)) { + dataAdapterMap.put(key, dataAdapterBizLogic); + } else { + throw new AdapterConflictRuntimeException(returnType, bizlogic, dataAdapterMap.get(key)); + } + } + } catch (NoSuchMethodException e) { + throw new RuntimeException(e); + } + } + + 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"; + public static final String MORE_THAN_1_DEPENDS_ON_ANNOTATIONS_FOUND = "More than 1 @DependsOn annotations found"; + public static final String COULD_NOT_DEDUCE_THE_STARTING_STEP = "Could not deduce the starting step"; + public static final String DATA_ADAPTER_NOT_RESOLVED_FOR = "Data Adapter not resolved for %s"; + } + + private void addDependencies(Class bizlogic, Class... dependencies) { + for (Class dependency : dependencies) { + Preconditions.checkArgument(bizlogic != dependency, Messages.A_BIZLOGIC_CANNOT_DEPEND_ON_SELF); + bizlogicDependencyMap.put(bizlogic, dependency); + reverseBizlogicDependencyMap.put(dependency, bizlogic); + } + } + + private void handleDataDependency(Class bizlogic) { + List fieldList = new ArrayList<>(); + + Class that = bizlogic; + do { + Field[] fields = that.getDeclaredFields(); + fieldList.addAll(Arrays.asList(fields)); + that = that.getSuperclass(); + } while (!that.equals(Object.class)); + + for (Field field : fieldList) { + InjectData injectable = field.getAnnotation(InjectData.class); + if (injectable != null && !injectable.optional()) { + dataDependencyMap.put(bizlogic, new DataAdapterKey<>(injectable.name(), field.getType())); + } + } + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/execution/FlowExecutor.java b/tef-impl/src/main/java/flipkart/tef/execution/FlowExecutor.java new file mode 100644 index 0000000..9ac1d04 --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/execution/FlowExecutor.java @@ -0,0 +1,189 @@ +/* + * 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.execution; + +import com.google.common.base.Preconditions; +import flipkart.tef.FlowExecutionListener; +import flipkart.tef.bizlogics.DataAdapterKey; +import flipkart.tef.bizlogics.DataAdapterResult; +import flipkart.tef.bizlogics.IBizlogic; +import flipkart.tef.bizlogics.IDataBizlogic; +import flipkart.tef.bizlogics.TefContext; +import flipkart.tef.exception.TefExecutionException; +import flipkart.tef.flow.SimpleFlow; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; + +/** + * The flow executor takes a SimpleFlow and DataContext as an input and then executes it. + * The executor takes care of invoking the right lifecycle methods, + * manages data injection and mutation handlers. + *

+ * This is a stateful class, and a new object should be created for every api call. + *

+ * + * Date: 22/06/20 + * Time: 10:03 AM + */ +public class FlowExecutor implements MutationListener, InjectableValueProvider { + + private final SimpleFlow flow; + private final DataContext context; + private final AllFlowExecutionListener listener; + private final Map> dataAdapterInstanceMap; + private final List mutationListeners; + private final TefContext tefContext; + + private final DataInjector dataInjector; + + + /** + * Create an instance of FlowExecutor. + * @param flow + * @param context + * @param tefContext + */ + public FlowExecutor(SimpleFlow flow, DataContext context, + TefContext tefContext) { + this.flow = flow; + this.context = context; + this.tefContext = tefContext; + this.dataAdapterInstanceMap = new HashMap<>(); + this.listener = new AllFlowExecutionListener(tefContext); + this.mutationListeners = new ArrayList<>(); + this.context.addMutationListener(this); + + this.dataInjector = tefContext.getInjector().getInstance(DataInjector.class); + } + + /** + * Add a Flow Execution Listener + * + * @param listener + */ + public void addListener(FlowExecutionListener listener) { + this.listener.addListener(listener); + } + + /** + * Remove a Flow Execution Listener + * + * @param listener + */ + public void removeListener(FlowExecutionListener listener) { + this.listener.removeListener(listener); + } + + public void execute() throws IllegalAccessException, InstantiationException, DataDependencyException, TefExecutionException { + Preconditions.checkArgument(flow != null); + Preconditions.checkArgument(context != null); + Preconditions.checkArgument(tefContext != null); + + for (Class bizlogicClass : flow.getBizlogics()) { + IBizlogic bizlogic = tefContext.getInjector().getInstance(bizlogicClass); + + if (bizlogic instanceof IDataBizlogic) { + dataAdapterInstanceMap.put(flow.getDataAdapterMap().inverse().get(bizlogicClass), (IDataBizlogic) bizlogic); + } + + if (bizlogic instanceof MutationListener) { + mutationListeners.add((MutationListener) bizlogic); + } + + listener.pre(bizlogic); + dataInjector.injectData(bizlogic, bizlogic.getClass(), this); + try { + Optional resultFromBizlogic = bizlogic.executeForData(tefContext); + resultFromBizlogic.ifPresent(context::put); + } catch (TefExecutionException e) { + tefContext.getExceptionLogger().accept(e); + throw e; + } + listener.post(bizlogic); + } + } + + @Override + public Object getValueToInject(Class fieldType, String name) throws TefExecutionException { + // This step will stash the result in the context + DataAdapterKey key = new DataAdapterKey<>(name, fieldType); + IDataBizlogic adapter = dataAdapterInstanceMap.get(key); + if (adapter != null) { + // Adapter can be null in case of implicit bindings + listener.pre(adapter); + try { + Optional adaptedData = adapter.executeForData(tefContext); + adaptedData.ifPresent(context::put); + } catch (TefExecutionException e) { + tefContext.getExceptionLogger().accept(e); + throw e; + } + listener.post(adapter); + } + + return context.get(key); + } + + @Override + public void mutated(DataAdapterResult object) { + mutationListeners.forEach(i -> i.mutated(object)); + } + + private static class AllFlowExecutionListener implements FlowExecutionListener { + private final List listeners; + private final TefContext tefContext; + + public AllFlowExecutionListener(TefContext tefContext) { + this.listeners = new ArrayList<>(); + this.tefContext = tefContext; + } + + public void addListener(FlowExecutionListener listener) { + listeners.add(listener); + } + + public void removeListener(FlowExecutionListener listener) { + listeners.remove(listener); + } + + @Override + public void pre(IBizlogic bizlogic) { + for (FlowExecutionListener listener : listeners) { + try { + listener.pre(bizlogic); + } catch (Exception e) { + tefContext.getExceptionLogger().accept(e); + } + } + } + + @Override + public void post(IBizlogic bizlogic) { + for (FlowExecutionListener listener : listeners) { + try { + listener.post(bizlogic); + } catch (Exception e) { + tefContext.getExceptionLogger().accept(e); + } + } + } + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/execution/FluentCapabilityBuilder.java b/tef-impl/src/main/java/flipkart/tef/execution/FluentCapabilityBuilder.java new file mode 100644 index 0000000..e51f5ac --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/execution/FluentCapabilityBuilder.java @@ -0,0 +1,167 @@ +/* + * 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.execution; + +import com.google.common.collect.Lists; +import flipkart.tef.bizlogics.BasicEnrichmentBizlogic; +import flipkart.tef.bizlogics.BasicValidationBizlogic; +import flipkart.tef.bizlogics.DataAdapterBizlogic; +import flipkart.tef.bizlogics.IBizlogic; +import flipkart.tef.capability.BizlogicDependency; +import flipkart.tef.capability.CapabilityDefinition; +import flipkart.tef.flow.SimpleFlow; + +import java.util.Collection; +import java.util.List; + +/** + * Simple Flow Builder + *

+ * + * Date: 23/06/20 + * Time: 6:57 PM + */ +@SuppressWarnings({"unchecked", "rawtypes"}) +public class FluentCapabilityBuilder { + + private final FlowBuilder flowBuilder; + + public FluentCapabilityBuilder() { + flowBuilder = new FlowBuilder(); + } + + public FluentCapabilityBuilder withValidator(Class... validators) { + return this.withValidators(Lists.newArrayList(validators)); + } + + public FluentCapabilityBuilder withEnricher(Class... enrichers) { + return this.withEnrichers(Lists.newArrayList(enrichers)); + } + + public FluentCapabilityBuilder withAdapter(Class adapter) { + flowBuilder.add(adapter); + return this; + } + + public FluentCapabilityBuilder withCapability(CapabilityDefinition... capabilities) { + return this.withCapabilities(Lists.newArrayList(capabilities)); + } + + public FluentCapabilityBuilder withValidators(List> validators) { + + if (isNullOrEmpty(validators)) { + return this; + } + + for (Class validator : validators) { + flowBuilder.add(validator); + } + return this; + } + + public FluentCapabilityBuilder withEnrichers(List> enrichers) { + + if (isNullOrEmpty(enrichers)) { + return this; + } + + for (Class enricher : enrichers) { + flowBuilder.add(enricher); + } + return this; + } + + public FluentCapabilityBuilder withAdapters(List> adapters) { + + if (isNullOrEmpty(adapters)) { + return this; + } + + for (Class adapter : adapters) { + flowBuilder.add(adapter); + } + return this; + } + + public FluentCapabilityBuilder withCapabilities(List capabilities) { + + if (isNullOrEmpty(capabilities)) { + return this; + } + + for (CapabilityDefinition capability : capabilities) { + this.withEnrichers(capability.enrichers()); + this.withValidators(capability.validators()); + this.withAdapters(capability.adapters()); + + if (!isNullOrEmpty(capability.exclusions())) { + capability.exclusions().forEach(this::withExclusion); + } + + if (!isNullOrEmpty(capability.dependentCapabilities())) { + // TODO fix possible infinite recursion here + this.withCapabilities(capability.dependentCapabilities()); + } + + List bizlogicDependencies = capability.bizlogicDependencies(); + if (!isNullOrEmpty(bizlogicDependencies)) { + for (BizlogicDependency dependency : bizlogicDependencies) { + this.withDependency(dependency.getBizlogic(), dependency.getDependencies()); + } + } + + } + return this; + } + + public FluentCapabilityBuilder withName(String name) { + return this; + } + + public SimpleFlow dataflow() { + return flowBuilder.build(); + } + + public FluentCapabilityBuilder withImplicitBindings(Class... bindings) { + flowBuilder.withImplicitBindings(bindings); + return this; + } + + public FluentCapabilityBuilder withExclusion(Class exclusion) { + + if (exclusion == null) { + return this; + } + + flowBuilder.exclude(exclusion); + return this; + } + + public FluentCapabilityBuilder withDependency(Class bizlogic, Class[] dependencies) { + flowBuilder.add(bizlogic, dependencies); + return this; + } + + public FluentCapabilityBuilder withBizlogic(Class bizlogic) { + flowBuilder.add(bizlogic); + return this; + } + + private boolean isNullOrEmpty(Collection collection) { + return (collection == null || collection.isEmpty()); + } +} diff --git a/tef-impl/src/main/java/flipkart/tef/execution/InjectableValueProvider.java b/tef-impl/src/main/java/flipkart/tef/execution/InjectableValueProvider.java new file mode 100644 index 0000000..5a6193b --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/execution/InjectableValueProvider.java @@ -0,0 +1,36 @@ +/* + * 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.execution; + +import flipkart.tef.exception.TefExecutionException; + +/** + * This interface is used for providing the value that has to be injected in a bizlogic + * + * Date: 16/04/21 + */ +public interface InjectableValueProvider { + + /** + * Returns + * + * @param fieldType + * @param name + * @return + */ + Object getValueToInject(Class fieldType, String name) throws TefExecutionException; +} diff --git a/tef-impl/src/main/java/flipkart/tef/execution/MutationListener.java b/tef-impl/src/main/java/flipkart/tef/execution/MutationListener.java new file mode 100644 index 0000000..0c67e6b --- /dev/null +++ b/tef-impl/src/main/java/flipkart/tef/execution/MutationListener.java @@ -0,0 +1,31 @@ +/* + * 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.execution; + +import flipkart.tef.bizlogics.DataAdapterResult; + +/** + * Implement this interface to listen to changes in Data Objects. + * + * + * Date: 09/07/20 + * Time: 5:18 PM + */ +@FunctionalInterface +public interface MutationListener { + void mutated(DataAdapterResult object); +} diff --git a/tef-impl/src/test/java/flipkart/tef/TestGuiceModule.java b/tef-impl/src/test/java/flipkart/tef/TestGuiceModule.java new file mode 100644 index 0000000..5952f41 --- /dev/null +++ b/tef-impl/src/test/java/flipkart/tef/TestGuiceModule.java @@ -0,0 +1,33 @@ +/* + * 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; + +import com.google.inject.AbstractModule; +import flipkart.tef.execution.DataInjector; +import flipkart.tef.execution.DefaultDataInjector; + +/** + * This class is used for injecting common test classes + * + * Date: 16/04/21 + */ +public class TestGuiceModule extends AbstractModule { + @Override + protected void configure() { + bind(DataInjector.class).to(DefaultDataInjector.class); + } +} diff --git a/tef-impl/src/test/java/flipkart/tef/TestTefContext.java b/tef-impl/src/test/java/flipkart/tef/TestTefContext.java new file mode 100644 index 0000000..17b2bd5 --- /dev/null +++ b/tef-impl/src/test/java/flipkart/tef/TestTefContext.java @@ -0,0 +1,34 @@ +/* + * 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; + +import com.google.inject.Guice; +import flipkart.tef.bizlogics.TefContext; + +import java.util.HashMap; + +/** + * This class is used for + * + * Date: 17/12/21 + */ +public class TestTefContext extends TefContext { + + public TestTefContext() { + super(new HashMap<>(), Guice.createInjector(new TestGuiceModule()), System.out::println); + } +} diff --git a/tef-impl/src/test/java/flipkart/tef/bizlogics/DataAdapterKeyTest.java b/tef-impl/src/test/java/flipkart/tef/bizlogics/DataAdapterKeyTest.java new file mode 100644 index 0000000..98b1621 --- /dev/null +++ b/tef-impl/src/test/java/flipkart/tef/bizlogics/DataAdapterKeyTest.java @@ -0,0 +1,107 @@ +/* + * 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.bizlogics; + + +import flipkart.tef.TestTefContext; +import flipkart.tef.annotations.EmitData; +import flipkart.tef.annotations.InjectData; +import flipkart.tef.exception.TefExecutionException; +import flipkart.tef.execution.DataContext; +import flipkart.tef.execution.DataDependencyException; +import flipkart.tef.execution.FlowExecutor; +import flipkart.tef.execution.FluentCapabilityBuilder; +import flipkart.tef.flow.SimpleFlow; +import org.junit.BeforeClass; +import org.junit.Test; + +import static org.junit.Assert.assertEquals; + +/** + * Test for DataAdapterResultKey + * + * Date: 19/01/21 + */ +public class DataAdapterKeyTest { + + + + @BeforeClass + public static void setup() { + + } + + @Test + public void testSimpleNamedInjection() throws IllegalAccessException, DataDependencyException, InstantiationException, TefExecutionException { + + FluentCapabilityBuilder capabilityBuilder = new FluentCapabilityBuilder(); + SimpleFlow flow = capabilityBuilder.withAdapter(DataAdapter1.class) + .withBizlogic(SampleBizlogic.class).dataflow(); + + FlowExecutor flowExecutor = new FlowExecutor(flow, new DataContext(), new TestTefContext()); + flowExecutor.execute(); + + } + + @Test + public void testMultiNamedInjection() throws IllegalAccessException, DataDependencyException, InstantiationException, TefExecutionException { + FluentCapabilityBuilder capabilityBuilder = new FluentCapabilityBuilder(); + SimpleFlow flow = capabilityBuilder.withAdapter(DataAdapter1.class) + .withAdapter(DataAdapter2.class) + .withBizlogic(SampleBizlogic.class).dataflow(); + + FlowExecutor flowExecutor = new FlowExecutor(flow, new DataContext(), new TestTefContext()); + flowExecutor.execute(); + } + + public static class SampleData { + public int value; + + public SampleData(int value) { + this.value = value; + } + } + + @EmitData(name = "1") + public static class DataAdapter1 extends DataAdapterBizlogic { + + @Override + public SampleData adapt(TefContext tefContext) { + return new SampleData(1); + } + } + + @EmitData(name = "2") + public static class DataAdapter2 extends DataAdapterBizlogic { + + @Override + public SampleData adapt(TefContext tefContext) { + return new SampleData(2); + } + } + + public static class SampleBizlogic implements IBizlogic { + + @InjectData(name = "1") + private SampleData sampleData1; + + @Override + public void execute(TefContext tefContext) { + assertEquals(sampleData1.value, 1); + } + } +} diff --git a/tef-impl/src/test/java/flipkart/tef/execution/ExecutionStage.java b/tef-impl/src/test/java/flipkart/tef/execution/ExecutionStage.java new file mode 100644 index 0000000..868f92d --- /dev/null +++ b/tef-impl/src/test/java/flipkart/tef/execution/ExecutionStage.java @@ -0,0 +1,26 @@ +/* + * 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. + */ + +/** + * + * Date: 09/07/20 + * Time: 3:07 PM + */ +package flipkart.tef.execution; + +public enum ExecutionStage { + PRE, POST +} diff --git a/tef-impl/src/test/java/flipkart/tef/execution/ExecutionStep.java b/tef-impl/src/test/java/flipkart/tef/execution/ExecutionStep.java new file mode 100644 index 0000000..556bd16 --- /dev/null +++ b/tef-impl/src/test/java/flipkart/tef/execution/ExecutionStep.java @@ -0,0 +1,42 @@ +/* + * 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. + */ + +/** + * + * Date: 09/07/20 + * Time: 3:07 PM + */ +package flipkart.tef.execution; + +import flipkart.tef.bizlogics.IBizlogic; + +public class ExecutionStep { + public Class bizlogic; + public ExecutionStage stage; + + public ExecutionStep(Class bizlogic, ExecutionStage stage) { + this.bizlogic = bizlogic; + this.stage = stage; + } + + @Override + public String toString() { + return "ExecutionStep{" + + "bizlogic=" + bizlogic.getSimpleName() + + ", stage=" + stage + + '}'; + } +} diff --git a/tef-impl/src/test/java/flipkart/tef/execution/FlowBuilderTest.java b/tef-impl/src/test/java/flipkart/tef/execution/FlowBuilderTest.java new file mode 100644 index 0000000..9acc0df --- /dev/null +++ b/tef-impl/src/test/java/flipkart/tef/execution/FlowBuilderTest.java @@ -0,0 +1,461 @@ +/* + * 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.execution; + +import flipkart.tef.annotations.DependsOn; +import flipkart.tef.annotations.InjectData; +import flipkart.tef.bizlogics.DataAdapterBizlogic; +import flipkart.tef.bizlogics.DataAdapterKey; +import flipkart.tef.bizlogics.IBizlogic; +import flipkart.tef.bizlogics.TefContext; +import flipkart.tef.capability.AdapterConflictRuntimeException; +import flipkart.tef.capability.model.MapBaseData; +import flipkart.tef.flow.SimpleFlow; +import org.junit.Test; + +import java.util.List; +import java.util.Map; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; + +public class FlowBuilderTest { + + /** + * Helper method to create anonymous Data Adapter Keys + * + * @param clazz + * @return + */ + private DataAdapterKey getResultKey(Class clazz) { + return new DataAdapterKey("", clazz); + } + + @Test + public void testIslandBizlogics() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleValidator4.class); + flowBuilder.add(SimpleValidator5.class); + flowBuilder.add(SimpleValidator6.class); + + assertEquals(3, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator4.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator5.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator6.class)); + + SimpleFlow flow = flowBuilder.build(); + assertEquals(3, flow.getBizlogics().size()); + assertTrue(flow.getBizlogics().contains(SimpleValidator4.class)); + assertTrue(flow.getBizlogics().contains(SimpleValidator5.class)); + assertTrue(flow.getBizlogics().contains(SimpleValidator6.class)); + } + + @Test + public void testDataDependencyBasic() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleDataAdapter.class); + flowBuilder.build(); + + assertEquals(1, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(SimpleDataAdapter.class)); + assertEquals(1, flowBuilder.getDataAdapterMap().size()); + assertEquals(flowBuilder.getDataAdapterMap().get(getResultKey(SimpleData.class)).getName(), SimpleDataAdapter.class.getName()); + + } + + @Test + public void testDataDependencyWithDuplicates() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleDataAdapter.class); + flowBuilder.add(SimpleDataAdapter.class); + flowBuilder.build(); + + assertEquals(1, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(SimpleDataAdapter.class)); + assertEquals(1, flowBuilder.getDataAdapterMap().size()); + assertEquals(flowBuilder.getDataAdapterMap().get(getResultKey(SimpleData.class)).getName(), SimpleDataAdapter.class.getName()); + + } + + @Test + public void testBizlogicRuntimeDependency() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleDataAdapter.class, SimpleValidator.class); + flowBuilder.build(); + + assertTrue(flowBuilder.getBizlogics().size() == 2); + assertTrue(flowBuilder.getBizlogics().contains(SimpleDataAdapter.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator.class)); + assertEquals(1, flowBuilder.getDataAdapterMap().size()); + assertEquals(SimpleDataAdapter.class, flowBuilder.getDataAdapterMap().get(getResultKey(SimpleData.class))); + assertEquals(1, flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator.class).size()); + assertTrue(flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator.class).contains(SimpleDataAdapter.class)); + } + + @Test + public void testBizlogicCompileDependency() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleEnricher.class); + flowBuilder.build(); + + assertTrue(flowBuilder.getBizlogics().size() == 2); + assertTrue(flowBuilder.getBizlogics().contains(SimpleEnricher.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator.class)); + assertEquals(1, flowBuilder.getBizlogicDependencyMap().get(SimpleEnricher.class).size()); + assertEquals(1, flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator.class).size()); + assertTrue(flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator.class).contains(SimpleEnricher.class)); + } + + @Test + public void testBizlogicDataDependency() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleEnricher2.class, SimpleDataAdapter.class); + flowBuilder.build(); + + assertTrue(flowBuilder.getBizlogics().size() == 2); + assertTrue(flowBuilder.getBizlogics().contains(SimpleEnricher2.class)); + assertTrue(flowBuilder.getDataDependencyMap().get(SimpleEnricher2.class).size() == 1); + assertTrue(flowBuilder.getDataDependencyMap().get(SimpleEnricher2.class).contains(getResultKey(SimpleData.class))); + } + + @Test + public void testImplicitControlDependencyWithoutStartNode() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleValidator2.class); + + assertEquals(1, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator2.class)); + + try { + flowBuilder.build(); + fail("Validation Error was expected"); + } catch (IllegalArgumentException e) { + assertEquals(FlowBuilder.Messages.COULD_NOT_DEDUCE_THE_STARTING_STEP, e.getMessage()); + } + } + + @Test + public void testCyclicDependency() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleValidator2.class); + flowBuilder.add(SimpleValidator4.class); + + assertEquals(2, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator2.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator4.class)); + + try { + flowBuilder.build(); + fail("Validation Error was expected"); + } catch (IllegalArgumentException e) { + assertEquals(FlowBuilder.Messages.CYCLIC_GRAPHS_ARE_NOT_SUPPORTED, e.getMessage()); + } + } + + @Test + public void testCyclicDataDependency() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleValidator6.class); + flowBuilder.add(DataAdapter1.class); + flowBuilder.add(DataAdapter2.class); + + assertEquals(3, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(DataAdapter1.class)); + assertTrue(flowBuilder.getBizlogics().contains(DataAdapter2.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator6.class)); + + try { + flowBuilder.build(); + fail("Validation Error was expected"); + } catch (IllegalArgumentException e) { + assertEquals(FlowBuilder.Messages.CYCLIC_GRAPHS_ARE_NOT_SUPPORTED, e.getMessage()); + } + } + + @Test + public void testMissingStartNode() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleValidator2.class, SimpleValidator3.class); + + assertEquals(2, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator2.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator3.class)); + + try { + flowBuilder.build(); + fail("Validation Error was expected"); + } catch(IllegalArgumentException e){ + assertEquals(FlowBuilder.Messages.COULD_NOT_DEDUCE_THE_STARTING_STEP, e.getMessage()); + } + } + + @Test + public void testBasicFlow() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleValidator4.class, SimpleValidator5.class); + flowBuilder.add(SimpleValidator5.class, SimpleValidator6.class); + SimpleFlow flow = flowBuilder.build(); + + assertEquals(3, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator4.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator5.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator6.class)); + assertEquals(1, flowBuilder.getBizlogicDependencyMap().get(SimpleValidator4.class).size()); + assertEquals(1, flowBuilder.getBizlogicDependencyMap().get(SimpleValidator5.class).size()); + assertEquals(0, flowBuilder.getBizlogicDependencyMap().get(SimpleValidator6.class).size()); + assertEquals(1, flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator6.class).size()); + assertEquals(1, flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator5.class).size()); + assertEquals(0, flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator4.class).size()); + + List> order = flow.getBizlogics(); + assertEquals(SimpleValidator6.class, order.get(0)); + assertEquals(SimpleValidator5.class, order.get(1)); + assertEquals(SimpleValidator4.class, order.get(2)); + } + + @Test + public void testBasicFlow2() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleValidator4.class, SimpleValidator5.class, SimpleValidator6.class); + SimpleFlow flow = flowBuilder.build(); + + assertEquals(3, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator4.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator5.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator6.class)); + assertEquals(2, flowBuilder.getBizlogicDependencyMap().get(SimpleValidator4.class).size()); + assertEquals(0, flowBuilder.getBizlogicDependencyMap().get(SimpleValidator5.class).size()); + assertEquals(0, flowBuilder.getBizlogicDependencyMap().get(SimpleValidator6.class).size()); + assertEquals(1, flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator6.class).size()); + assertEquals(1, flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator5.class).size()); + assertEquals(0, flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator4.class).size()); + + List> order = flow.getBizlogics(); + // Since 5 and 6 are pre-requisites for 4, any one of them can get executed before 4. + assertTrue(order.get(0).equals(SimpleValidator6.class) || order.get(0).equals(SimpleValidator5.class)); + assertTrue(order.get(1).equals(SimpleValidator6.class) || order.get(1).equals(SimpleValidator5.class)); + assertEquals(SimpleValidator4.class, order.get(2)); + } + + @Test + public void testBasicFlow3() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleValidator4.class, SimpleValidator6.class); + flowBuilder.add(SimpleValidator5.class, SimpleValidator6.class); + SimpleFlow flow = flowBuilder.build(); + + assertEquals(3, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator4.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator5.class)); + assertTrue(flowBuilder.getBizlogics().contains(SimpleValidator6.class)); + assertEquals(1, flowBuilder.getBizlogicDependencyMap().get(SimpleValidator4.class).size()); + assertEquals(1, flowBuilder.getBizlogicDependencyMap().get(SimpleValidator5.class).size()); + assertEquals(0, flowBuilder.getBizlogicDependencyMap().get(SimpleValidator6.class).size()); + assertEquals(2, flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator6.class).size()); + assertEquals(0, flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator5.class).size()); + assertEquals(0, flowBuilder.getReverseBizlogicDependencyMap().get(SimpleValidator4.class).size()); + + List> order = flow.getBizlogics(); + assertEquals(SimpleValidator6.class, order.get(0)); + assertEquals(SimpleValidator5.class, order.get(1)); + assertEquals(SimpleValidator4.class, order.get(2)); + } + + @Test + public void testDataDependencyInFlow() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleEnricher2.class); + flowBuilder.add(SimpleDataAdapter.class); + SimpleFlow flow = flowBuilder.build(); + + assertEquals(2, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(SimpleDataAdapter.class)); + assertEquals(1, flowBuilder.getDataAdapterMap().size()); + assertEquals(SimpleDataAdapter.class, flowBuilder.getDataAdapterMap().get(getResultKey(SimpleData.class))); + + List> order = flow.getBizlogics(); + assertEquals(SimpleDataAdapter.class, order.get(0)); + assertEquals(SimpleEnricher2.class, order.get(1)); + } + + @Test + public void testDataDependencyAbsentInFlow() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleEnricher2.class); + + assertEquals(1, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(SimpleEnricher2.class)); + + try { + flowBuilder.build(); + fail("Validation Error was expected"); + } catch (IllegalArgumentException e) { + assertEquals("Data Adapter not resolved for flipkart.tef.execution.FlowBuilderTest.SimpleData", e.getMessage()); + } + } + + @Test + public void testConflictingDataAdapters() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(SimpleDataAdapter.class); + flowBuilder.add(SimpleDataAdapter2.class); + try { + flowBuilder.build(); + fail("Validation Error was expected"); + } catch (AdapterConflictRuntimeException e) { + // No-op + } + } + + @Test + public void testComplexDataAdapter() { + FlowBuilder flowBuilder = new FlowBuilder(); + flowBuilder.add(DataAdapter3.class); + SimpleFlow simpleFlow = flowBuilder.build(); + assertEquals(1, simpleFlow.getBizlogics().size()); + assertTrue(simpleFlow.getBizlogics().contains(DataAdapter3.class)); + } + + + class SimpleData extends MapBaseData { + + } + + class SimpleDataAdapter extends DataAdapterBizlogic { + + @Override + public SimpleData adapt(TefContext tefContext) { + return new SimpleData(); + } + } + + class SimpleDataAdapter2 extends DataAdapterBizlogic { + + @Override + public SimpleData adapt(TefContext tefContext) { + return new SimpleData(); + } + } + + class SimpleValidator implements IBizlogic { + + @Override + public void execute(TefContext tefContext) { + + } + } + + @DependsOn(SimpleValidator.class) + class SimpleEnricher implements IBizlogic { + + @Override + public void execute(TefContext tefContext) { + + } + } + + class SimpleEnricher2 implements IBizlogic { + @InjectData + private SimpleData simpleData; + + @Override + public void execute(TefContext tefContext) { + + } + } + + @DependsOn(SimpleValidator3.class) + class SimpleValidator2 implements IBizlogic { + @Override + public void execute(TefContext tefContext) { + + } + } + + @DependsOn(SimpleValidator2.class) + class SimpleValidator3 implements IBizlogic { + + @Override + public void execute(TefContext tefContext) { + + } + } + + class SimpleValidator4 implements IBizlogic { + + @Override + public void execute(TefContext tefContext) { + + } + } + + class SimpleValidator5 implements IBizlogic { + + @Override + public void execute(TefContext tefContext) { + + } + } + + class SimpleValidator6 implements IBizlogic { + + @Override + public void execute(TefContext tefContext) { + + } + } + + class CyclicData1 { + + } + + class CyclicData2 { + + } + + class DataAdapter1 extends DataAdapterBizlogic { + + @InjectData + CyclicData2 cyclicData2; + + @Override + public CyclicData1 adapt(TefContext tefContext) { + return null; + } + } + + class DataAdapter2 extends DataAdapterBizlogic { + + @InjectData + CyclicData1 cyclicData1; + + @Override + public CyclicData2 adapt(TefContext tefContext) { + return null; + } + } + + class DataAdapter3 extends DataAdapterBizlogic> { + + @Override + public Map adapt(TefContext tefContext) { + return null; + } + } + +} \ No newline at end of file diff --git a/tef-impl/src/test/java/flipkart/tef/execution/FlowExecutorMutationTest.java b/tef-impl/src/test/java/flipkart/tef/execution/FlowExecutorMutationTest.java new file mode 100644 index 0000000..411c1d2 --- /dev/null +++ b/tef-impl/src/test/java/flipkart/tef/execution/FlowExecutorMutationTest.java @@ -0,0 +1,233 @@ +/* + * 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. + */ + +/** + * + * Date: 14/07/20 + * Time: 9:12 AM + */ +package flipkart.tef.execution; + + +import flipkart.tef.TestTefContext; +import flipkart.tef.annotations.DependsOn; +import flipkart.tef.annotations.InjectData; +import flipkart.tef.bizlogics.DataAdapterBizlogic; +import flipkart.tef.bizlogics.DataAdapterResult; +import flipkart.tef.bizlogics.IBizlogic; +import flipkart.tef.bizlogics.TefContext; +import flipkart.tef.exception.TefExecutionException; +import flipkart.tef.flow.SimpleFlow; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Ignore; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +public class FlowExecutorMutationTest { + + public static final String TEST_TENANT = "TestTenant"; + private FlowBuilder flowBuilder; + + // static so that inner classes can access + private static List dataToReturn = null; + private static int dataIndex; + + @BeforeClass + public static void setup() { + + } + + @Before + public void setUp() { + flowBuilder = new FlowBuilder(); + dataToReturn = new ArrayList<>(); + dataToReturn.add(new DataA(1)); + dataToReturn.add(new DataA(2)); + dataIndex = 0; + } + + @Ignore + @Test + public void testMutation() throws IllegalAccessException, DataDependencyException, InstantiationException, TefExecutionException { + + FlowExecutorTest.SampleDataAdapter.ADAPTED = new FlowExecutorTest.SampleData(); + + /** + * Validator1 and Validator2 depends on DataA + * DataADataAdapter's DataB dependency is resolved via implicit binding + * So the order of execution is + * 1. DataADataAdapter + * 2. Followed by Validator1 (since DataA is now available) + * 3. Followed by DataBDataAdapter since Validator2 defines a control dependency on it. + * 4. Followed by Validator2 since it depends on DataA and DataBDataAdapter (via control dependency) + */ + flowBuilder.add(Validator1.class); + flowBuilder.add(Validator2.class); + flowBuilder.add(DataADataAdapter.class); + flowBuilder.add(DataBDataAdapter.class); + + flowBuilder.withImplicitBindings(DataB.class); + assertEquals(4, flowBuilder.getBizlogics().size()); + + SimpleFlow flow = flowBuilder.build(); + + List> bizlogics = new ArrayList<>(flow.getBizlogics()); + assertEquals(4, bizlogics.size()); + assertEquals(DataADataAdapter.class, bizlogics.get(0)); + assertEquals(Validator1.class, bizlogics.get(1)); + assertEquals(DataBDataAdapter.class, bizlogics.get(2)); + assertEquals(Validator2.class, bizlogics.get(3)); + + DataContext dataContext = new DataContext(); + dataContext.put(new DataAdapterResult(new DataB())); + FlowExecutor executor = new FlowExecutor(flow, dataContext, new TestTefContext()); + + MyFlowExecutionListener listener = new MyFlowExecutionListener(); + executor.addListener(listener); + executor.execute(); + + /** + * The expected execution order is as follows + * 1. DataAAdapter executes first and its dependency on DataB is resolved via implicit binding + * 2. This is followed by Validator1's execution since DataA is available + * 3. This is followed by DataBDataAdapter's execution, where DataA is injected from #1 + * 4. In #3, a new instance of DataB is emitted, and since DataADataAdapter's injection of DataB is listening for mutations + * this changed DataB causes a re-trigger of DataADataAdapter for whoever + * 5. Validator2 executes with a new value of DataA + */ + + List executionOrder = listener.getExecutionOrder(); + + Integer idx = 0; + + // DataADataAdapter starts execution (both pre and post) + idx = assertExecutionOrder(executionOrder, idx, DataADataAdapter.class); + + // Validator1 starts execution + assertEquals(ExecutionStage.PRE, executionOrder.get(idx).stage); + assertEquals(Validator1.class, executionOrder.get(idx++).bizlogic); + + // Validator1 injects DataA and causes DataADataAdapter's pre/post to get executed (returns data from cache) + idx = assertExecutionOrder(executionOrder, idx, DataADataAdapter.class); + + // Validator1 completes execution + assertEquals(ExecutionStage.POST, executionOrder.get(idx).stage); + assertEquals(Validator1.class, executionOrder.get(idx++).bizlogic); + + // DataBDataAdapter begins execution + assertEquals(ExecutionStage.PRE, executionOrder.get(idx).stage); + assertEquals(DataBDataAdapter.class, executionOrder.get(idx++).bizlogic); + + // DataBDataAdapter injects DataA and causes DataADataAdapter's pre/post to get executed (invalidates cache and triggers adapt method) + idx = assertExecutionOrder(executionOrder, idx, DataADataAdapter.class); + + // DataBDataAdapter completes execution + assertEquals(ExecutionStage.POST, executionOrder.get(idx).stage); + assertEquals(DataBDataAdapter.class, executionOrder.get(idx++).bizlogic); + + // Validator2 starts execution + assertEquals(ExecutionStage.PRE, executionOrder.get(idx).stage); + assertEquals(Validator2.class, executionOrder.get(idx++).bizlogic); + + // Validator2 injects DataA and causes DataADataAdapter's pre/post to get executed (returns data from cache) + idx = assertExecutionOrder(executionOrder, idx, DataADataAdapter.class); + + // Validator2 completes execution + assertEquals(ExecutionStage.POST, executionOrder.get(idx).stage); + assertEquals(Validator2.class, executionOrder.get(idx++).bizlogic); + } + + private Integer assertExecutionOrder(List executionOrder, Integer idx, Class executed) { + assertEquals(ExecutionStage.PRE, executionOrder.get(idx).stage); + assertEquals(executed, executionOrder.get(idx++).bizlogic); + assertEquals(ExecutionStage.POST, executionOrder.get(idx).stage); + assertEquals(executed, executionOrder.get(idx++).bizlogic); + return idx; + } + + public static class DataA { + int data = 0; + + public DataA(int data) { + this.data = data; + } + } + + public static class DataB { + + } + + public static class DataADataAdapter extends DataAdapterBizlogic { + + // Whenever DataB changes, this adapter will be force-invoked to get a fresh value of DataA + // At the first invocation, DataB is present in the DataContext + @InjectData(mutable = true) + private DataB dataB; + + @Override + public DataA adapt(TefContext tefContext) { + return dataToReturn.get(dataIndex++); + } + } + + public static class DataBDataAdapter extends DataAdapterBizlogic { + + @InjectData + private DataA dataA; + + // This bizlogic modifies the DataB (that was present in DataContext) + // hence triggering the invalidation of DataA. + @Override + public DataB adapt(TefContext tefContext) { + assertEquals(1, dataA.data); + return new DataB(); + } + } + + public static class Validator1 implements IBizlogic { + + // When DataA is injected here, it is right after the first invocation + @InjectData + private DataA dataA; + + @Override + public void execute(TefContext tefContext) { + assertEquals(1, dataA.data); + } + } + + /** + * In the ideal world, Data Dependency should come via Data injections, and not via control dependency. + * The code here is just to ensure a specific order. + */ + @DependsOn(DataBDataAdapter.class) + public static class Validator2 implements IBizlogic { + + // When DataA is injected here, DataAdapter for DataA is re-triggered. + @InjectData + private DataA dataA; + + @Override + public void execute(TefContext tefContext) { + assertEquals(2, dataA.data); + } + } +} diff --git a/tef-impl/src/test/java/flipkart/tef/execution/FlowExecutorTest.java b/tef-impl/src/test/java/flipkart/tef/execution/FlowExecutorTest.java new file mode 100644 index 0000000..433f627 --- /dev/null +++ b/tef-impl/src/test/java/flipkart/tef/execution/FlowExecutorTest.java @@ -0,0 +1,395 @@ +/* + * 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.execution; + +import flipkart.tef.TestTefContext; +import flipkart.tef.annotations.InjectData; +import flipkart.tef.bizlogics.BasicEnrichmentBizlogic; +import flipkart.tef.bizlogics.DataAdapterBizlogic; +import flipkart.tef.bizlogics.DataAdapterResult; +import flipkart.tef.bizlogics.IBizlogic; +import flipkart.tef.bizlogics.TefContext; +import flipkart.tef.capability.model.EnrichmentResultData; +import flipkart.tef.capability.model.MapBaseData; +import flipkart.tef.exception.TefExecutionException; +import flipkart.tef.flow.SimpleFlow; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.Collection; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class FlowExecutorTest { + + private FlowBuilder flowBuilder; + + @BeforeClass + public static void setup() { + + } + + @Before + public void setUp() { + flowBuilder = new FlowBuilder(); + } + + @Test + public void testBasicFlowExecution() throws IllegalAccessException, DataDependencyException, InstantiationException, TefExecutionException { + Class adapterClass = SampleDataAdapter.class; + Class enricherClass = SampleEnrichmentBizlogic.class; + + SampleDataAdapter.ADAPTED = new SampleData(); + + flowBuilder.add(adapterClass); + flowBuilder.add(enricherClass); + + assertEquals(2, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(enricherClass)); + assertTrue(flowBuilder.getBizlogics().contains(adapterClass)); + + SimpleFlow flow = flowBuilder.build(); + DataContext dataContext = new DataContext(); + FlowExecutor executor = new FlowExecutor(flow, dataContext, new TestTefContext()); + + MyFlowExecutionListener listener = new MyFlowExecutionListener(); + executor.addListener(listener); + executor.execute(); + List executionOrder = listener.getExecutionOrder(); + + int idx = 0; + + idx = assertExecutionOrder(executionOrder, idx, adapterClass); + + assertEquals(executionOrder.get(idx).stage, ExecutionStage.PRE); + assertEquals(executionOrder.get(idx++).bizlogic, enricherClass); + + idx = assertExecutionOrder(executionOrder, idx, adapterClass); + + assertEquals(executionOrder.get(idx).bizlogic, enricherClass); + assertEquals(executionOrder.get(idx).stage, ExecutionStage.POST); + } + + private Integer assertExecutionOrder(List executionOrder, Integer idx, Class executed) { + assertEquals(ExecutionStage.PRE, executionOrder.get(idx).stage); + assertEquals(executed, executionOrder.get(idx++).bizlogic); + + assertEquals(ExecutionStage.POST, executionOrder.get(idx).stage); + assertEquals(executed, executionOrder.get(idx++).bizlogic); + return idx; + } + + @Test + public void testFlowExecutionWithNullData() throws IllegalAccessException, DataDependencyException, InstantiationException, TefExecutionException { + Class adapterClass = SampleDataAdapter.class; + Class enricherClass = SampleEnrichmentBizlogic.class; + + SampleDataAdapter.ADAPTED = null; + + flowBuilder.add(adapterClass); + flowBuilder.add(enricherClass); + + assertEquals(2, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(enricherClass)); + assertTrue(flowBuilder.getBizlogics().contains(adapterClass)); + + SimpleFlow flow = flowBuilder.build(); + List> order = flow.getBizlogics(); + assertEquals(adapterClass, order.get(0)); + assertEquals(enricherClass, order.get(1)); + + FlowExecutor executor = new FlowExecutor(flow, new DataContext(), new TestTefContext()); + MyFlowExecutionListener listener = new MyFlowExecutionListener(); + executor.addListener(listener); + List executionOrder = listener.getExecutionOrder(); + + try { + executor.execute(); + } catch (DataDependencyException e) { + assertEquals("Injectable Data sampleData cannot be null in flipkart.tef.execution.FlowExecutorTest$SampleEnrichmentBizlogic", e.getMessage()); + } + + int idx = 0; + + idx = assertExecutionOrder(executionOrder, idx, adapterClass); + + assertEquals(executionOrder.get(idx).stage, ExecutionStage.PRE); + assertEquals(executionOrder.get(idx++).bizlogic, enricherClass); + assertExecutionOrder(executionOrder, idx, adapterClass); + } + + @Test + public void testFlowExecutionWithValidNullData() throws IllegalAccessException, DataDependencyException, InstantiationException, TefExecutionException { + Class adapterClass = SampleDataAdapter.class; + Class enricherClass = SampleEnrichmentBizlogic2.class; + + SampleDataAdapter.ADAPTED = null; + flowBuilder.add(adapterClass); + flowBuilder.add(enricherClass); + + assertEquals(2, flowBuilder.getBizlogics().size()); + assertTrue(flowBuilder.getBizlogics().contains(enricherClass)); + assertTrue(flowBuilder.getBizlogics().contains(adapterClass)); + + SimpleFlow flow = flowBuilder.build(); + List> order = flow.getBizlogics(); + assertEquals(adapterClass, order.get(0)); + assertEquals(enricherClass, order.get(1)); + + DataContext dataContext = new DataContext(); + FlowExecutor executor = new FlowExecutor(flow, dataContext, new TestTefContext()); + + MyFlowExecutionListener listener = new MyFlowExecutionListener(); + executor.addListener(listener); + executor.execute(); + List executionOrder = listener.getExecutionOrder(); + + int idx = 0; + + idx = assertExecutionOrder(executionOrder, idx, adapterClass); + + assertEquals(executionOrder.get(idx).stage, ExecutionStage.PRE); + assertEquals(executionOrder.get(idx++).bizlogic, enricherClass); + assertExecutionOrder(executionOrder, idx, adapterClass); + } + + /** + * For an optional injection with no implicit binding, the value is not injected (remains null) + */ + @Test + public void testOptionalInjectionWithNoBinding() throws IllegalAccessException, DataDependencyException, InstantiationException, TefExecutionException { + flowBuilder.add(OptionalInjectionBizlogic1.class); + SimpleFlow simpleFlow = flowBuilder.build(); + assertEquals(1, simpleFlow.getBizlogics().size()); + assertTrue(simpleFlow.getBizlogics().contains(OptionalInjectionBizlogic1.class)); + + DataContext dataContext = new DataContext(); + FlowExecutor executor = new FlowExecutor(simpleFlow, dataContext, new TestTefContext()); + executor.execute(); + } + + /** + * For an optional injection data is injected from the implicit data binding if its available. + */ + @Test + public void testOptionalInjectionWithImplicitBinding() throws IllegalAccessException, DataDependencyException, InstantiationException, TefExecutionException { + flowBuilder.add(OptionalInjectionBizlogic2.class); + SimpleFlow simpleFlow = flowBuilder.build(); + assertEquals(1, simpleFlow.getBizlogics().size()); + assertTrue(simpleFlow.getBizlogics().contains(OptionalInjectionBizlogic2.class)); + + DataContext dataContext = new DataContext(); + dataContext.put(new DataAdapterResult(new OptionalInjectedData())); + FlowExecutor executor = new FlowExecutor(simpleFlow, dataContext, new TestTefContext()); + executor.execute(); + } + + /** + * For an optional injection Data adapter is invoked if its available in the flow. + */ + @Test + public void testOptionalInjectionWithDataAdapter() throws IllegalAccessException, DataDependencyException, InstantiationException, TefExecutionException { + flowBuilder.add(OptionalInjectionBizlogic2.class); + flowBuilder.add(OptionalInjectedDataAdapter.class); + SimpleFlow simpleFlow = flowBuilder.build(); + assertEquals(2, simpleFlow.getBizlogics().size()); + assertTrue(simpleFlow.getBizlogics().contains(OptionalInjectionBizlogic2.class)); + assertTrue(simpleFlow.getBizlogics().contains(OptionalInjectedDataAdapter.class)); + + MyFlowExecutionListener listener = new MyFlowExecutionListener(); + FlowExecutor executor = new FlowExecutor(simpleFlow, new DataContext(), new TestTefContext()); + executor.addListener(listener); + executor.execute(); + + List executionOrder = listener.getExecutionOrder(); + + int idx = 0; + + idx = assertExecutionOrder(executionOrder, idx, OptionalInjectedDataAdapter.class); + assertEquals(executionOrder.get(idx).stage, ExecutionStage.PRE); + assertEquals(executionOrder.get(idx++).bizlogic, OptionalInjectionBizlogic2.class); + idx = assertExecutionOrder(executionOrder, idx, OptionalInjectedDataAdapter.class); + assertEquals(executionOrder.get(idx).stage, ExecutionStage.POST); + assertEquals(executionOrder.get(idx++).bizlogic, OptionalInjectionBizlogic2.class); + } + + /** + * This test creates a scenario of a data adapter having a data injection, such that + * it triggers the mutation flow and validates that it has no side-effects. + * + * @throws DataDependencyException + * @throws IllegalAccessException + * @throws InstantiationException + */ + @Test + public void testDataAdapterWithDataInjection() throws DataDependencyException, IllegalAccessException, InstantiationException, TefExecutionException { + Class adapterClass = SampleDataAdapter.class; + Class adapterClass2 = SampleDataAdapter2.class; + Class bizlogic1Class = SampleBizlogic1.class; + + SampleDataAdapter.ADAPTED = new SampleData(); + + flowBuilder.add(adapterClass); + flowBuilder.add(adapterClass2); + flowBuilder.add(bizlogic1Class); + + Collection> bizlogics = flowBuilder.getBizlogics(); + assertEquals(3, bizlogics.size()); + assertTrue(bizlogics.contains(adapterClass2)); + assertTrue(bizlogics.contains(adapterClass)); + assertTrue(bizlogics.contains(bizlogic1Class)); + + SimpleFlow flow = flowBuilder.build(); + assertEquals(3, flow.getBizlogics().size()); + assertEquals(adapterClass, flow.getBizlogics().get(0)); + assertEquals(adapterClass2, flow.getBizlogics().get(1)); + assertEquals(bizlogic1Class, flow.getBizlogics().get(2)); + + DataContext dataContext = new DataContext(); + FlowExecutor executor = new FlowExecutor(flow, dataContext, new TestTefContext()); + + MyFlowExecutionListener listener = new MyFlowExecutionListener(); + executor.addListener(listener); + executor.execute(); + } + + public static class SampleData extends MapBaseData { + } + + public static class SampleData2 extends MapBaseData { + } + + public static class SampleDataAdapter extends DataAdapterBizlogic { + + public static SampleData ADAPTED = new SampleData(); + + @Override + public SampleData adapt(TefContext tefContext) { + return ADAPTED; + } + } + + public static class SampleDataAdapter2 extends DataAdapterBizlogic { + + public static SampleData2 ADAPTED = new SampleData2(); + + private static int counter = 0; + @InjectData + private SampleData sampleData; + + @Override + public SampleData2 adapt(TefContext tefContext) { + assertEquals(0, (counter++)); + return ADAPTED; + } + } + + public static class SampleBizlogic1 implements IBizlogic { + + @InjectData + private SampleData sampleData; + + @InjectData + private SampleData2 sampleData2; + + @Override + public void execute(TefContext tefContext) { + + } + } + + public static class SampleEnrichmentBizlogic extends BasicEnrichmentBizlogic { + + @InjectData + private SampleData sampleData; + + @Override + public EnrichmentResultData enrich() { + assertEquals(sampleData, SampleDataAdapter.ADAPTED); + return null; + } + + @Override + public void map(EnrichmentResultData enriched, Object o) { + + } + + @Override + public Object getTarget() { + return new Object(); + } + } + + public static class SampleEnrichmentBizlogic2 extends BasicEnrichmentBizlogic { + + @InjectData(nullable = true) + private SampleData sampleData; + + @Override + public EnrichmentResultData enrich() { + assertEquals(sampleData, SampleDataAdapter.ADAPTED); + return null; + } + + @Override + public void map(EnrichmentResultData enriched, Object o) { + + } + + @Override + public Object getTarget() { + return new Object(); + } + } + + public static class OptionalInjectionBizlogic1 implements IBizlogic { + + @InjectData(optional = true) + private OptionalInjectedData optionalInjectedData; + + @Override + public void execute(TefContext tefContext) { + assertTrue(optionalInjectedData == null); + } + } + + public static class OptionalInjectionBizlogic2 implements IBizlogic { + + @InjectData(optional = true) + private OptionalInjectedData optionalInjectedData; + + @Override + public void execute(TefContext tefContext) { + assertTrue(optionalInjectedData != null); + } + } + + public static class OptionalInjectedDataAdapter extends DataAdapterBizlogic { + + @Override + public OptionalInjectedData adapt(TefContext tefContext) { + return new OptionalInjectedData(); + } + } + + public static class OptionalInjectedData { + + } +} \ 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 new file mode 100644 index 0000000..624b0de --- /dev/null +++ b/tef-impl/src/test/java/flipkart/tef/execution/FluentCapabilityBuilderTest.java @@ -0,0 +1,204 @@ +/* + * 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.execution; + +import com.google.common.collect.ImmutableList; +import flipkart.tef.bizlogics.BasicEnrichmentBizlogic; +import flipkart.tef.bizlogics.BasicValidationBizlogic; +import flipkart.tef.bizlogics.DataAdapterBizlogic; +import flipkart.tef.bizlogics.IBizlogic; +import flipkart.tef.bizlogics.TefContext; +import flipkart.tef.capability.BizlogicDependency; +import flipkart.tef.capability.CapabilityDefinition; +import flipkart.tef.capability.EmptyCapabilityDefinition; +import flipkart.tef.capability.model.EnrichmentResultData; +import flipkart.tef.capability.model.ValidationResultData; +import flipkart.tef.flow.SimpleFlow; +import org.junit.BeforeClass; +import org.junit.Test; + +import java.util.Collections; +import java.util.List; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; + +public class FluentCapabilityBuilderTest { + + @BeforeClass + public static void setup() { + + } + + @Test + public void testNullCapability() throws Exception { + CapabilityDefinition capabilityDefinition = new CapabilityDefinition() { + + @Override + public String name() { + return null; + } + + @Override + public List dependentCapabilities() { + return null; + } + + @Override + public List> validators() { + return null; + } + + @Override + public List> enrichers() { + return null; + } + + @Override + public List> adapters() { + return null; + } + + @Override + public List> exclusions() { + return null; + } + + @Override + public List bizlogicDependencies() { + return null; + } + }; + FluentCapabilityBuilder manager = new FluentCapabilityBuilder(); + try { + manager.withCapability(capabilityDefinition).dataflow(); + assertTrue("Exception should have been thrown", true); + } catch (IllegalArgumentException e) { + assertEquals(FlowBuilder.Messages.COULD_NOT_DEDUCE_THE_STARTING_STEP, e.getMessage()); + } + } + + @Test + public void testEmptyCapability() throws Exception { + CapabilityDefinition capabilityDefinition = new EmptyCapabilityDefinition() { + @Override + public String name() { + return "c1"; + } + }; + + FluentCapabilityBuilder manager = new FluentCapabilityBuilder(); + try { + manager.withCapability(capabilityDefinition).dataflow(); + assertTrue("Exception should have been thrown", true); + } catch (IllegalArgumentException e) { + assertEquals(FlowBuilder.Messages.COULD_NOT_DEDUCE_THE_STARTING_STEP, e.getMessage()); + } + } + + @Test + public void testBasicCapability() throws Exception { + + CapabilityDefinition capabilityDefinition = new EmptyCapabilityDefinition() { + @Override + public String name() { + return "C1"; + } + + @Override + public List dependentCapabilities() { + return Collections.emptyList(); + } + + @Override + public List> validators() { + return ImmutableList.of(BasicValidationBizlogic1.class); + } + + @Override + public List> enrichers() { + return ImmutableList.of(BasicEnrichmentBizlogic1.class); + } + + @Override + public List> adapters() { + return ImmutableList.of(DataAdapterBizlogic1.class); + } + + @Override + public List> exclusions() { + return Collections.emptyList(); + } + }; + + FluentCapabilityBuilder manager = new FluentCapabilityBuilder(); + SimpleFlow flow = manager.withCapability(capabilityDefinition).dataflow(); + + assertNotNull(flow); + + assertEquals(3, flow.getBizlogics().size()); + assertTrue(flow.getBizlogics().contains(BasicValidationBizlogic1.class)); + assertTrue(flow.getBizlogics().contains(BasicEnrichmentBizlogic1.class)); + assertTrue(flow.getBizlogics().contains(DataAdapterBizlogic1.class)); + + } + + class BasicValidationBizlogic1 extends BasicValidationBizlogic { + + @Override + protected void applyValidationResult(ValidationResultData result, Object o) { + + } + + @Override + public ValidationResultData validate() { + return null; + } + + @Override + public Object getTarget() { + return null; + } + } + + class BasicEnrichmentBizlogic1 extends BasicEnrichmentBizlogic { + + @Override + public EnrichmentResultData enrich() { + return null; + } + + @Override + public void map(EnrichmentResultData enriched, Object o) { + + } + + @Override + public Object getTarget() { + return null; + } + } + + class DataAdapterBizlogic1 extends DataAdapterBizlogic { + + @Override + public Object adapt(TefContext tefContext) { + return null; + } + } +} \ No newline at end of file diff --git a/tef-impl/src/test/java/flipkart/tef/execution/MyFlowExecutionListener.java b/tef-impl/src/test/java/flipkart/tef/execution/MyFlowExecutionListener.java new file mode 100644 index 0000000..e46e7d8 --- /dev/null +++ b/tef-impl/src/test/java/flipkart/tef/execution/MyFlowExecutionListener.java @@ -0,0 +1,47 @@ +/* + * 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. + */ + +/** + * + * Date: 09/07/20 + * Time: 3:02 PM + */ +package flipkart.tef.execution; + +import flipkart.tef.FlowExecutionListener; +import flipkart.tef.bizlogics.IBizlogic; + +import java.util.ArrayList; +import java.util.List; + +public class MyFlowExecutionListener implements FlowExecutionListener { + + List executionOrder = new ArrayList<>(); + + @Override + public void pre(IBizlogic bizlogic) { + executionOrder.add(new ExecutionStep(bizlogic.getClass(), ExecutionStage.PRE)); + } + + @Override + public void post(IBizlogic bizlogic) { + executionOrder.add(new ExecutionStep(bizlogic.getClass(), ExecutionStage.POST)); + } + + public List getExecutionOrder() { + return executionOrder; + } +} From 85eda141d37d357d40b708b1bb6cc70d8c0e0430 Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Wed, 19 Jan 2022 13:19:12 +0530 Subject: [PATCH 2/3] Creating mavel.yml action To test and build via github action --- .github/workflows/maven.yml | 26 ++++++++++++++++++++++++++ 1 file changed, 26 insertions(+) create mode 100644 .github/workflows/maven.yml diff --git a/.github/workflows/maven.yml b/.github/workflows/maven.yml new file mode 100644 index 0000000..420016c --- /dev/null +++ b/.github/workflows/maven.yml @@ -0,0 +1,26 @@ +# This workflow will build a Java project with Maven, and cache/restore any dependencies to improve the workflow execution time +# For more information see: https://help.github.com/actions/language-and-framework-guides/building-and-testing-java-with-maven + +name: Java CI with Maven + +on: + push: + branches: [ main ] + pull_request: + branches: [ main ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up JDK 8 + uses: actions/setup-java@v2 + with: + java-version: '8' + distribution: 'temurin' + cache: maven + - name: Build with Maven + run: mvn -B package --file pom.xml From c7a70f0d12f0ec1260aede9f5bf2d7ae550247dd Mon Sep 17 00:00:00 2001 From: bageshwar <2353296+bageshwar@users.noreply.github.com> Date: Wed, 19 Jan 2022 15:49:50 +0530 Subject: [PATCH 3/3] POM update. --- pom.xml | 20 ++++++++++++++++++++ tef-core/pom.xml | 2 +- tef-impl/pom.xml | 2 +- 3 files changed, 22 insertions(+), 2 deletions(-) diff --git a/pom.xml b/pom.xml index 238998a..48220dd 100644 --- a/pom.xml +++ b/pom.xml @@ -42,6 +42,26 @@ 1.8 + + + org.codehaus.mojo + flatten-maven-plugin + 1.0.1 + + true + target + bom + + + + flatten + process-resources + + flatten + + + + diff --git a/tef-core/pom.xml b/tef-core/pom.xml index 8941dca..982e4c9 100644 --- a/tef-core/pom.xml +++ b/tef-core/pom.xml @@ -21,7 +21,7 @@ flipkart.tef tef - 0.0.1 + ${revision} 4.0.0 diff --git a/tef-impl/pom.xml b/tef-impl/pom.xml index 616a7b2..dfbf34f 100644 --- a/tef-impl/pom.xml +++ b/tef-impl/pom.xml @@ -21,7 +21,7 @@ flipkart.tef tef - 0.0.1 + ${revision} 4.0.0