diff --git a/.github/workflows/codeql.yml b/.github/workflows/codeql.yml new file mode 100644 index 000000000..f2a50edc1 --- /dev/null +++ b/.github/workflows/codeql.yml @@ -0,0 +1,77 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ "master" ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ "master" ] + schedule: + - cron: '28 14 * * 2' + +jobs: + analyze: + name: Analyze + runs-on: ${{ (matrix.language == 'swift' && 'macos-latest') || 'ubuntu-latest' }} + timeout-minutes: ${{ (matrix.language == 'swift' && 120) || 360 }} + permissions: + actions: read + contents: read + security-events: write + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python', 'ruby', 'swift' ] + # Use only 'java' to analyze code written in Java, Kotlin or both + # Use only 'javascript' to analyze code written in JavaScript, TypeScript or both + # Learn more about CodeQL language support at https://aka.ms/codeql-docs/language-support + + steps: + - name: Checkout repository + uses: actions/checkout@v3 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v2 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + + # For more details on CodeQL's query packs, refer to: https://docs.github.com/en/code-security/code-scanning/automatically-scanning-your-code-for-vulnerabilities-and-errors/configuring-code-scanning#using-queries-in-ql-packs + # queries: security-extended,security-and-quality + + + # Autobuild attempts to build any compiled languages (C/C++, C#, Go, Java, or Swift). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v2 + + # ℹī¸ Command-line programs to run using the OS shell. + # 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun + + # If the Autobuild fails above, remove it and uncomment the following three lines. + # modify them (or add more) to build your code if your project, please refer to the EXAMPLE below for guidance. + + # - run: | + # echo "Run, Build Application using script" + # ./location_of_script_within_repo/buildscript.sh + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v2 + with: + category: "/language:${{matrix.language}}" diff --git a/README.md b/README.md index 108cf73bd..ed39c762d 100644 --- a/README.md +++ b/README.md @@ -7,67 +7,124 @@ [![Github build](https://github.com/v12technology/fluxtion/workflows/MavenCI/badge.svg)](https://github.com/v12technology/fluxtion/actions) [![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.fluxtion/runtime/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.fluxtion/runtime) - -# Lightweight event stream processor -- Pure java in memory complex event processing -- Ultra fast [sub-microsecond response times](http://fluxtion.com/solutions/high-performance-flight-analysis/) -- Ahead of time compiler for fast startup and easy embedding - -# Introduction -Thanks for dropping by, hope we can persuade you to donate your time to investigate Fluxtion further. - -Fluxtion is a fully featured java based event stream processor that brings real-time data processing -inside your application. If you need to build applications that react to complex events and make -fast decisions then Fluxtion is for you. We build stream processing logic free from any messaging -layer. - -Whether you need to process tens of millions of events per -second or write complex rule driven applications that make decisions in microseconds Fluxtion can help. -Built to embed within an applications, invoking user functions as well as publishing data results. - -Uniquely among stream processors Fluxtion employs ahead of time compilation to create a stream processing engine. -Describe your processing and Fluxtion tailors a solution to your needs at build time. -Ahead of time compilation offers several critical advantages over existing products, -- Faster startup times for your application, perfect for serverless architectures -- No vendor lock-in, the engine can be used within any java application -- Compiler optimized code gives higher performance and lower running costs -- Generated source code simplifies debugging and maintenance - -# Uses -- Real-time analytics and processing -- ETL -- Rules engines -- Low response time requirements -- IoT processing - -## Fluxtion application integration -![](docs/images/integration-overview.png) - -A Fluxtion event processor embeds within a user application, processing events, -publishing events to sinks or interacting with user classes. Events are feed from -the application directly into the processor or into a pipeline. A pipeline provides -additional capabilities such as threading, scheduling, auditing, access control - -## Philosophy +# [USER DOCUMENTATION](https://v12technology.github.io/fluxtion/) + +# Fluxtion is event driven Java + +Fluxtion is a java development productivity tool that makes writing and maintaining event driven business logic cheaper +and quicker. The Fluxtion dependency injection container exposes user beans as event driven service endpoints. A +container instance can be connected to any event delivery system freeing the business logic from messaging vendor lock-in. + +**Fluxtion minimises the cost of developing and maintaining event driven business logic** + +Developers concentrate on developing and extending business logic, dependency injection and realtime event dispatch is +handled by the container. The container supports: + +
+
+
+
    +
  • Streaming event processing
  • +
  • AOT compilation for fast start
  • +
  • Spring integration
  • +
+
+
+
+
+
    +
  • Low latency microsecond response
  • +
  • Event sourcing compatible
  • +
  • Functional and imperative construction
  • +
+
+
+
+ +# Top level components +There are two major components provided by Fluxtion the developer uses to build event driven logic. + +## Compiler +The compiler analyses the configuration information provided by the programmer and builds a dependency injection container +that houses all the user components or beans combined with pre-calculated event dispatch. Outputs from the compiler +are either +- In memory di container running in an interpreted mode +- A container generated ahead of time and serialised to code + +## Runtime +The runtime provides the dependency injection container with a core set of libraries required at runtime. An AOT generated +container only requires the runtime to function, no compiler libraries are required. + +# Philosophy Our philosophy is to make delivering streaming applications in java simple by employing a -clean modern api similar to the familiar Java streams api. The Fluxtion compiler carries the +clean modern api that requires very little integration effort. The Fluxtion compiler carries the burden of generating simple efficient code that is optimised for your specific application. We pay the cost at compile time only once, so every execution of your stream processor sees benefits in reduced startup time and smaller running costs. -Why concentrate solely on the processing logic? There are many great messaging systems -out there offering scale out to hundreds of millions of events per second. But many reactive -applications do not need that scale, the problem is integrating the event streams from -different messaging systems into a single decision making engine. In cases like these -you want to concentrate on writing the logic. +# The cost of complexity problem + +Increasing system complexity makes delivery of new features expensive and time-consuming to deliver. Efficiently managing +complexity reduces both operational costs and time to market for new functionality, critical for a business to remain +profitable in a competitive environment. + +Event driven systems have two types of complexity to manage: + +- Delivering events to application components in a fault-tolerant predictable fashion. +- Developing application logic responses to events that meets business requirements + +Initially all the project complexity centres on the event delivery system, but over time this system becomes stable and +the complexity demands are minimal. Pre-packaged event delivery systems are a common solution to control complexity and +cost of event distribution. The opposite is true for event driven application logic, functional requirements increase +over time and developing application logic becomes ever more complex and expensive to deliver. + +**Fluxtion combines dependency injection and event dispatch increasing developer productivity** + +# Combining dependency injection and event processing + +The introduction of dependency injection gave developers a consistent approach to linking application components. +Fluxtion extends dependency injection to support container managed event driven beans. Extending a familiar development +pattern has the following benefits: +- Shallow learning curve for developers to use Fluxtion effectively +- Consistent programming model for event driven logic increases developer productivity +- Re-use of industrial quality and predictable event dispatch model + +**Fluxtion's familiar dependency injection programming model simplifies integration** + +## Dependency injection container + +Fluxtion builds a dependency injection container from configuration information given by the programmer. Functions +supported by the container include: creating instances, injecting references between beans, setting properties, calling +lifecycle methods, factory methods, singleton injection, named references, constructor and setter injection. +Configuration data can be programmatic, spring xml config, yaml or custom data format. -## Example -We have a five minute tutorial to dive into [here](https://github.com/v12technology/fluxtion-quickstart/tree/master). +There are three options for building a container: -The sample below demonstrates the fluent functional api Fluxtion provides to describe data processing logic. The api -should be familiar to anyone who has coded with java 8 streams. +- Interpreted - built and run in process, uses dynamic dispatch can handle millions of nodes +- Compiled - static analysis, code generated and compiled in process. handles thousands of nodes +- Compiled AOT - code generated at build time, zero cost start time when deployed + +Fluxtion DI containers are very lightweight and designed to be run within an application. Multiple containers can be +used within a single application each container providing specialised business processing logic. + +## Automatic event dispatch + +The container exposes event consumer end-points, routing events as methods calls to beans within the container +via an internal dispatcher. The internal dispatcher propagates event notification through the object graph. + +Fluxtion leverages the familiar dependency injection workflow for constructing the object graph. Annotated +event handler and trigger methods are dispatch targets. When building a container Fluxtion uses the annotations to +calculate the dispatch call trees for the internal dispatcher. A bean can export multiple service interfaces or just a +single method. For exported interfaces the container generates proxies that routes calls from the proxy handler methods +to the container's dispatcher. + + + + +## Code sample +Fluxtion supports both imperative service style and functional patterns. Below is an example of functional coding style +that adds two numbers from independent data streams and logs when the sum is greater than 100. -### Code sample ```java /** * Simple Fluxtion hello world stream example. Add two numbers and log when sum > 100 @@ -111,7 +168,7 @@ public class HelloWorld { } ``` -### Execution output +## Execution output ```text rcvd -> Data1[value=20.5] rcvd -> Data2[value=63.0] @@ -122,32 +179,10 @@ Process finished with exit code 0 ``` -## Highlights -### Ahead of time compiler -Fluxtion constructs a model of the stream processor and generates a set of java classes -that meet the requirement. The compiled code is highly optimised for memory and cpu. Small, -compact and jit friendly flxution stream processors get the best out of the JVM, giving -unbeatable performance. -### Pipeline vs graph processing -Fluxtion is built as a graph processor and not a pipeline. A pipeline has a single entry -point and single execution path, a graph processor has multiple entry points multiple execution -paths. Handling heterogeneous event types in a unique fashion is the default behaviour. -In fact the more complex the problem the greater the advantage that Fluxtion displays. -### Integrating with client code -Traditional stream processors have an ingest, transform and publish cycle. When moving -from analytics to actually taking actions there is a barrier to integrating the output -with the client application. With Fluxtion client code is integrated into the generated -processor and invoked directly. -### Describing a processor -Fluxtion constructs an intermediate representation for the ahead of time compiler to process. -The intermediate representation can be built from a variety of forms each with their -own advantages. The following descriptions are supported: -- Declarative or DSL -- Imperative -- Data driven -- Dependency injection based - -## Contributing + + + +# Contributing We welcome contributions to the project. Detailed information on our ways of working will be written in time. In brief our goals are: @@ -155,7 +190,7 @@ be written in time. In brief our goals are: * Author a change with suitabke test case and documentation. * Push your changes to a fork. * Submit a pull request. -## License +# License Fluxtion is licensed under the [Server Side Public License](https://www.mongodb.com/licensing/server-side-public-license). This license is created by MongoDb, for further info see [FAQ](https://www.mongodb.com/licensing/server-side-public-license/faq) and comparison with [AGPL v3.0](https://www.mongodb.com/licensing/server-side-public-license/faq). diff --git a/compiler/lombok.config b/compiler/lombok.config new file mode 100644 index 000000000..f919ef505 --- /dev/null +++ b/compiler/lombok.config @@ -0,0 +1,2 @@ +config.stopBubbling = true +lombok.copyableAnnotations += com.fluxtion.runtime.annotations.builder.AssignToField \ No newline at end of file diff --git a/compiler/pom.xml b/compiler/pom.xml index 04631cc0a..fbdac11fc 100644 --- a/compiler/pom.xml +++ b/compiler/pom.xml @@ -19,13 +19,17 @@ Copyright (C) 2018 V12 Technology Ltd. com.fluxtion root-parent-pom - 9.0.1 + 9.1.13-SNAPSHOT ../parent-root/pom.xml compiler fluxtion :: compiler - + + + 5.3.29 + + @@ -82,6 +86,12 @@ Copyright (C) 2018 V12 Technology Ltd. + + org.springframework + spring-context + ${org.springframework.version} + provided + com.fluxtion.csv-compiler csv-compiler @@ -164,6 +174,10 @@ Copyright (C) 2018 V12 Technology Ltd. org.apache.commons commons-lang3 + + org.apache.commons + commons-text + com.google.googlejavaformat google-java-format diff --git a/compiler/src/main/java/com/fluxtion/compiler/EventProcessorConfig.java b/compiler/src/main/java/com/fluxtion/compiler/EventProcessorConfig.java index 3180d004a..c5b2fe5c4 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/EventProcessorConfig.java +++ b/compiler/src/main/java/com/fluxtion/compiler/EventProcessorConfig.java @@ -16,28 +16,20 @@ */ package com.fluxtion.compiler; -import com.fluxtion.compiler.builder.callback.CallBackDispatcherFactory; -import com.fluxtion.compiler.builder.callback.CallbackNodeFactory; -import com.fluxtion.compiler.builder.callback.DirtyStateMonitorFactory; -import com.fluxtion.compiler.builder.callback.EventDispatcherFactory; -import com.fluxtion.compiler.builder.callback.EventProcessorCallbackInternalFactory; +import com.fluxtion.compiler.builder.callback.*; import com.fluxtion.compiler.builder.context.EventProcessorContextFactory; import com.fluxtion.compiler.builder.context.InstanceSupplierFactory; -import com.fluxtion.compiler.builder.factory.NodeFactory; -import com.fluxtion.compiler.builder.factory.NodeFactoryRegistration; -import com.fluxtion.compiler.builder.factory.NodeNameLookupFactory; -import com.fluxtion.compiler.builder.factory.NodeNameProducer; -import com.fluxtion.compiler.builder.factory.SingletonNodeFactory; +import com.fluxtion.compiler.builder.factory.*; import com.fluxtion.compiler.builder.filter.EventHandlerFilterOverride; import com.fluxtion.compiler.builder.input.SubscriptionManagerFactory; +import com.fluxtion.compiler.builder.output.SinkPublisherFactory; import com.fluxtion.compiler.builder.time.ClockFactory; -import com.fluxtion.compiler.generation.serialiser.FieldContext; -import com.fluxtion.compiler.generation.serialiser.FormatSerializer; -import com.fluxtion.compiler.generation.serialiser.IoSerializer; -import com.fluxtion.compiler.generation.serialiser.TimeSerializer; +import com.fluxtion.compiler.generation.serialiser.*; import com.fluxtion.runtime.audit.Auditor; import com.fluxtion.runtime.audit.EventLogControlEvent.LogLevel; import com.fluxtion.runtime.audit.EventLogManager; +import com.fluxtion.runtime.dataflow.function.MergeProperty; +import com.fluxtion.runtime.partition.LambdaReflection; import com.fluxtion.runtime.time.Clock; import lombok.ToString; @@ -49,23 +41,8 @@ import java.text.DecimalFormat; import java.text.NumberFormat; import java.text.SimpleDateFormat; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.Period; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Date; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.time.*; +import java.util.*; import java.util.function.Function; /** @@ -78,7 +55,7 @@ public class EventProcessorConfig { private final Set> interfaces = new HashSet<>(); - private final Clock clock = ClockFactory.SINGLETON; + private final Clock clock = Clock.DEFAULT_CLOCK; private final Map class2replace = new HashMap<>(); private final Map filterMap = new HashMap<>(); private final Map, Function> classSerializerMap = new HashMap<>(); @@ -96,7 +73,28 @@ public class EventProcessorConfig { private List compilerOptions = new ArrayList<>(); public EventProcessorConfig() { + clock(); this.nodeFactoryRegistration = new NodeFactoryRegistration(NodeFactoryConfig.required.getFactoryClasses()); + classSerializerMap.put(String.class, BasicTypeSerializer::stringToSource); + classSerializerMap.put(Character.class, BasicTypeSerializer::charToSource); + classSerializerMap.put(char.class, BasicTypeSerializer::charToSource); + classSerializerMap.put(Long.class, BasicTypeSerializer::longToSource); + classSerializerMap.put(long.class, BasicTypeSerializer::longToSource); + classSerializerMap.put(int.class, BasicTypeSerializer::intToSource); + classSerializerMap.put(Integer.class, BasicTypeSerializer::intToSource); + classSerializerMap.put(Short.class, BasicTypeSerializer::shortToSource); + classSerializerMap.put(short.class, BasicTypeSerializer::shortToSource); + classSerializerMap.put(Byte.class, BasicTypeSerializer::byteToSource); + classSerializerMap.put(byte.class, BasicTypeSerializer::byteToSource); + classSerializerMap.put(Double.class, BasicTypeSerializer::doubleToSource); + classSerializerMap.put(double.class, BasicTypeSerializer::doubleToSource); + classSerializerMap.put(Float.class, BasicTypeSerializer::floatToSource); + classSerializerMap.put(float.class, BasicTypeSerializer::floatToSource); + classSerializerMap.put(Boolean.class, BasicTypeSerializer::booleanToSource); + classSerializerMap.put(boolean.class, BasicTypeSerializer::booleanToSource); + classSerializerMap.put(Map.class, CollectionSerializer::mapToSource); + classSerializerMap.put(List.class, CollectionSerializer::listToSource); + classSerializerMap.put(Set.class, CollectionSerializer::setToSource); classSerializerMap.put(Duration.class, TimeSerializer::durationToSource); classSerializerMap.put(Instant.class, TimeSerializer::instantToSource); classSerializerMap.put(LocalDate.class, TimeSerializer::localDateToSource); @@ -114,6 +112,9 @@ public EventProcessorConfig() { classSerializerMap.put(DateFormat.class, FormatSerializer::simpleDataFormatToSource); classSerializerMap.put(DecimalFormat.class, FormatSerializer::decimalFormatToSource); classSerializerMap.put(NumberFormat.class, FormatSerializer::decimalFormatToSource); + classSerializerMap.put(Class.class, MetaSerializer::classToSource); + classSerializerMap.put(MergeProperty.class, MetaSerializer::mergePropertyToSource); + classSerializerMap.put(LambdaReflection.MethodReferenceReflection.class, MetaSerializer::methodReferenceToSource); } /** @@ -139,7 +140,8 @@ public T addNode(T node) { return (T) getNodeList().get(getNodeList().indexOf(node)); } - public void addNode(Object... nodeList) { + public void addNode(Object node, Object... nodeList) { + addNode(node); Arrays.asList(nodeList).forEach(this::addNode); } @@ -231,6 +233,13 @@ public void addEventAudit(LogLevel tracingLogLevel) { addAuditor(new EventLogManager().tracingOn(tracingLogLevel), EventLogManager.NODE_NAME); } + /** + * Add an {@link EventLogManager} auditor to the generated SEP without method tracing + */ + public void addEventAudit() { + addAuditor(new EventLogManager().tracingOff(), EventLogManager.NODE_NAME); + } + public void addEventAudit(LogLevel tracingLogLevel, boolean printEventToString) { addEventAudit(tracingLogLevel, printEventToString, true); } @@ -290,6 +299,15 @@ public HashMap getPublicNodes() { return publicNodes; } + public T getNode(String name) { + Object[] obj = new Object[1]; + publicNodes.entrySet().stream() + .filter(e -> e.getValue().equals(name)) + .findFirst() + .ifPresent(e -> obj[0] = e.getKey()); + return (T) obj[0]; + } + public void setPublicNodes(HashMap publicNodes) { this.publicNodes = publicNodes; } @@ -523,7 +541,8 @@ enum NodeFactoryConfig { EventProcessorCallbackInternalFactory.class, EventProcessorContextFactory.class, NodeNameLookupFactory.class, - SubscriptionManagerFactory.class + SubscriptionManagerFactory.class, + SinkPublisherFactory.class ); private final HashSet>> defaultFactories = new HashSet<>(); diff --git a/compiler/src/main/java/com/fluxtion/compiler/Fluxtion.java b/compiler/src/main/java/com/fluxtion/compiler/Fluxtion.java index 7095c9c4b..6c5568286 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/Fluxtion.java +++ b/compiler/src/main/java/com/fluxtion/compiler/Fluxtion.java @@ -1,6 +1,7 @@ package com.fluxtion.compiler; import com.fluxtion.compiler.generation.EventProcessorFactory; +import com.fluxtion.compiler.generation.RuntimeConstants; import com.fluxtion.runtime.EventProcessor; import com.fluxtion.runtime.StaticEventProcessor; import com.fluxtion.runtime.annotations.builder.Disabled; @@ -42,6 +43,14 @@ static EventProcessor compile(SerializableConsumer sepConf return EventProcessorFactory.compile(sepConfig); } + static EventProcessor compile(Object... nodes) { + return compile(c -> { + for (int i = 0; i < nodes.length; i++) { + c.addNode(nodes[i]); + } + }); + } + /** * Compiles the SEP in memory and captures the output to a user supplied {@link Writer} * @@ -71,6 +80,23 @@ static EventProcessor compileAot(SerializableConsumer cfgB return compile(cfgBuilder, compilerCfg -> compilerCfg.setPackageName(packageName)); } + static EventProcessor compileAot(String packageName, + String className, + Object... nodes) { + return compileAot(c -> { + for (int i = 0; i < nodes.length; i++) { + c.addNode(nodes[i]); + } + }, packageName, className); + } + + static EventProcessor compileAot(Object... nodes) { + return compileAot(c -> { + for (int i = 0; i < nodes.length; i++) { + c.addNode(nodes[i]); + } + }); + } @SneakyThrows static EventProcessor compileAot(SerializableConsumer cfgBuilder, @@ -101,6 +127,14 @@ static EventProcessor interpret(SerializableConsumer sepCo return EventProcessorFactory.interpreted(sepConfig, generateDescription); } + static EventProcessor interpret(Object... nodes) { + return interpret(c -> { + for (int i = 0; i < nodes.length; i++) { + c.addNode(nodes[i]); + } + }); + } + /** * Generates and compiles Java source code for a {@link StaticEventProcessor}. The compiled version only requires * the Fluxtion runtime dependencies to operate and process events. @@ -236,6 +270,7 @@ static int scanAndCompileFluxtionBuilders(File... files) { generationCount.increment(); System.out.println(generationCount.intValue() + ": invoking builder " + c.getName()); try { + final FluxtionGraphBuilder newInstance = (FluxtionGraphBuilder) c.loadClass().getDeclaredConstructor().newInstance(); compile(newInstance::buildGraph, newInstance::configureGeneration); } catch (InstantiationException | IllegalAccessException | InvocationTargetException | @@ -246,4 +281,44 @@ static int scanAndCompileFluxtionBuilders(File... files) { } return generationCount.intValue(); } + + /** + * Scans the supplied File resources for any classes that implement the {@link FluxtionGraphBuilder} interface + * and will generate an {@link EventProcessor} for any located builders. + *

+ * Any builder marked with the {@link Disabled} annotation will be ignored + *

+ * No compilations are carried out + * + * @param classLoader the classloader to be used for the generation + * @param files The locations to search for {@link FluxtionGraphBuilder} classes + * @return The number of processors generated + */ + static int scanAndGenerateFluxtionBuilders(ClassLoader classLoader, File... files) { + Objects.requireNonNull(files, "provide valid locations to search for fluxtion builders"); + System.setProperty(RuntimeConstants.FLUXTION_NO_COMPILE, "true"); + LongAdder generationCount = new LongAdder(); + try (ScanResult scanResult = new ClassGraph() + .enableAllInfo() + .overrideClasspath(files) + .scan()) { + + ClassInfoList builderList = scanResult + .getClassesImplementing(FluxtionGraphBuilder.class) + .exclude(scanResult.getClassesWithAnnotation(Disabled.class.getCanonicalName())); + + builderList.forEach(c -> { + generationCount.increment(); + System.out.println(generationCount.intValue() + ": invoking builder " + c.getName()); + try { + final FluxtionGraphBuilder newInstance = (FluxtionGraphBuilder) classLoader.loadClass(c.getName()).getDeclaredConstructor().newInstance(); + compile(newInstance::buildGraph, newInstance::configureGeneration); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException | ClassNotFoundException e) { + throw new RuntimeException("cannot instantiate FluxtionGraphBuilder", e); + } + }); + } + return generationCount.intValue(); + } } diff --git a/compiler/src/main/java/com/fluxtion/compiler/FluxtionCompilerConfig.java b/compiler/src/main/java/com/fluxtion/compiler/FluxtionCompilerConfig.java index 42b3ff417..25db8b8ab 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/FluxtionCompilerConfig.java +++ b/compiler/src/main/java/com/fluxtion/compiler/FluxtionCompilerConfig.java @@ -94,6 +94,10 @@ public class FluxtionCompilerConfig { * The if {@link #writeSourceToFile} is false this writer will capture the content of the generation process */ private Writer sourceWriter; + /** + * Flag controlling adding build time to generated source files + */ + private boolean addBuildTime; private transient ClassLoader classLoader; @@ -101,6 +105,7 @@ public FluxtionCompilerConfig() { generateDescription = false; writeSourceToFile = false; compileSource = true; + addBuildTime = false; formatSource = true; templateSep = JAVA_TEMPLATE; classLoader = FluxtionCompilerConfig.class.getClassLoader(); @@ -157,6 +162,14 @@ public void setWriteSourceToFile(boolean writeSourceToFile) { this.writeSourceToFile = writeSourceToFile; } + public boolean isAddBuildTime() { + return addBuildTime; + } + + public void setAddBuildTime(boolean addBuildTime) { + this.addBuildTime = addBuildTime; + } + public void setPackageName(String packageName) { this.packageName = packageName; } diff --git a/compiler/src/main/java/com/fluxtion/compiler/Sample.java b/compiler/src/main/java/com/fluxtion/compiler/Sample.java deleted file mode 100644 index bb129d72b..000000000 --- a/compiler/src/main/java/com/fluxtion/compiler/Sample.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.fluxtion.compiler; - -public class Sample { - - public static String foo(Object bar) { -// return switch(bar) { -// case Integer i -> "I'm an Integer: " + i; -// case Long l -> "I'm a Long: " + l; -// default -> "I'm an object"; -// }; - return ""; - } -} diff --git a/compiler/src/main/java/com/fluxtion/compiler/builder/context/InstanceSupplierFactory.java b/compiler/src/main/java/com/fluxtion/compiler/builder/context/InstanceSupplierFactory.java index bbcef24f1..8f67fa05b 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/builder/context/InstanceSupplierFactory.java +++ b/compiler/src/main/java/com/fluxtion/compiler/builder/context/InstanceSupplierFactory.java @@ -3,6 +3,7 @@ import com.fluxtion.compiler.builder.factory.NodeFactory; import com.fluxtion.compiler.builder.factory.NodeRegistry; import com.fluxtion.compiler.generation.GenerationContext; +import com.fluxtion.runtime.EventProcessorContext; import com.fluxtion.runtime.audit.Auditor; import com.fluxtion.runtime.node.InstanceSupplier; import com.fluxtion.runtime.node.InstanceSupplierNode; @@ -35,10 +36,11 @@ public InstanceSupplier createNode(Map config, NodeRegistry r rawType = Object.class; } final String typeName = "contextService_" + rawType.getSimpleName() + "_" + instanceName + count++; + return new InstanceSupplierNode<>( hasInstanceQualifier ? rawType.getCanonicalName() + "_" + instanceName : rawType.getCanonicalName(), true, - null, + registry.findOrCreateNode(EventProcessorContext.class, config, null), typeName.replace(".", "_")); } diff --git a/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/AbstractFlowBuilder.java b/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/AbstractFlowBuilder.java index 91963f344..ea8bf5f05 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/AbstractFlowBuilder.java +++ b/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/AbstractFlowBuilder.java @@ -16,11 +16,13 @@ import com.fluxtion.runtime.dataflow.function.PushFlowFunction; import com.fluxtion.runtime.dataflow.helpers.InternalEventDispatcher; import com.fluxtion.runtime.dataflow.helpers.Peekers; +import com.fluxtion.runtime.dataflow.helpers.Predicates.PredicateWrapper; import com.fluxtion.runtime.output.SinkPublisher; import com.fluxtion.runtime.partition.LambdaReflection; import com.fluxtion.runtime.partition.LambdaReflection.SerializableBiFunction; import com.fluxtion.runtime.partition.LambdaReflection.SerializableConsumer; import com.fluxtion.runtime.partition.LambdaReflection.SerializableFunction; +import com.fluxtion.runtime.partition.LambdaReflection.SerializableSupplier; public abstract class AbstractFlowBuilder> { @@ -68,6 +70,10 @@ public B filter(SerializableFunction filterFunction) { return connect(new FilterFlowFunction<>(eventStream, filterFunction)); } + public B filter(SerializableSupplier filterFunction) { + return filter(new PredicateWrapper(filterFunction)::test); + } + public

B filterByProperty(SerializableFunction accessor, SerializableFunction filterFunction) { return connect(new FilterByPropertyFlowFunction<>(eventStream, accessor, filterFunction)); } diff --git a/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/DataFlow.java b/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/DataFlow.java index 3b3913feb..9b4e80a11 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/DataFlow.java +++ b/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/DataFlow.java @@ -9,6 +9,7 @@ import com.fluxtion.runtime.event.Event; import com.fluxtion.runtime.event.Signal; import com.fluxtion.runtime.node.DefaultEventHandlerNode; +import com.fluxtion.runtime.partition.LambdaReflection.SerializableBiFunction; import com.fluxtion.runtime.partition.LambdaReflection.SerializableFunction; import com.fluxtion.runtime.partition.LambdaReflection.SerializableSupplier; @@ -69,132 +70,271 @@ static FlowBuilder subscribe(Class classSubscription, in ); } - static GroupByFlowBuilder groupBy(SerializableFunction keyFunction) { - @SuppressWarnings("unchecked") - Class classSubscription = (Class) keyFunction.method().getDeclaringClass(); - return subscribe(classSubscription).groupBy(keyFunction); - } - - static > GroupByFlowBuilder groupBy( - SerializableFunction keyFunction, SerializableSupplier aggregateFunctionSupplier) { - @SuppressWarnings("unchecked") - Class classSubscription = (Class) keyFunction.method().getDeclaringClass(); - return subscribe(classSubscription).groupBy(keyFunction, aggregateFunctionSupplier); - } - - //SerializableSupplier aggregateFunctionSupplier - static GroupByFlowBuilder> groupByToList(SerializableFunction keyFunction) { - @SuppressWarnings("unchecked") - Class classSubscription = (Class) keyFunction.method().getDeclaringClass(); - return subscribe(classSubscription).groupByToList(keyFunction); - } - - static GroupByFlowBuilder> groupByToSet(SerializableFunction keyFunction) { - @SuppressWarnings("unchecked") - Class classSubscription = (Class) keyFunction.method().getDeclaringClass(); - return subscribe(classSubscription).groupByToSet(keyFunction); + /** + * Subscribes to an internal node within the processing graph and presents it as an {@link FlowBuilder} + * for constructing stream processing logic. + * + * @param source The node to be wrapped and made head of this stream + * @param The type of the node + * @return An {@link FlowBuilder} that can used to construct stream processing logic + */ + static FlowBuilder subscribeToNode(T source) { + return new FlowBuilder<>(new NodeToFlowFunction<>(source)); } - static GroupByFlowBuilder groupBy( - SerializableFunction keyFunction, - SerializableFunction valueFunction) { - @SuppressWarnings("unchecked") - Class classSubscription = (Class) keyFunction.method().getDeclaringClass(); - return subscribe(classSubscription).groupBy(keyFunction, valueFunction); + /** + * Subscribes to a property on an internal node within the processing graph and presents it as an {@link FlowBuilder} + * for constructing stream processing logic. The node will be created and added to the graph + * + * @param sourceProperty The property accessor + * @param The type of the node + * @param The type of the property that will be supplied in the stream + * @return An {@link FlowBuilder} that can used to construct stream processing logic + */ + static FlowBuilder subscribeToNodeProperty(SerializableFunction sourceProperty) { + T source; + if (sourceProperty.captured().length == 0) { + try { + source = (T) sourceProperty.getContainingClass().getDeclaredConstructor().newInstance(); + } catch (InstantiationException | IllegalAccessException | InvocationTargetException | + NoSuchMethodException e) { + throw new RuntimeException("no default constructor found for class:" + + sourceProperty.getContainingClass() + + " either add default constructor or pass in a node instance"); + } + } else { + source = (T) sourceProperty.captured()[0]; + } + return subscribeToNode(source).map(sourceProperty); } - static > GroupByFlowBuilder - groupBy(SerializableFunction keyFunction, - SerializableFunction valueFunction, - SerializableSupplier aggregateFunctionSupplier) { - @SuppressWarnings("unchecked") - Class classSubscription = (Class) keyFunction.method().getDeclaringClass(); - return subscribe(classSubscription).groupBy(keyFunction, valueFunction, aggregateFunctionSupplier); + /** + * Subscribes to a property on an internal node within the processing graph and presents it as an {@link FlowBuilder} + * for constructing stream processing logic. + * + * @param propertySupplier The property accessor + * @param The type of the property that will be supplied in the stream + * @return An {@link FlowBuilder} that can used to construct stream processing logic + */ + static FlowBuilder subscribeToNodeProperty(SerializableSupplier propertySupplier) { + EventProcessorBuilderService.service().addOrReuse(propertySupplier.captured()[0]); + return new FlowBuilder<>(new NodePropertyToFlowFunction<>(propertySupplier)); } + /** + * See {@link DataFlow#subscribe(Class)} shortcut method to create a stream that subscribes to a filtered + * {@link Signal} event. Useful for invoking triggers on flows. + * + * @param filterId The filter string to apply + * @return An {@link FlowBuilder} that can used to construct stream processing logic + */ static FlowBuilder subscribeToSignal(String filterId) { return subscribe(Signal.class, filterId); } + /** + * See {@link DataFlow#subscribe(Class)} shortcut method to create a stream that subscribes to a filtered + * {@link Signal} event, containing an event of the type specified. Useful for invoking triggers on flows. + * + * @param filterId The filter string to apply + * @param signalType The type of value that is held by the {@link Signal} event + * @return An {@link FlowBuilder} that can used to construct stream processing logic + */ static FlowBuilder subscribeToSignal(String filterId, Class signalType) { - //subscribe(Signal.class, filterId); return subscribe(Signal.class, filterId).map(Signal::getValue); } + /** + * See {@link DataFlow#subscribe(Class)} shortcut method to create a stream that subscribes to a filtered + * {@link Signal} event, containing an event of the type specified. A default value is provided if the signal + * event contains a null value. Useful for invoking triggers on flows. + * + * @param filterId The filter string to apply + * @param signalType The type of value that is held by the {@link Signal} event + * @param defaultValue the value to use if the signal event value is null + * @return An {@link FlowBuilder} that can used to construct stream processing logic + */ static FlowBuilder subscribeToSignal(String filterId, Class signalType, T defaultValue) { return subscribe(Signal.class, filterId).map(Signal::getValue).defaultValue(defaultValue); } + /** + * See {@link DataFlow#subscribe(Class)} shortcut method to create a int stream that subscribes to a filtered + * {@link Signal} event. Useful for invoking triggers on flows. + * + * @param filterId The filter string to apply + * @return An {@link IntFlowBuilder} that can used to construct stream processing logic + */ static IntFlowBuilder subscribeToIntSignal(String filterId) { return subscribe(Signal.IntSignal.class, filterId).mapToInt(Signal.IntSignal::getValue); } + /** + * See {@link DataFlow#subscribe(Class)} shortcut method to create a int stream that subscribes to a filtered + * {@link Signal} event. Useful for invoking triggers on flows. A default value is provided if the signal + * event contains a 0 value + * + * @param filterId The filter string to apply + * @param defaultValue to use if the signal event value is 0 + * @return An {@link IntFlowBuilder} that can used to construct stream processing logic + */ static IntFlowBuilder subscribeToIntSignal(String filterId, int defaultValue) { return subscribe(Signal.IntSignal.class, filterId).mapToInt(Signal.IntSignal::getValue) .defaultValue(defaultValue); } + /** + * See {@link DataFlow#subscribe(Class)} shortcut method to create a double stream that subscribes to a filtered + * {@link Signal} event. Useful for invoking triggers on flows. + * + * @param filterId The filter string to apply + * @return An {@link DoubleFlowBuilder} that can used to construct stream processing logic + */ static DoubleFlowBuilder subscribeToDoubleSignal(String filterId) { return subscribe(Signal.DoubleSignal.class, filterId).mapToDouble(Signal.DoubleSignal::getValue); } + /** + * See {@link DataFlow#subscribe(Class)} shortcut method to create a double stream that subscribes to a filtered + * {@link Signal} event. Useful for invoking triggers on flows. A default value is provided if the signal + * event contains a 0 value + * + * @param filterId The filter string to apply + * @param defaultValue to use if the signal event value is 0 + * @return An {@link DoubleFlowBuilder} that can used to construct stream processing logic + */ static DoubleFlowBuilder subscribeToDoubleSignal(String filterId, double defaultValue) { return subscribe(Signal.DoubleSignal.class, filterId).mapToDouble(Signal.DoubleSignal::getValue) .defaultValue(defaultValue); } + /** + * See {@link DataFlow#subscribe(Class)} shortcut method to create a long stream that subscribes to a filtered + * {@link Signal} event. Useful for invoking triggers on flows. + * + * @param filterId The filter string to apply + * @return An {@link LongFlowBuilder} that can used to construct stream processing logic + */ static LongFlowBuilder subscribeToLongSignal(String filterId) { return subscribe(Signal.LongSignal.class, filterId).mapToLong(Signal.LongSignal::getValue); } + /** + * See {@link DataFlow#subscribe(Class)} shortcut method to create a long stream that subscribes to a filtered + * {@link Signal} event. Useful for invoking triggers on flows. A default value is provided if the signal + * event contains a 0 value + * + * @param filterId The filter string to apply + * @param defaultValue to use if the signal event value is 0 + * @return An {@link LongFlowBuilder} that can used to construct stream processing logic + */ static LongFlowBuilder subscribeToLongSignal(String filterId, long defaultValue) { return subscribe(Signal.LongSignal.class, filterId).mapToLong(Signal.LongSignal::getValue) .defaultValue(defaultValue); } + /** - * Subscribes to an internal node within the processing graph and presents it as an {@link FlowBuilder} - * for constructing stream processing logic. + * Merges and maps several {@link FlowFunction}'s into a single event stream of type T * - * @param source The node to be wrapped and made head of this stream - * @param The type of the node + * @param builder The builder defining the merge operations + * @param The output type of the merged stream * @return An {@link FlowBuilder} that can used to construct stream processing logic */ - static FlowBuilder subscribeToNode(T source) { - return new FlowBuilder<>(new NodeToFlowFunction<>(source)); + static FlowBuilder mergeMap(MergeAndMapFlowBuilder builder) { + MergeMapFlowFunction build = builder.build(); + return new FlowBuilder<>(build); } - static FlowBuilder subscribeToNodeProperty(SerializableFunction sourceProperty) { - T source; - if (sourceProperty.captured().length == 0) { - try { - source = (T) sourceProperty.getContainingClass().getDeclaredConstructor().newInstance(); - } catch (InstantiationException | IllegalAccessException | InvocationTargetException | - NoSuchMethodException e) { - throw new RuntimeException("no default constructor found for class:" - + sourceProperty.getContainingClass() - + " either add default constructor or pass in a node instance"); - } - } else { - source = (T) sourceProperty.captured()[0]; - } - return subscribeToNode(source).map(sourceProperty); + /** + * Merges two {@link FlowBuilder}'s into a single event stream of type T + * + * @param streamAToMerge stream A to merge + * @param streamBToMerge stream B to merge + * @param type of stream A + * @param type of stream B + * @return An {@link FlowBuilder} that can used to construct stream processing logic + */ + static FlowBuilder merge(FlowBuilder streamAToMerge, FlowBuilder streamBToMerge) { + return streamAToMerge.merge(streamBToMerge); } - static FlowBuilder subscribeToNodeProperty(SerializableSupplier propertySupplier) { - EventProcessorBuilderService.service().addOrReuse(propertySupplier.captured()[0]); - return new FlowBuilder<>(new NodePropertyToFlowFunction<>(propertySupplier)); + /** + * Merges multiple {@link FlowBuilder}'s into a single event stream of type T + * + * @param streamAToMerge stream A to merge + * @param streamBToMerge stream B to merge + * @param streamsToMerge streams to merge + * @param type of stream A + * @param type of stream B + * @return An {@link FlowBuilder} that can used to construct stream processing logic + */ + @SuppressWarnings("unchecked") + static FlowBuilder merge( + FlowBuilder streamAToMerge, + FlowBuilder streamBToMerge, + FlowBuilder... streamsToMerge) { + return streamAToMerge.merge(streamBToMerge, streamsToMerge); } /** - * Merges and maps several {@link FlowFunction}'s into a single event stream of type T + * Applies a mapping bi function to a pair of streams, creating a stream that is the output of the function. The + * mapping function will be invoked whenever either stream triggers a notification * - * @param builder The builder defining the merge operations - * @param The output type of the merged stream + * @param biFunction The mapping {@link java.util.function.BiFunction} + * @param streamArg1 Stream providing the first argument to the mapping function + * @param streamArg2 Stream providing the second argument to the mapping function + * @param The type of argument 1 stream + * @param The type of argument 2 stream + * @param The return type of the mapping function * @return An {@link FlowBuilder} that can used to construct stream processing logic */ - static FlowBuilder mergeMap(MergeAndMapFlowBuilder builder) { - MergeMapFlowFunction build = builder.build(); - return new FlowBuilder<>(build); + static FlowBuilder mapBiFunction(SerializableBiFunction biFunction, + FlowBuilder streamArg1, + FlowBuilder streamArg2) { + return streamArg1.mapBiFunction(biFunction, streamArg2); + } + + static GroupByFlowBuilder groupBy(SerializableFunction keyFunction) { + @SuppressWarnings("unchecked") + Class classSubscription = (Class) keyFunction.method().getDeclaringClass(); + return subscribe(classSubscription).groupBy(keyFunction); + } + + static > GroupByFlowBuilder groupBy( + SerializableFunction keyFunction, SerializableSupplier aggregateFunctionSupplier) { + @SuppressWarnings("unchecked") + Class classSubscription = (Class) keyFunction.method().getDeclaringClass(); + return subscribe(classSubscription).groupBy(keyFunction, aggregateFunctionSupplier); + } + + //SerializableSupplier aggregateFunctionSupplier + static GroupByFlowBuilder> groupByToList(SerializableFunction keyFunction) { + @SuppressWarnings("unchecked") + Class classSubscription = (Class) keyFunction.method().getDeclaringClass(); + return subscribe(classSubscription).groupByToList(keyFunction); + } + + static GroupByFlowBuilder> groupByToSet(SerializableFunction keyFunction) { + @SuppressWarnings("unchecked") + Class classSubscription = (Class) keyFunction.method().getDeclaringClass(); + return subscribe(classSubscription).groupByToSet(keyFunction); + } + + static GroupByFlowBuilder groupBy( + SerializableFunction keyFunction, + SerializableFunction valueFunction) { + @SuppressWarnings("unchecked") + Class classSubscription = (Class) keyFunction.method().getDeclaringClass(); + return subscribe(classSubscription).groupBy(keyFunction, valueFunction); + } + + static > GroupByFlowBuilder + groupBy(SerializableFunction keyFunction, + SerializableFunction valueFunction, + SerializableSupplier aggregateFunctionSupplier) { + @SuppressWarnings("unchecked") + Class classSubscription = (Class) keyFunction.method().getDeclaringClass(); + return subscribe(classSubscription).groupBy(keyFunction, valueFunction, aggregateFunctionSupplier); } } diff --git a/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/FlowBuilder.java b/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/FlowBuilder.java index 9f24da887..6989be7b5 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/FlowBuilder.java +++ b/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/FlowBuilder.java @@ -1,6 +1,7 @@ package com.fluxtion.compiler.builder.dataflow; import com.fluxtion.runtime.EventProcessorBuilderService; +import com.fluxtion.runtime.dataflow.FlowFunction; import com.fluxtion.runtime.dataflow.FlowSupplier; import com.fluxtion.runtime.dataflow.TriggeredFlowFunction; import com.fluxtion.runtime.dataflow.aggregate.AggregateFlowFunction; @@ -8,16 +9,9 @@ import com.fluxtion.runtime.dataflow.aggregate.function.TimedSlidingWindow; import com.fluxtion.runtime.dataflow.aggregate.function.TumblingWindow; import com.fluxtion.runtime.dataflow.function.BinaryMapFlowFunction.BinaryMapToRefFlowFunction; -import com.fluxtion.runtime.dataflow.function.FlatMapArrayFlowFunction; -import com.fluxtion.runtime.dataflow.function.FlatMapFlowFunction; -import com.fluxtion.runtime.dataflow.function.LookupFlowFunction; -import com.fluxtion.runtime.dataflow.function.MapFlowFunction; +import com.fluxtion.runtime.dataflow.function.*; import com.fluxtion.runtime.dataflow.function.MapFlowFunction.MapRef2RefFlowFunction; -import com.fluxtion.runtime.dataflow.function.MergeFlowFunction; -import com.fluxtion.runtime.dataflow.groupby.GroupBy; -import com.fluxtion.runtime.dataflow.groupby.GroupByFlowFunctionWrapper; -import com.fluxtion.runtime.dataflow.groupby.GroupByTimedSlidingWindow; -import com.fluxtion.runtime.dataflow.groupby.GroupByTumblingWindow; +import com.fluxtion.runtime.dataflow.groupby.*; import com.fluxtion.runtime.dataflow.helpers.Aggregates; import com.fluxtion.runtime.dataflow.helpers.Collectors; import com.fluxtion.runtime.dataflow.helpers.DefaultValue; @@ -27,6 +21,7 @@ import com.fluxtion.runtime.partition.LambdaReflection.SerializableFunction; import com.fluxtion.runtime.partition.LambdaReflection.SerializableSupplier; +import java.util.ArrayList; import java.util.List; import java.util.Set; @@ -78,6 +73,30 @@ public FlowBuilder map(SerializableFunction mapFunction) { return super.mapBase(mapFunction); } + public FlowBuilder> mapToSet() { + return map(Collectors.toSet()); + } + + public FlowBuilder> mapToSet(SerializableFunction mapFunction) { + return map(mapFunction).map(Collectors.toSet()); + } + + public FlowBuilder> mapToList() { + return map(Collectors.toList()); + } + + public FlowBuilder> mapToList(SerializableFunction mapFunction) { + return map(mapFunction).map(Collectors.toList()); + } + + public FlowBuilder> mapToList(int maxElements) { + return map(Collectors.toList(maxElements)); + } + + public FlowBuilder> mapToList(SerializableFunction mapFunction, int maxElements) { + return map(mapFunction).map(Collectors.toList(maxElements)); + } + public FlowBuilder mapBiFunction(SerializableBiFunction int2IntFunction, FlowBuilder stream2Builder) { @@ -92,6 +111,17 @@ public FlowBuilder merge(FlowBuilder streamToMerge) { return new FlowBuilder<>(new MergeFlowFunction<>(eventStream, streamToMerge.eventStream)); } + @SuppressWarnings("unchecked") + public FlowBuilder merge(FlowBuilder streamToMerge, FlowBuilder... streamsToMerge) { + List> mergeList = new ArrayList<>(); + mergeList.add(eventStream); + mergeList.add(streamToMerge.eventStream); + for (FlowBuilder flowBuilder : streamsToMerge) { + mergeList.add(flowBuilder.eventStream); + } + return new FlowBuilder<>(new MergeFlowFunction<>(mergeList)); + } + public FlowBuilder flatMap(SerializableFunction> iterableFunction) { return new FlowBuilder<>(new FlatMapFlowFunction<>(eventStream, iterableFunction)); } @@ -117,6 +147,19 @@ public FlowBuilder flatMapFromArray(SerializableFunction iterable new TimedSlidingWindow<>(eventStream, aggregateFunction, bucketSizeMillis, bucketsPerWindow)); } + /** + * Aggregates a flow using a key function to group by and an aggregating function to process new values for a keyed + * bucket. + * + * @param keyFunction The key function that groups and buckets incoming values + * @param valueFunction The value that is extracted from the incoming stream and applied to the aggregating function + * @param aggregateFunctionSupplier A factory that supplies aggregating functions, each function has its own function instance + * @param Value type extracted from the incoming data flow + * @param The type of the key used to group values + * @param The return type of the aggregating function + * @param The aggregating function type + * @return A GroupByFlowBuilder for the aggregated flow + */ public > GroupByFlowBuilder groupBy(SerializableFunction keyFunction, SerializableFunction valueFunction, @@ -126,33 +169,147 @@ public FlowBuilder flatMapFromArray(SerializableFunction iterable return new GroupByFlowBuilder<>(x); } + /** + * Specialisation of groupBy where the value is the identity of the incoming data flow + * + * @param keyFunction The key function that groups and buckets incoming values + * @param aggregateFunctionSupplier A factory that supplies aggregating functions, each function has its own function instance + * @param The type of the key used to group values + * @param The return type of the aggregating function + * @param The aggregating function type + * @return A GroupByFlowBuilder for the aggregated flow + * @see FlowBuilder#groupBy(SerializableFunction, SerializableFunction, SerializableSupplier) + */ public > GroupByFlowBuilder groupBy(SerializableFunction keyFunction, SerializableSupplier aggregateFunctionSupplier) { return groupBy(keyFunction, Mappers::identity, aggregateFunctionSupplier); } + /** + * Specialisation of groupBy where the output of the groupBy is the last value received for a bucket. The value is + * extracted using the value function + * + * @param keyFunction The key function that groups and buckets incoming values + * @param valueFunction The value that is extracted from the incoming stream and applied to the aggregating function + * @param Value type extracted from the incoming data flow + * @param The type of the key used to group values + * @return A GroupByFlowBuilder for the aggregated flow + * @see FlowBuilder#groupBy(SerializableFunction, SerializableFunction, SerializableSupplier) + */ public GroupByFlowBuilder groupBy( SerializableFunction keyFunction, SerializableFunction valueFunction) { return groupBy(keyFunction, valueFunction, Aggregates.identityFactory()); } + /** + * Specialisation of groupBy where the output of the groupBy is the last value received for a bucket, where + * the value is the identity of the incoming data flow + * + * @param keyFunction The key function that groups and buckets incoming values + * @param The type of the key used to group values + * @return A GroupByFlowBuilder for the aggregated flow + */ public GroupByFlowBuilder groupBy(SerializableFunction keyFunction) { return groupBy(keyFunction, Mappers::identity); } + /** + * Creates a GroupByFlowBuilder using a compound key created by a set of method reference accessors to for the value. + * The value is the last value supplied + * + * @param keyFunction key accessor + * @param keyFunctions multi arg key accessors + * @return GroupByFlowBuilder keyed on properties + */ + @SafeVarargs + public final GroupByFlowBuilder, T> groupByFields( + SerializableFunction keyFunction, + SerializableFunction... keyFunctions) { + return groupBy(GroupByKey.build(keyFunction, keyFunctions)); + } + + /** + * Aggregates a flow using a key to group by and an aggregating function to process new values for a keyed + * bucket. The key is a compound key created by a set of method reference accessors to for the value. + * + * @param aggregateFunctionSupplier A factory that supplies aggregating functions, each function has its own function instance + * @param keyFunction key accessor + * @param keyFunctions multi arg key accessors + * @param The return type of the aggregating function + * @param The aggregating function type + * @return A GroupByFlowBuilder for the aggregated flow + * @see FlowBuilder#groupBy(SerializableFunction, SerializableFunction, SerializableSupplier) + */ + @SafeVarargs + public final > GroupByFlowBuilder, A> groupByFieldsAggregate( + SerializableSupplier aggregateFunctionSupplier, + SerializableFunction keyFunction, + SerializableFunction... keyFunctions) { + return groupBy(GroupByKey.build(keyFunction, keyFunctions), aggregateFunctionSupplier); + } + + /** + * Creates a GroupByFlowBuilder using a compound key created by a set of method reference accessors to for the key + * The value is extracted from the input using the value function + * + * @param valueFunction the value that will be stored in the groupBy + * @param keyFunction key accessor + * @param keyFunctions multi arg key accessors + * @return GroupByFlowBuilder keyed on properties + */ + @SafeVarargs + public final GroupByFlowBuilder, V> groupByFieldsAndGet( + SerializableFunction valueFunction, + SerializableFunction keyFunction, + SerializableFunction... keyFunctions) { + return groupBy(GroupByKey.build(keyFunction, keyFunctions), valueFunction); + } + + /** + * Creates a GroupByFlowBuilder using a compound key created by a set of method reference accessors to for the key + * The value is extracted from the input using the value function and is used as an input to the aggregating function + * + * @param valueFunction the value that will be stored in the groupBy + * @param aggregateFunctionSupplier A factory that supplies aggregating functions, each function has its own function instance + * @param keyFunction key accessor + * @param keyFunctions multi arg key accessors + * @param Value type extracted from the incoming data flow + * @param The return type of the aggregating function + * @param The aggregating function type + * @return A GroupByFlowBuilder for the aggregated flow + * @see FlowBuilder#groupBy(SerializableFunction, SerializableFunction, SerializableSupplier) + */ + @SafeVarargs + public final > GroupByFlowBuilder, A> groupByFieldsGetAndAggregate( + SerializableFunction valueFunction, + SerializableSupplier aggregateFunctionSupplier, + SerializableFunction keyFunction, + SerializableFunction... keyFunctions) { + return groupBy(GroupByKey.build(keyFunction, keyFunctions), valueFunction, aggregateFunctionSupplier); + } + public GroupByFlowBuilder> groupByToList(SerializableFunction keyFunction) { - return groupBy(keyFunction, Mappers::identity, Collectors.toList()); + return groupBy(keyFunction, Mappers::identity, Collectors.listFactory()); + } + + public GroupByFlowBuilder> groupByToList( + SerializableFunction keyFunction, SerializableFunction valueFunction) { + return groupBy(keyFunction, valueFunction, Collectors.listFactory()); } public GroupByFlowBuilder> groupByToSet(SerializableFunction keyFunction) { - return groupBy(keyFunction, Mappers::identity, Collectors.toSet()); + return groupBy(keyFunction, Mappers::identity, Collectors.setFactory()); + } + + public GroupByFlowBuilder> groupByToSet(SerializableFunction keyFunction, SerializableFunction valueFunction) { + return groupBy(keyFunction, valueFunction, Collectors.setFactory()); } public GroupByFlowBuilder> groupByToList( SerializableFunction keyFunction, int maxElementsInList) { - return groupBy(keyFunction, Mappers::identity, Collectors.toList(maxElementsInList)); + return groupBy(keyFunction, Mappers::identity, Collectors.listFactory(maxElementsInList)); } public > GroupByFlowBuilder diff --git a/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/FlowBuilderBase.java b/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/FlowBuilderBase.java index 4b996ef51..382c552e9 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/FlowBuilderBase.java +++ b/compiler/src/main/java/com/fluxtion/compiler/builder/dataflow/FlowBuilderBase.java @@ -129,12 +129,12 @@ public FlowBuilderBase merge(FlowBuilderBase streamToMerge) { public GroupByFlowBuilder> groupByAsList(SerializableFunction keyFunction) { - return groupBy(keyFunction, Mappers::identity, Collectors.toList()); + return groupBy(keyFunction, Mappers::identity, Collectors.listFactory()); } public GroupByFlowBuilder> groupByAsList(SerializableFunction keyFunction, int maxElementsInList) { - return groupBy(keyFunction, Mappers::identity, Collectors.toList(maxElementsInList)); + return groupBy(keyFunction, Mappers::identity, Collectors.listFactory(maxElementsInList)); } public > GroupByFlowBuilder diff --git a/compiler/src/main/java/com/fluxtion/compiler/builder/filter/FilterDescription.java b/compiler/src/main/java/com/fluxtion/compiler/builder/filter/FilterDescription.java index 6588c6696..af3fc9f6d 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/builder/filter/FilterDescription.java +++ b/compiler/src/main/java/com/fluxtion/compiler/builder/filter/FilterDescription.java @@ -19,6 +19,7 @@ import com.fluxtion.runtime.event.Event; import lombok.ToString; +import java.lang.reflect.Method; import java.util.Objects; /** @@ -81,6 +82,8 @@ public class FilterDescription { */ public String variableName; + private Method exportFunction; + public static FilterDescription build(Object input) { FilterDescription result = DEFAULT_FILTER; if (input instanceof Event) { @@ -184,6 +187,14 @@ public void setEventClass(Class eventClass) { this.eventClass = eventClass; } + public void setExportFunction(Method exportFunction) { + this.exportFunction = exportFunction; + } + + public Method getExportFunction() { + return exportFunction; + } + @Override public int hashCode() { int hash = 5; diff --git a/compiler/src/main/java/com/fluxtion/compiler/builder/output/SinkPublisherFactory.java b/compiler/src/main/java/com/fluxtion/compiler/builder/output/SinkPublisherFactory.java new file mode 100644 index 000000000..e2cbea5ed --- /dev/null +++ b/compiler/src/main/java/com/fluxtion/compiler/builder/output/SinkPublisherFactory.java @@ -0,0 +1,17 @@ +package com.fluxtion.compiler.builder.output; + +import com.fluxtion.compiler.builder.factory.NodeFactory; +import com.fluxtion.compiler.builder.factory.NodeRegistry; +import com.fluxtion.runtime.output.SinkPublisher; + +import java.util.Map; + +public class SinkPublisherFactory implements NodeFactory { + + @Override + public SinkPublisher createNode(Map config, NodeRegistry registry) { + final String instanceName = (String) config.get(NodeFactory.INSTANCE_KEY); + return new SinkPublisher<>(instanceName); + } + +} diff --git a/compiler/src/main/java/com/fluxtion/compiler/builder/time/ClockFactory.java b/compiler/src/main/java/com/fluxtion/compiler/builder/time/ClockFactory.java index b9186cfdf..10c4d9671 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/builder/time/ClockFactory.java +++ b/compiler/src/main/java/com/fluxtion/compiler/builder/time/ClockFactory.java @@ -27,12 +27,10 @@ */ public class ClockFactory implements NodeFactory { - public static final Clock SINGLETON = new Clock(); - @Override public Clock createNode(Map config, NodeRegistry registry) { - registry.registerAuditor(SINGLETON, "clock"); - return SINGLETON; + registry.registerAuditor(Clock.DEFAULT_CLOCK, "clock"); + return Clock.DEFAULT_CLOCK; } } diff --git a/compiler/src/main/java/com/fluxtion/compiler/extern/spring/FluxtionSpring.java b/compiler/src/main/java/com/fluxtion/compiler/extern/spring/FluxtionSpring.java new file mode 100644 index 000000000..39ff07e18 --- /dev/null +++ b/compiler/src/main/java/com/fluxtion/compiler/extern/spring/FluxtionSpring.java @@ -0,0 +1,129 @@ +package com.fluxtion.compiler.extern.spring; + +import com.fluxtion.compiler.EventProcessorConfig; +import com.fluxtion.compiler.Fluxtion; +import com.fluxtion.compiler.FluxtionCompilerConfig; +import com.fluxtion.runtime.EventProcessor; +import com.fluxtion.runtime.partition.LambdaReflection; +import com.fluxtion.runtime.partition.LambdaReflection.SerializableConsumer; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.context.ApplicationContext; +import org.springframework.context.support.FileSystemXmlApplicationContext; + +import java.nio.file.Path; +import java.util.function.Consumer; + +/** + * Provides utility functions to build Fluxtion {@link EventProcessor} using a spring {@link ApplicationContext} to define + * the object instances managed by Fluxtion. + */ +public class FluxtionSpring { + + private final static Logger LOGGER = LoggerFactory.getLogger(FluxtionSpring.class); + private final ApplicationContext context; + private Consumer configCustomizer = c -> { + }; + + public FluxtionSpring(String springFile) { + this(new FileSystemXmlApplicationContext(springFile)); + LOGGER.debug("loading spring springFile:{}", springFile); + } + + public FluxtionSpring(String springFile, Consumer configCustomizer) { + this(new FileSystemXmlApplicationContext(springFile), configCustomizer); + LOGGER.debug("loading spring springFile:{}", springFile); + } + + public FluxtionSpring(ApplicationContext context, Consumer configCustomizer) { + this.context = context; + this.configCustomizer = configCustomizer; + } + + public FluxtionSpring(ApplicationContext context) { + this.context = context; + } + + public static EventProcessor compileAot( + Path springFile, + SerializableConsumer compilerConfig) { + FluxtionSpring fluxtionSpring = new FluxtionSpring(springFile.toAbsolutePath().toUri().toString()); + return fluxtionSpring._compileAot(compilerConfig); + } + + public static EventProcessor compileAot( + Path springFile, + Consumer configCustomizer, + SerializableConsumer compilerConfig) { + return new FluxtionSpring(springFile.toAbsolutePath().toUri().toString(), configCustomizer)._compileAot(compilerConfig); + } + + public static EventProcessor compileAot( + ApplicationContext context, + SerializableConsumer compilerConfig) { + return new FluxtionSpring(context)._compileAot(compilerConfig); + } + + public static EventProcessor compileAot( + ApplicationContext context, + Consumer configCustomizer, + SerializableConsumer compilerConfig) { + return new FluxtionSpring(context, configCustomizer)._compileAot(compilerConfig); + } + + public static EventProcessor compile(Path springFile) { + FluxtionSpring fluxtionSpring = new FluxtionSpring(springFile.toAbsolutePath().toUri().toString()); + return fluxtionSpring._compile(); + } + + public static EventProcessor compile(Path springFile, Consumer configCustomizer) { + return new FluxtionSpring(springFile.toAbsolutePath().toUri().toString(), configCustomizer)._compile(); + } + + public static EventProcessor compile(ApplicationContext context) { + return new FluxtionSpring(context)._compile(); + } + + public static EventProcessor compile(ApplicationContext context, Consumer configCustomizer) { + return new FluxtionSpring(context, configCustomizer)._compile(); + } + + public static EventProcessor interpret(Path springFile) { + FluxtionSpring fluxtionSpring = new FluxtionSpring(springFile.toAbsolutePath().toUri().toString()); + return fluxtionSpring._interpret(); + } + + public static EventProcessor interpret(Path springFile, Consumer configCustomizer) { + return new FluxtionSpring(springFile.toAbsolutePath().toUri().toString(), configCustomizer)._interpret(); + } + + public static EventProcessor interpret(ApplicationContext context) { + return new FluxtionSpring(context)._interpret(); + } + + public static EventProcessor interpret(ApplicationContext context, Consumer configCustomizer) { + return new FluxtionSpring(context, configCustomizer)._interpret(); + } + + private EventProcessor _compileAot(LambdaReflection.SerializableConsumer compilerConfig) { + return Fluxtion.compile(this::addNodes, compilerConfig); + } + + private EventProcessor _compile() { + return Fluxtion.compile(this::addNodes); + } + + private EventProcessor _interpret() { + return Fluxtion.interpret(this::addNodes); + } + + private void addNodes(EventProcessorConfig config) { + LOGGER.debug("loading spring context:{}", context); + for (String beanDefinitionName : context.getBeanDefinitionNames()) { + Object bean = context.getBean(beanDefinitionName); + LOGGER.debug("adding bean:{} to fluxtion", beanDefinitionName); + config.addNode(bean, beanDefinitionName); + } + configCustomizer.accept(config); + } +} diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/EventProcessorFactory.java b/compiler/src/main/java/com/fluxtion/compiler/generation/EventProcessorFactory.java index 65fbedf62..334673c73 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/EventProcessorFactory.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/EventProcessorFactory.java @@ -197,9 +197,8 @@ public static EventProcessor compile( cfgBuilder.accept(fluxtionCompilerConfig); EventProcessorCompilation compiler = new EventProcessorCompilation(); - Class sepClass = compiler.compile(fluxtionCompilerConfig, new InProcessEventProcessorConfig(sepConfig)); - EventProcessor sep = sepClass.getDeclaredConstructor().newInstance(); - return sep; + Class> sepClass = compiler.compile(fluxtionCompilerConfig, new InProcessEventProcessorConfig(sepConfig)); + return sepClass == null ? null : sepClass.getDeclaredConstructor().newInstance(); } public static EventProcessor compile( diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/GenerationContext.java b/compiler/src/main/java/com/fluxtion/compiler/generation/GenerationContext.java index 6de4925fd..4617c4325 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/GenerationContext.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/GenerationContext.java @@ -16,6 +16,7 @@ */ package com.fluxtion.compiler.generation; +import com.fluxtion.runtime.callback.InstanceCallbackEvent; import lombok.Data; import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; @@ -185,6 +186,7 @@ private GenerationContext(String packageName, String sepClassName, File outputDi log.info("classloader:{}", this.classLoader); log.debug("built GenerationContext: {}", this); cacheMap = new HashMap<>(); + InstanceCallbackEvent.reset(); } private GenerationContext(ClassLoader classLoasder, String packageName, String sepClassName, File outputDirectory, File resourcesRootDirectory, File buildOutputDirectory) { @@ -194,6 +196,7 @@ private GenerationContext(ClassLoader classLoasder, String packageName, String s this.resourcesRootDirectory = resourcesRootDirectory; this.classLoader = classLoasder; cacheMap = new HashMap<>(); + InstanceCallbackEvent.reset(); log.debug("built GenerationContext: {}", this); } diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/RuntimeConstants.java b/compiler/src/main/java/com/fluxtion/compiler/generation/RuntimeConstants.java new file mode 100644 index 000000000..6aec17cd2 --- /dev/null +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/RuntimeConstants.java @@ -0,0 +1,6 @@ +package com.fluxtion.compiler.generation; + +public interface RuntimeConstants { + String FLUXTION_NO_COMPILE = "FLUXTION.NO_COMPILE"; + String GENERATION_CLASSPATH = "FLUXTION.GENERATION.CLASSPATH"; +} diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/annotationprocessor/ValidateExportFunctionAnnotations.java b/compiler/src/main/java/com/fluxtion/compiler/generation/annotationprocessor/ValidateExportFunctionAnnotations.java new file mode 100644 index 000000000..d50120dc2 --- /dev/null +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/annotationprocessor/ValidateExportFunctionAnnotations.java @@ -0,0 +1,52 @@ +package com.fluxtion.compiler.generation.annotationprocessor; + +import com.google.auto.service.AutoService; + +import javax.annotation.processing.AbstractProcessor; +import javax.annotation.processing.Processor; +import javax.annotation.processing.RoundEnvironment; +import javax.lang.model.SourceVersion; +import javax.lang.model.element.Element; +import javax.lang.model.element.Modifier; +import javax.lang.model.element.TypeElement; +import javax.lang.model.type.ExecutableType; +import javax.lang.model.type.TypeKind; +import javax.tools.Diagnostic; +import java.util.HashSet; +import java.util.Set; +import java.util.stream.Collectors; + +@AutoService(Processor.class) +public class ValidateExportFunctionAnnotations extends AbstractProcessor { + @Override + public boolean process(Set annotations, RoundEnvironment roundEnv) { + for (TypeElement annotation : annotations) { + Set annotatedElements = roundEnv.getElementsAnnotatedWith(annotation); + Set typeElements = annotatedElements.stream() + .filter(element -> + { + TypeKind returnType = ((ExecutableType) element.asType()).getReturnType().getKind(); + boolean validReturn = returnType == TypeKind.BOOLEAN || returnType == TypeKind.VOID; + return !validReturn || !element.getModifiers().contains(Modifier.PUBLIC); + } + ) + .collect(Collectors.toSet()); + typeElements.forEach(element -> + processingEnv.getMessager().printMessage(Diagnostic.Kind.ERROR, + "exported method should be public method and a boolean return type ", element)); + + } + return false; + } + + @Override + public SourceVersion getSupportedSourceVersion() { + return SourceVersion.latestSupported(); + } + + @Override + public Set getSupportedAnnotationTypes() { + Set supportedAnnotations = new HashSet<>(); + return supportedAnnotations; + } +} diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/compiler/EventProcessorCompilation.java b/compiler/src/main/java/com/fluxtion/compiler/generation/compiler/EventProcessorCompilation.java index 25c9860ab..ae0fb81c7 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/compiler/EventProcessorCompilation.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/compiler/EventProcessorCompilation.java @@ -22,6 +22,7 @@ import com.fluxtion.compiler.builder.factory.NodeFactoryLocator; import com.fluxtion.compiler.builder.factory.NodeFactoryRegistration; import com.fluxtion.compiler.generation.GenerationContext; +import com.fluxtion.compiler.generation.RuntimeConstants; import com.fluxtion.compiler.generation.compiler.classcompiler.StringCompilation; import com.google.googlejavaformat.java.Formatter; import org.apache.commons.io.FileUtils; @@ -30,15 +31,7 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import java.io.Closeable; -import java.io.File; -import java.io.FileWriter; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.Reader; -import java.io.StringWriter; -import java.io.Writer; +import java.io.*; import java.nio.file.Files; import java.nio.file.Paths; @@ -101,6 +94,9 @@ private Class generateSep() throws Exception { outFile.getParentFile().mkdirs(); if (outFile.exists()) { backupFile = new File(outFile.getParentFile(), outFile.getName() + ".backup"); + if (backupFile.exists()) { + throw new RuntimeException("Fluxtion generation problem backup file exists - please move or delete file:" + backupFile.getCanonicalPath()); + } FileUtils.moveFile(outFile, backupFile); } writer = new FileWriter(outFile); @@ -109,7 +105,7 @@ private Class generateSep() throws Exception { } EventProcessorGenerator eventProcessorGenerator = new EventProcessorGenerator(); - eventProcessorGenerator.templateSep(builderConfig, compilerConfig.isGenerateDescription(), writer); + eventProcessorGenerator.templateSep(builderConfig, compilerConfig, writer); GenerationContext generationConfig = GenerationContext.SINGLETON; String fqn = generationConfig.getPackageName() + "." + generationConfig.getSepClassName(); File file = new File(generationConfig.getPackageDirectory(), generationConfig.getSepClassName() + ".java"); @@ -140,7 +136,7 @@ private Class generateSep() throws Exception { EventProcessorGenerator.formatSource(file); LOG.debug("completed formatting source"); } - if (compilerConfig.isCompileSource()) { + if (compilerConfig.isCompileSource() && !Boolean.getBoolean(RuntimeConstants.FLUXTION_NO_COMPILE)) { LOG.debug("start compiling source"); if (compilerConfig.isWriteSourceToFile()) { builderConfig.getCompilerOptions(); @@ -155,6 +151,8 @@ private Class generateSep() throws Exception { FileUtils.delete(backupFile); backupFile = null; } + } else if (backupFile != null && formatSuccess) { + FileUtils.delete(backupFile); } if (backupFile != null && !formatSuccess) { FileUtils.delete(backupFile); diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/compiler/EventProcessorGenerator.java b/compiler/src/main/java/com/fluxtion/compiler/generation/compiler/EventProcessorGenerator.java index 023fb25c6..5a6f8c2ce 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/compiler/EventProcessorGenerator.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/compiler/EventProcessorGenerator.java @@ -18,6 +18,7 @@ package com.fluxtion.compiler.generation.compiler; import com.fluxtion.compiler.EventProcessorConfig; +import com.fluxtion.compiler.FluxtionCompilerConfig; import com.fluxtion.compiler.builder.factory.NodeFactoryLocator; import com.fluxtion.compiler.builder.factory.NodeFactoryRegistration; import com.fluxtion.compiler.generation.GenerationContext; @@ -63,6 +64,7 @@ public class EventProcessorGenerator { private EventProcessorConfig config; private static final Logger LOG = LoggerFactory.getLogger(EventProcessorGenerator.class); private SimpleEventProcessorModel simpleEventProcessorModel; + private FluxtionCompilerConfig compilerConfig; public InMemoryEventProcessor inMemoryProcessor(EventProcessorConfig config, boolean generateDescription) throws Exception { config.buildConfig(); @@ -92,13 +94,14 @@ public InMemoryEventProcessor inMemoryProcessor(EventProcessorConfig config, boo if (generateDescription && !GenerationContext.SINGLETON.getPackageName().isEmpty()) { exportGraphMl(graph); } - return new InMemoryEventProcessor(simpleEventProcessorModel); + return new InMemoryEventProcessor(simpleEventProcessorModel, config); } - public void templateSep(EventProcessorConfig config, boolean generateDescription, Writer writer) throws Exception { + public void templateSep(EventProcessorConfig config, FluxtionCompilerConfig compilerConfig, Writer writer) throws Exception { ExecutorService execSvc = Executors.newCachedThreadPool(); config.buildConfig(); this.config = config; + this.compilerConfig = compilerConfig; LOG.debug("init velocity"); initVelocity(); LOG.debug("start graph calc"); @@ -117,7 +120,7 @@ public void templateSep(EventProcessorConfig config, boolean generateDescription simpleEventProcessorModel.generateMetaModel(config.isSupportDirtyFiltering()); //TODO add conditionality for different target languages //buildJava output - if (generateDescription) { + if (compilerConfig.isGenerateDescription()) { execSvc.submit(() -> { LOG.debug("start exporting graphML/images"); exportGraphMl(graph); @@ -138,8 +141,8 @@ public SimpleEventProcessorModel getSimpleEventProcessorModel() { private static void initVelocity() { - Velocity.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath"); - Velocity.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName()); + Velocity.setProperty(RuntimeConstants.RESOURCE_LOADERS, "classpath"); + Velocity.setProperty("resource.loader.classpath.class", ClasspathResourceLoader.class.getName()); ClassLoader originalClassLoader = Thread.currentThread().getContextClassLoader(); Thread.currentThread().setContextClassLoader(GenerationContext.SINGLETON.getClassLoader()); Velocity.init(); @@ -190,7 +193,11 @@ private void templateJavaOutput(Writer templateWriter) throws Exception { private void addVersionInformation(Context ctx) { ctx.put("generator_version_information", this.getClass().getPackage().getImplementationVersion()); ctx.put("api_version_information", OnEventHandler.class.getPackage().getImplementationVersion()); - ctx.put("build_time", LocalDateTime.now()); + if (compilerConfig.isAddBuildTime()) { + ctx.put("build_time", LocalDateTime.now()); + } else { + ctx.put("build_time", "Not available"); + } } public static void formatSource(File outFile) { diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/compiler/classcompiler/StringCompilation.java b/compiler/src/main/java/com/fluxtion/compiler/generation/compiler/classcompiler/StringCompilation.java index d20bba3ec..05053424f 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/compiler/classcompiler/StringCompilation.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/compiler/classcompiler/StringCompilation.java @@ -1,9 +1,7 @@ package com.fluxtion.compiler.generation.compiler.classcompiler; -import com.fluxtion.compiler.generation.annotationprocessor.ValidateEventHandlerAnnotations; -import com.fluxtion.compiler.generation.annotationprocessor.ValidateLifecycleAnnotations; -import com.fluxtion.compiler.generation.annotationprocessor.ValidateOnParentUpdateHandlerAnnotations; -import com.fluxtion.compiler.generation.annotationprocessor.ValidateOnTriggerAnnotations; +import com.fluxtion.compiler.generation.RuntimeConstants; +import com.fluxtion.compiler.generation.annotationprocessor.*; import javax.tools.*; import java.io.IOException; @@ -36,6 +34,12 @@ static Class compile(String className, String source, List option final JavaByteObject byteObject = new JavaByteObject(className); StandardJavaFileManager standardFileManager = compiler.getStandardFileManager(diagnostics, null, null); JavaFileManager fileManager = createFileManager(standardFileManager, byteObject); + String cp = System.getProperty(RuntimeConstants.GENERATION_CLASSPATH); + if (cp != null) { + optionList.add("-classpath"); + optionList.add(cp); + } + JavaCompiler.CompilationTask task = compiler.getTask( null, fileManager, diagnostics, optionList, null, Collections.singletonList(new JavaStringObject(className, source)) ); @@ -43,7 +47,8 @@ static Class compile(String className, String source, List option new ValidateEventHandlerAnnotations(), new ValidateLifecycleAnnotations(), new ValidateOnTriggerAnnotations(), - new ValidateOnParentUpdateHandlerAnnotations())); + new ValidateOnParentUpdateHandlerAnnotations(), + new ValidateExportFunctionAnnotations())); if (!task.call()) { diagnostics.getDiagnostics().forEach(System.out::println); throw new RuntimeException("unable to compile source file to class:'" + className + "'"); diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/exporter/JgraphGraphMLExporter.java b/compiler/src/main/java/com/fluxtion/compiler/generation/exporter/JgraphGraphMLExporter.java index 3ec13803f..f1df7bcd2 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/exporter/JgraphGraphMLExporter.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/exporter/JgraphGraphMLExporter.java @@ -17,8 +17,8 @@ */ package com.fluxtion.compiler.generation.exporter; -import com.fluxtion.runtime.node.EventHandlerNode; import com.fluxtion.runtime.annotations.OnEventHandler; +import com.fluxtion.runtime.node.EventHandlerNode; import org.jgrapht.Graph; import org.jgrapht.ext.EdgeNameProvider; import org.jgrapht.ext.IntegerEdgeNameProvider; @@ -37,6 +37,7 @@ import java.io.PrintWriter; import java.io.Writer; import java.lang.reflect.Method; +import java.util.Set; /** @@ -104,7 +105,7 @@ public JgraphGraphMLExporter( * @throws org.xml.sax.SAXException exception during reading * @throws javax.xml.transform.TransformerConfigurationException exception during reading */ - public void export(Writer writer, Graph g) + public void export(Writer writer, Graph g, Set> exportServiceSet) throws SAXException, TransformerConfigurationException { // Prepare an XML file to receive the GraphML data PrintWriter out = new PrintWriter(writer); @@ -182,6 +183,7 @@ public void export(Writer writer, Graph g) // Add all the vertices as elements... for (V v : g.vertexSet()) { boolean isHandler = v instanceof EventHandlerNode; + boolean isServiceClass = v instanceof Class && exportServiceSet.contains(v); boolean isEventClass = v instanceof Class; if (!isHandler) { Method[] methodList = v.getClass().getMethods(); @@ -224,8 +226,12 @@ public void export(Writer writer, Graph g) attr.clear(); if (isHandler) { attr.addAttribute("", "", "text", "CDATA", "<>\n" - + vertexLabel + ":\n" - + v.getClass().getSimpleName() + + "id:" + vertexLabel + "\n" + + "class:" + v.getClass().getSimpleName() + ); + } else if (isServiceClass) { + attr.addAttribute("", "", "text", "CDATA", "<>\n" + + "class:" + ((Class) v).getSimpleName() + "\n" ); } else if (isEventClass) { attr.addAttribute("", "", "text", "CDATA", "<>\n" @@ -233,8 +239,8 @@ public void export(Writer writer, Graph g) ); } else { attr.addAttribute("", "", "text", "CDATA", "" - + vertexLabel + ":\n" - + v.getClass().getSimpleName() + + "id:" + vertexLabel + "\n" + + "class:" + v.getClass().getSimpleName() ); } handler.startElement("", "", "jGraph:label", attr); @@ -244,6 +250,8 @@ public void export(Writer writer, Graph g) attr.clear(); if (isHandler) { attr.addAttribute("", "", "properties", "CDATA", "EVENTHANDLER"); + } else if (isServiceClass) { + attr.addAttribute("", "", "properties", "CDATA", "EXPORTSERVICE"); } else if (isEventClass) { attr.addAttribute("", "", "properties", "CDATA", "EVENT"); } else { diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/exporter/PngGenerator.java b/compiler/src/main/java/com/fluxtion/compiler/generation/exporter/PngGenerator.java index 404ad62b3..406797e38 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/exporter/PngGenerator.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/exporter/PngGenerator.java @@ -77,6 +77,7 @@ public static void generatePNG(File graphmlFile, File pngFile) { style.put(mxConstants.STYLE_VERTICAL_ALIGN, mxConstants.ALIGN_MIDDLE); style.put(mxConstants.STYLE_AUTOSIZE, 1); stylesheet.putCellStyle("EVENT", style); + stylesheet.putCellStyle("EXPORTSERVICE", style); style = new Hashtable<>(); style.put(mxConstants.STYLE_SHAPE, mxConstants.SHAPE_RECTANGLE); diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/model/CbMethodHandle.java b/compiler/src/main/java/com/fluxtion/compiler/generation/model/CbMethodHandle.java index e6a94ced6..2c6edf1c6 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/model/CbMethodHandle.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/model/CbMethodHandle.java @@ -17,10 +17,7 @@ */ package com.fluxtion.compiler.generation.model; -import com.fluxtion.runtime.annotations.AfterTrigger; -import com.fluxtion.runtime.annotations.OnEventHandler; -import com.fluxtion.runtime.annotations.OnParentUpdate; -import com.fluxtion.runtime.annotations.OnTrigger; +import com.fluxtion.runtime.annotations.*; import com.fluxtion.runtime.dataflow.ParallelFunction; import java.lang.reflect.Method; @@ -32,6 +29,8 @@ */ public class CbMethodHandle { + public enum CallBackType {TRIGGER, EVENT_HANDLER, EXPORT_FUNCTION;} + /** * The callback method. */ @@ -54,6 +53,10 @@ public class CbMethodHandle { * indicates is an {@link OnEventHandler} method */ public final boolean isEventHandler; + /** + * Is a multi arg event handler + */ + private final boolean exportedHandler; public final boolean isPostEventHandler; @@ -64,12 +67,13 @@ public class CbMethodHandle { private final boolean isNoPropagateEventHandler; private final boolean failBuildOnUnguardedTrigger; private final boolean forkExecution; + private final boolean isNoPropagateFunction; public CbMethodHandle(Method method, Object instance, String variableName) { - this(method, instance, variableName, null, false); + this(method, instance, variableName, null, false, false); } - public CbMethodHandle(Method method, Object instance, String variableName, Class parameterClass, boolean isEventHandler) { + public CbMethodHandle(Method method, Object instance, String variableName, Class parameterClass, boolean isEventHandler, boolean exportedHandler) { this.method = method; this.instance = instance; this.variableName = variableName; @@ -79,12 +83,15 @@ public CbMethodHandle(Method method, Object instance, String variableName, Class OnTrigger onTriggerAnnotation = method.getAnnotation(OnTrigger.class); OnParentUpdate onParentUpdateAnnotation = method.getAnnotation(OnParentUpdate.class); OnEventHandler onEventHandlerAnnotation = method.getAnnotation(OnEventHandler.class); + NoPropagateFunction noPropagateFunction = method.getAnnotation(NoPropagateFunction.class); + this.exportedHandler = exportedHandler; this.isInvertedDirtyHandler = onTriggerAnnotation != null && !onTriggerAnnotation.dirty(); boolean parallel = (instance instanceof ParallelFunction) ? ((ParallelFunction) instance).parallelCandidate() : false; this.forkExecution = parallel || onTriggerAnnotation != null && onTriggerAnnotation.parallelExecution(); this.failBuildOnUnguardedTrigger = onTriggerAnnotation != null && onTriggerAnnotation.failBuildIfNotGuarded(); this.isGuardedParent = onParentUpdateAnnotation != null && onParentUpdateAnnotation.guarded(); this.isNoPropagateEventHandler = onEventHandlerAnnotation != null && !onEventHandlerAnnotation.propagate(); + this.isNoPropagateFunction = noPropagateFunction != null; } public Method getMethod() { @@ -127,6 +134,14 @@ public boolean isForkExecution() { return forkExecution; } + public boolean isNoPropagateFunction() { + return isNoPropagateFunction; + } + + public boolean isExportedHandler() { + return exportedHandler; + } + public String getMethodTarget() { if (Modifier.isStatic(getMethod().getModifiers())) { return instance.getClass().getSimpleName(); @@ -150,6 +165,7 @@ public String toString() { ", variableName='" + variableName + '\'' + ", parameterClass=" + parameterClass + ", isEventHandler=" + isEventHandler + + ", isExportHandler=" + exportedHandler + ", isPostEventHandler=" + isPostEventHandler + ", isInvertedDirtyHandler=" + isInvertedDirtyHandler + ", isGuardedParent=" + isGuardedParent + diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/model/ExportFunctionData.java b/compiler/src/main/java/com/fluxtion/compiler/generation/model/ExportFunctionData.java new file mode 100644 index 000000000..7664ea82e --- /dev/null +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/model/ExportFunctionData.java @@ -0,0 +1,37 @@ +package com.fluxtion.compiler.generation.model; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.List; + +public class ExportFunctionData { + + private final Method exportedmethod; + private final List functionCallBackList = new ArrayList<>(); + + public ExportFunctionData(Method exportedmethod) { + this.exportedmethod = exportedmethod; + } + + public Method getExportedmethod() { + return exportedmethod; + } + + public List getFunctionCallBackList() { + return functionCallBackList; + } + + public void addCbMethodHandle(CbMethodHandle cbMethodHandle) { + functionCallBackList.add(cbMethodHandle); + } + + public boolean isBooleanReturn() { + for (int i = 0, functionCallBackListSize = functionCallBackList.size(); i < functionCallBackListSize; i++) { + CbMethodHandle cbMethodHandle = functionCallBackList.get(i); + if (cbMethodHandle.getMethod().getReturnType() == boolean.class) { + return true; + } + } + return false; + } +} diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/model/ExportFunctionMarker.java b/compiler/src/main/java/com/fluxtion/compiler/generation/model/ExportFunctionMarker.java new file mode 100644 index 000000000..cffd852c4 --- /dev/null +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/model/ExportFunctionMarker.java @@ -0,0 +1,4 @@ +package com.fluxtion.compiler.generation.model; + +public final class ExportFunctionMarker { +} diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/model/SimpleEventProcessorModel.java b/compiler/src/main/java/com/fluxtion/compiler/generation/model/SimpleEventProcessorModel.java index beb9c675e..78da94efd 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/model/SimpleEventProcessorModel.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/model/SimpleEventProcessorModel.java @@ -45,6 +45,7 @@ import java.lang.reflect.*; import java.util.*; import java.util.Map.Entry; +import java.util.concurrent.atomic.LongAdder; import java.util.logging.Level; import java.util.stream.Collectors; @@ -347,7 +348,7 @@ private void generateComplexConstructors() { nodeFields.forEach(f -> { HashSet privateFields = new HashSet<>(); final Object field = f.instance; - LOGGER.debug("mapping constructor for var:{}", f.name); + LOGGER.debug("mapping constructor for var:{} {}", f.name, f); List directParents = dependencyGraph.getDirectParents(field); Field.MappedField[] cstrArgList = new Field.MappedField[(directParents.size()) + 200]; Class fieldClass = field.getClass(); @@ -639,6 +640,7 @@ private void eventHandlers() throws Exception { List topologicalHandlers = dependencyGraph.getSortedDependents(); List eventCbList = new ArrayList<>(); for (Object object : topologicalHandlers) { + String name = dependencyGraph.getInstanceMap().get(object); if (object instanceof EventHandlerNode) { eventCbList.add(new EventCallList((EventHandlerNode) object)); } @@ -648,7 +650,49 @@ private void eventHandlers() throws Exception { eventCbList.add(new EventCallList(object, method)); } } + Class clazz = object.getClass(); + //exported services + for (AnnotatedType annotatedInterface : clazz.getAnnotatedInterfaces()) { + if (annotatedInterface.isAnnotationPresent(ExportService.class)) { + Class interfaceType = (Class) annotatedInterface.getType(); + dependencyGraph.getConfig().addInterfaceImplementation(interfaceType); + for (Method method : interfaceType.getMethods()) { + String exportMethodName = method.getName(); + try { + method = object.getClass().getMethod(exportMethodName, method.getParameterTypes()); + LongAdder argNumber = new LongAdder(); + boolean booleanReturn = method.getReturnType() == boolean.class; + StringBuilder signature = booleanReturn + ? new StringBuilder("@Override\npublic boolean " + exportMethodName) + : new StringBuilder("@Override\npublic void " + exportMethodName); + signature.append('('); + StringJoiner sj = new StringJoiner(", "); + Type[] params = method.getGenericParameterTypes(); + for (int j = 0; j < params.length; j++) { + String param = params[j].getTypeName() + .replace("$", ".") + .replace("java.lang.", ""); + if (method.isVarArgs() && (j == params.length - 1)) // replace T[] with T... + param = param.replaceFirst("\\[\\]$", "..."); + param += " arg" + argNumber.intValue(); + sj.add(param); + argNumber.increment(); + } + signature.append(sj); + signature.append(")"); + eventCbList.add(new EventCallList(object, method, signature.toString())); + if (method.getAnnotation(NoPropagateFunction.class) == null) { + node2UpdateMethodMap.put(object, new CbMethodHandle(method, object, name)); + } + } catch (NoSuchMethodException e) { + + } + } + } + } } + + //build the no filter handlers - ready to merge in with the filtered lists for (EventCallList eventCb : eventCbList) { if (eventCb.isFiltered) { @@ -739,6 +783,7 @@ private void eventHandlers() throws Exception { //Ignore as this non-filtered dispatch - already resolved continue; } + filterDescription.setExportFunction(eventCb.exportMethod); //TODO add null filter singleton Map> handlerMap = getHandlerMap(eventClass); //get the sublist for this event handler @@ -1150,6 +1195,10 @@ public Map, Map>> getHandlerOnl return Collections.unmodifiableMap(handlerOnlyDispatchMap); } + public Map getExportedFunctionMap() { + return Collections.unmodifiableMap(dependencyGraph.getExportedFunctionMap()); + } + public Map> getParentUpdateListenerMethodMap() { return Collections.unmodifiableMap(parentUpdateListenerMethodMap); } @@ -1220,6 +1269,7 @@ private class EventCallList { final boolean isFiltered; final boolean isInverseFiltered; final Class eventTypeClass; + final Method exportMethod; private final List sortedDependents; private final List dispatchMethods; /** @@ -1237,6 +1287,7 @@ private class EventCallList { sortedDependents = dependencyGraph.getEventSortedDependents(eh); dispatchMethods = new ArrayList<>(); postDispatchMethods = new ArrayList<>(); + exportMethod = null; if (eh.eventClass() == null) { eventTypeClass = (TypeResolver.resolveRawArguments(EventHandlerNode.class, eh.getClass()))[0]; } else { @@ -1249,7 +1300,7 @@ private class EventCallList { ); Method onEventMethod = ehMethodList.iterator().next(); String name = dependencyGraph.variableName(eh); - final CbMethodHandle cbMethodHandle = new CbMethodHandle(onEventMethod, eh, name, eventTypeClass, true); + final CbMethodHandle cbMethodHandle = new CbMethodHandle(onEventMethod, eh, name, eventTypeClass, true, false); dispatchMethods.add(cbMethodHandle); node2UpdateMethodMap.put(eh, cbMethodHandle); for (int i = 1; i < sortedDependents.size(); i++) { @@ -1275,6 +1326,50 @@ private class EventCallList { isInverseFiltered = false; } + EventCallList(Object instance, Method onEventMethod, String exportedMethodName) throws Exception { + if (onEventMethod.getAnnotation(NoPropagateFunction.class) == null) { + sortedDependents = dependencyGraph.getEventSortedDependents(instance); + } else { + sortedDependents = Collections.EMPTY_LIST; + } + exportMethod = onEventMethod; + dispatchMethods = new ArrayList<>(); + postDispatchMethods = new ArrayList<>(); + eventTypeClass = ExportFunctionMarker.class; + filterId = 0; + filterString = exportedMethodName; + isIntFilter = false; + isFiltered = true; + isInverseFiltered = false; + String name = dependencyGraph.variableName(instance); + dispatchMethods.add(new CbMethodHandle(onEventMethod, instance, name, eventTypeClass, true, true)); + //check for @OnEventComplete on the root of the event tree + Method[] methodList = instance.getClass().getMethods(); + for (Method method : methodList) { + if (annotationInHierarchy(method, AfterTrigger.class)) { + postDispatchMethods.add(new CbMethodHandle(method, instance, name)); + } + } + + for (int i = 0; i < sortedDependents.size(); i++) { + Object object = sortedDependents.get(i); + if (object == instance) { + continue; + } + name = dependencyGraph.variableName(object); + methodList = object.getClass().getMethods(); + for (Method method : methodList) { + if (annotationInHierarchy(method, OnTrigger.class)) { + dispatchMethods.add(new CbMethodHandle(method, object, name)); + } + if (annotationInHierarchy(method, AfterTrigger.class) && i > 0) { + postDispatchMethods.add(new CbMethodHandle(method, object, name)); + } + } + } + + } + @SuppressWarnings("unchecked") EventCallList(Object instance, Method onEventMethod) throws Exception { String tmpFilterString = null; @@ -1282,19 +1377,12 @@ private class EventCallList { boolean tmpIsIntFilter = true; boolean tmpIsFiltered = true; boolean tmpIsInverseFiltered = false; + exportMethod = null; Set fields = ReflectionUtils.getAllFields(instance.getClass(), ReflectionUtils.withAnnotation(FilterId.class)); OnEventHandler annotation = onEventMethod.getAnnotation(OnEventHandler.class); //int attribute filter on annoatation int filterIdOverride = annotation.filterId(); - //String attribute filter on annoatation -// String genericFilter = ""; -// if (onEventMethod.getGenericParameterTypes().length == 1 && onEventMethod.getGenericParameterTypes()[0] instanceof ParameterizedType) { -// ParameterizedType pt = (ParameterizedType) onEventMethod.getGenericParameterTypes()[0]; -// final Type actualType = pt.getActualTypeArguments()[0]; -// genericFilter = actualType instanceof Class ? ((Class) actualType).getCanonicalName() : actualType.getTypeName(); -// } String filterStringOverride = annotation.filterStringFromClass() != void.class ? annotation.filterStringFromClass().getCanonicalName() : annotation.filterString(); -// filterStringOverride = filterStringOverride.isEmpty() ? genericFilter : filterStringOverride; Set s = ReflectionUtils.getAllFields(instance.getClass(), ReflectionUtils.withName(annotation.filterVariable())); if (annotation.filterVariable().length() > 0 && s.size() > 0) { java.lang.reflect.Field f = s.iterator().next(); @@ -1373,7 +1461,7 @@ private class EventCallList { postDispatchMethods = new ArrayList<>(); eventTypeClass = annotation.ofType() == void.class ? onEventMethod.getParameterTypes()[0] : annotation.ofType(); String name = dependencyGraph.variableName(instance); - dispatchMethods.add(new CbMethodHandle(onEventMethod, instance, name, eventTypeClass, true)); + dispatchMethods.add(new CbMethodHandle(onEventMethod, instance, name, eventTypeClass, true, false)); //check for @OnEventComplete on the root of the event tree Method[] methodList = instance.getClass().getMethods(); for (Method method : methodList) { diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/model/TopologicallySortedDependencyGraph.java b/compiler/src/main/java/com/fluxtion/compiler/generation/model/TopologicallySortedDependencyGraph.java index dce8aea00..278b13f12 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/model/TopologicallySortedDependencyGraph.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/model/TopologicallySortedDependencyGraph.java @@ -27,23 +27,8 @@ import com.fluxtion.compiler.generation.GenerationContext; import com.fluxtion.compiler.generation.exporter.JgraphGraphMLExporter; import com.fluxtion.compiler.generation.util.NaturalOrderComparator; -import com.fluxtion.runtime.annotations.AfterEvent; -import com.fluxtion.runtime.annotations.AfterTrigger; -import com.fluxtion.runtime.annotations.Initialise; -import com.fluxtion.runtime.annotations.NoTriggerReference; -import com.fluxtion.runtime.annotations.OnBatchEnd; -import com.fluxtion.runtime.annotations.OnBatchPause; -import com.fluxtion.runtime.annotations.OnEventHandler; -import com.fluxtion.runtime.annotations.OnParentUpdate; -import com.fluxtion.runtime.annotations.OnTrigger; -import com.fluxtion.runtime.annotations.PushReference; -import com.fluxtion.runtime.annotations.TearDown; -import com.fluxtion.runtime.annotations.TriggerEventOverride; -import com.fluxtion.runtime.annotations.builder.Config; -import com.fluxtion.runtime.annotations.builder.ConfigVariable; -import com.fluxtion.runtime.annotations.builder.ExcludeNode; -import com.fluxtion.runtime.annotations.builder.Inject; -import com.fluxtion.runtime.annotations.builder.SepNode; +import com.fluxtion.runtime.annotations.*; +import com.fluxtion.runtime.annotations.builder.*; import com.fluxtion.runtime.audit.Auditor; import com.fluxtion.runtime.event.Event; import com.fluxtion.runtime.node.Anchor; @@ -68,24 +53,9 @@ import javax.xml.transform.TransformerConfigurationException; import java.io.Writer; -import java.lang.reflect.AnnotatedElement; -import java.lang.reflect.Array; import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; -import java.lang.reflect.Modifier; -import java.lang.reflect.ParameterizedType; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.lang.reflect.*; +import java.util.*; import java.util.function.Predicate; import java.util.stream.Collectors; @@ -106,6 +76,7 @@ public class TopologicallySortedDependencyGraph implements NodeRegistry { private final Set pushEdges = new HashSet<>(); private final List topologicalHandlers = new ArrayList<>(); private final List noPushTopologicalHandlers = new ArrayList<>(); + private final Map exportedFunctionMap; private final NodeFactoryRegistration nodeFactoryRegistration; private final HashMap, CbMethodHandle> class2FactoryMethod; private final HashMap name2FactoryMethod; @@ -160,6 +131,7 @@ public TopologicallySortedDependencyGraph(List nodes, Map pub this.inst2NameTemp = HashBiMap.create(); this.class2FactoryMethod = new HashMap<>(); this.name2FactoryMethod = new HashMap<>(); + this.exportedFunctionMap = new HashMap<>(); if (nodes == null) { nodes = Collections.EMPTY_LIST; } @@ -250,6 +222,10 @@ public List getObjectSortedDependents() throws Exception { return Collections.unmodifiableList(noPushTopologicalHandlers); } + public Map getExportedFunctionMap() { + return Collections.unmodifiableMap(exportedFunctionMap); + } + //TODO this should be a list that is sorted topologically and then // with natural order public Map getRegistrationListenerMap() { @@ -562,6 +538,7 @@ public synchronized void generateDependencyTree() throws Exception { inst2Name.putAll(inst2NameTemp); inst2Name.entrySet().removeIf(o -> Anchor.class.isAssignableFrom(o.getKey().getClass())); inst2Name.entrySet().removeIf(o -> o.getKey().getClass().isAnnotationPresent(ExcludeNode.class)); + inst2Name.keySet().forEach(this::addExportedMethods); //all instances are in inst2Name, can now generate final graph for (Map.Entry entry : inst2Name.entrySet()) { @@ -681,7 +658,7 @@ private void walkDependenciesForEventHandling(Object object) throws IllegalArgum }); fields = s.toArray(fields); for (Field field : fields) { - if (!trySetAccessible(field)) { + if (!trySetAccessible(field) || Modifier.isTransient(field.getModifiers())) { continue; } Object refField = field.get(object); @@ -745,6 +722,33 @@ private void walkDependenciesForEventHandling(Object object) throws IllegalArgum } } + private void addExportedMethods(Object object) { + final Class clazz = object.getClass(); + //now find the methods for an interface + for (AnnotatedType annotatedInterface : clazz.getAnnotatedInterfaces()) { + if (annotatedInterface.isAnnotationPresent(ExportService.class)) { + Class interfaceType = (Class) annotatedInterface.getType(); + config.addInterfaceImplementation(interfaceType); + for (Method method : interfaceType.getMethods()) { + String exportMethodName = method.getName(); + Method cbMethod = method; + try { + cbMethod = object.getClass().getMethod(exportMethodName, method.getParameterTypes()); + } catch (NoSuchMethodException e) { + + } + //TODO key on method + ExportFunctionData exportFunctionData = exportedFunctionMap.computeIfAbsent( + method, n -> new ExportFunctionData(method)); + registerNode(object, null); + final String name = inst2Name.get(object); + exportFunctionData.addCbMethodHandle(new CbMethodHandle(cbMethod, object, name)); + } + } + } + + } + @SuppressWarnings("unchecked") private String getInstanceName(Field field, Object node) throws IllegalArgumentException, IllegalAccessException { Object refField = field.get(node); @@ -758,7 +762,9 @@ private String getInstanceName(Field field, Object node) throws IllegalArgumentE annotationPredicate() ).isEmpty(); addNode |= EventHandlerNode.class.isAssignableFrom(refField.getClass()) - | refField.getClass().getAnnotation(SepNode.class) != null; + | refField.getClass().getAnnotation(SepNode.class) != null + | Arrays.stream(refField.getClass().getAnnotatedInterfaces()).anyMatch(i -> i.isAnnotationPresent(ExportService.class)) + ; } if (refName == null && addNode && !inst2NameTemp.containsKey(refField) && refField != null) { LOGGER.debug("cannot find node in supplied list, but has SepNode annotation adding to managed node list"); @@ -781,7 +787,9 @@ private void implicitAddVectorMember(Object refField, Field collection) { annotationPredicate() ).isEmpty(); addNode |= EventHandlerNode.class.isAssignableFrom(refField.getClass()) - | refField.getClass().getAnnotation(SepNode.class) != null; + | refField.getClass().getAnnotation(SepNode.class) != null + | Arrays.stream(refField.getClass().getAnnotatedInterfaces()).anyMatch(i -> i.isAnnotationPresent(ExportService.class)) + ; if (addNode | collection.getAnnotation(SepNode.class) != null) { inst2NameTemp.put(refField, nameNode(refField)); } @@ -800,6 +808,7 @@ private Predicate annotationPredicate() { .or(ReflectionUtils.withAnnotation(TearDown.class)) .or(ReflectionUtils.withAnnotation(Initialise.class)) .or(ReflectionUtils.withAnnotation(TriggerEventOverride.class)) + .or(ReflectionUtils.withAnnotation(ExportService.class)) ; } @@ -807,12 +816,14 @@ private Predicate eventHandlingAnnotationPredicate() { return ReflectionUtils.withAnnotation(OnEventHandler.class) .or(ReflectionUtils.withAnnotation(OnTrigger.class)) .or(ReflectionUtils.withAnnotation(TriggerEventOverride.class)) + .or(ReflectionUtils.withAnnotation(ExportService.class)) ; } private boolean handlesEvents(Object obj) { return EventHandlerNode.class.isAssignableFrom(obj.getClass()) - || !ReflectionUtils.getAllMethods(obj.getClass(), eventHandlingAnnotationPredicate()).isEmpty(); + || !ReflectionUtils.getAllMethods(obj.getClass(), eventHandlingAnnotationPredicate()).isEmpty() + || Arrays.stream(obj.getClass().getAnnotatedInterfaces()).anyMatch(i -> i.isAnnotationPresent(ExportService.class)); } private void walkDependencies(Object object) throws IllegalArgumentException, IllegalAccessException { @@ -1047,6 +1058,7 @@ public void exportAsGraphMl(Writer writer, boolean addEvents) throws SAXExceptio JgraphGraphMLExporter mlExporter = new JgraphGraphMLExporter<>(np, np, new IntegerEdgeNameProvider<>(), new IntegerEdgeNameProvider<>()); @SuppressWarnings("unchecked") SimpleDirectedGraph exportGraph = (SimpleDirectedGraph) graph.clone(); + Set> exportServiceSet = new HashSet<>(); if (addEvents) { graph.vertexSet().forEach((t) -> { Method[] methodList = t.getClass().getMethods(); @@ -1065,6 +1077,15 @@ public void exportAsGraphMl(Writer writer, boolean addEvents) throws SAXExceptio exportGraph.addEdge(eventClass, t); } } + for (AnnotatedType annotatedInterface : t.getClass().getAnnotatedInterfaces()) { + if (annotatedInterface.isAnnotationPresent(ExportService.class)) { + Class interfaceType = (Class) annotatedInterface.getType(); + exportServiceSet.add(interfaceType); + exportGraph.addVertex(interfaceType); + exportGraph.addEdge(interfaceType, t); + } + } +// t.getClass().getInterfaces() }); // pushEdges.stream() @@ -1077,11 +1098,10 @@ public void exportAsGraphMl(Writer writer, boolean addEvents) throws SAXExceptio // }); } - mlExporter.export(writer, exportGraph);//new EdgeReversedGraph(graph)); + mlExporter.export(writer, exportGraph, exportServiceSet);//new EdgeReversedGraph(graph)); } private String nameNode(Object node) { return nameStrategy.mappedNodeName(node); } - } diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/BasicTypeSerializer.java b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/BasicTypeSerializer.java new file mode 100644 index 000000000..b15d69a6c --- /dev/null +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/BasicTypeSerializer.java @@ -0,0 +1,44 @@ +package com.fluxtion.compiler.generation.serialiser; + + +import org.apache.commons.text.StringEscapeUtils; + +public interface BasicTypeSerializer { + + static String stringToSource(FieldContext fieldContext) { + return "\"" + StringEscapeUtils.escapeJava(fieldContext.getInstanceToMap()) + "\""; + } + + static String charToSource(FieldContext fieldContext) { + return "'" + StringEscapeUtils.escapeJava(fieldContext.getInstanceToMap().toString()) + "'"; + } + + static String longToSource(FieldContext fieldContext) { + return fieldContext.getInstanceToMap().toString() + "L"; + } + + static String intToSource(FieldContext fieldContext) { + return fieldContext.getInstanceToMap().toString(); + } + + static String shortToSource(FieldContext fieldContext) { + return "(short)" + fieldContext.getInstanceToMap().toString(); + } + + static String byteToSource(FieldContext fieldContext) { + return "(byte)" + fieldContext.getInstanceToMap().toString(); + } + + static String doubleToSource(FieldContext fieldContext) { + Double doubleVal = fieldContext.getInstanceToMap(); + return doubleVal.isNaN() ? "Double.NaN" : doubleVal.toString(); + } + + static String floatToSource(FieldContext fieldContext) { + return fieldContext.getInstanceToMap().toString() + "f"; + } + + static String booleanToSource(FieldContext fieldContext) { + return fieldContext.getInstanceToMap().toString(); + } +} diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/CollectionSerializer.java b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/CollectionSerializer.java new file mode 100644 index 000000000..20465dc18 --- /dev/null +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/CollectionSerializer.java @@ -0,0 +1,84 @@ +package com.fluxtion.compiler.generation.serialiser; + +import com.fluxtion.compiler.generation.model.Field; +import com.fluxtion.runtime.serializer.MapBuilder; +import org.jetbrains.annotations.NotNull; + +import java.util.*; +import java.util.stream.Collectors; + +public interface CollectionSerializer { + + static String mapToSource(FieldContext> fieldContext) { + fieldContext.getImportList().add(MapBuilder.class); + Map values = fieldContext.getInstanceToMap(); + Map newMap = new HashMap<>(); + values.entrySet().stream().forEach(item -> { + Object key = item.getKey(); + Object value = item.getValue(); + for (Field nodeField : fieldContext.getNodeFields()) { + if (nodeField.instance.equals(key)) { + key = nodeField.instance; + break; + } + } + for (Field nodeField : fieldContext.getNodeFields()) { + if (nodeField.instance.equals(value)) { + value = nodeField.instance; + break; + } + } + newMap.put(key, value); + }); + return newMap.entrySet().stream().sorted(Comparator.comparing(e -> e.getKey().toString())).map( + entry -> { + String keyString = fieldContext.mapToJavaSource(entry.getKey()); + String valueString = fieldContext.mapToJavaSource(entry.getValue()); + return "(" + keyString + ", " + valueString + ")"; + } + ).collect(Collectors.joining(".put", "MapBuilder.builder().put", ".build()")); + } + + @NotNull + static String setToSource(FieldContext> fieldContext) { + fieldContext.getImportList().add(Arrays.class); + fieldContext.getImportList().add(HashSet.class); + Set values = fieldContext.getInstanceToMap(); + Set newList = new HashSet<>(); + values.stream().forEach(item -> { + boolean foundMatch = false; + for (Field nodeField : fieldContext.getNodeFields()) { + if (nodeField.instance.equals(item)) { + newList.add(nodeField.instance); + foundMatch = true; + break; + } + } + if (!foundMatch) { + newList.add(item); + } + }); + return newList.stream().map(f -> fieldContext.mapToJavaSource(f)).collect(Collectors.joining(", ", "new HashSet<>(Arrays.asList(", "))")); + } + + @NotNull + static String listToSource(FieldContext> fieldContext) { + fieldContext.getImportList().add(Arrays.class); + List values = fieldContext.getInstanceToMap(); + List newList = new ArrayList<>(); + values.stream().forEach(item -> { + boolean foundMatch = false; + for (Field nodeField : fieldContext.getNodeFields()) { + if (nodeField.instance.equals(item)) { + newList.add(nodeField.instance); + foundMatch = true; + break; + } + } + if (!foundMatch) { + newList.add(item); + } + }); + return newList.stream().map(f -> fieldContext.mapToJavaSource(f)).collect(Collectors.joining(", ", "Arrays.asList(", ")")); + } +} diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/FieldContext.java b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/FieldContext.java index 0cecb42ea..0dd166861 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/FieldContext.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/FieldContext.java @@ -1,30 +1,29 @@ package com.fluxtion.compiler.generation.serialiser; import com.fluxtion.compiler.generation.model.Field; +import lombok.AccessLevel; +import lombok.Getter; import java.util.List; import java.util.Set; +@Getter public class FieldContext { private final T instanceToMap; private final List nodeFields; private final Set> importList; + @Getter(AccessLevel.NONE) + private final MapFieldToJavaSource mapFieldToJavaSource; - public FieldContext(T instanceToMap, List nodeFields, Set> importList) { + public FieldContext(T instanceToMap, List nodeFields, Set> importList, MapFieldToJavaSource mapFieldToJavaSource) { this.instanceToMap = instanceToMap; this.nodeFields = nodeFields; this.importList = importList; + this.mapFieldToJavaSource = mapFieldToJavaSource; } - public T getInstanceToMap() { - return instanceToMap; + public String mapToJavaSource(Object instance) { + return mapFieldToJavaSource.mapToJavaSource(instance, nodeFields, importList); } - public List getNodeFields() { - return nodeFields; - } - - public Set> getImportList() { - return importList; - } } diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/FieldSerializer.java b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/FieldSerializer.java index bcb4ca9ce..f6f635ea7 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/FieldSerializer.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/FieldSerializer.java @@ -4,26 +4,15 @@ import com.fluxtion.compiler.generation.GenerationContext; import com.fluxtion.compiler.generation.model.Field; import com.fluxtion.compiler.generation.util.ClassUtils; -import com.fluxtion.runtime.dataflow.FlowFunction; -import com.fluxtion.runtime.dataflow.function.MergeProperty; +import com.fluxtion.runtime.dataflow.groupby.GroupByKey; import com.fluxtion.runtime.dataflow.helpers.GroupingFactory; -import com.fluxtion.runtime.partition.LambdaReflection; -import com.fluxtion.runtime.partition.LambdaReflection.MethodReferenceReflection; -import org.apache.commons.lang3.StringEscapeUtils; +import org.jetbrains.annotations.NotNull; import java.beans.PropertyDescriptor; import java.lang.reflect.Array; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.ServiceLoader; -import java.util.Set; +import java.util.*; import java.util.function.Function; import java.util.stream.Collectors; @@ -35,67 +24,53 @@ *
  • Registered programmatically with {@link EventProcessorConfig#addClassSerializer(Class, Function)}
  • * */ -public class FieldSerializer { +public class FieldSerializer implements MapFieldToJavaSource { private final Map, Function> classSerializerMap = new HashMap<>(); - private final List namingStrategies; + private final List serviceLoadedFieldSerializerList; public FieldSerializer(EventProcessorConfig config) { if (config != null) { classSerializerMap.putAll(config.getClassSerializerMap()); } ServiceLoader loadServices; - namingStrategies = new ArrayList<>(); + serviceLoadedFieldSerializerList = new ArrayList<>(); if (GenerationContext.SINGLETON != null && GenerationContext.SINGLETON.getClassLoader() != null) { loadServices = ServiceLoader.load(FieldToSourceSerializer.class, GenerationContext.SINGLETON.getClassLoader()); } else { loadServices = ServiceLoader.load(FieldToSourceSerializer.class); } - loadServices.forEach(namingStrategies::add); - } - - private static boolean _nativeSerializerSupport(Class type) { - boolean zeroArg = false; - try { - type.getDeclaredConstructor(); - zeroArg = true; - } catch (NoSuchMethodException | SecurityException ex) { - } - return _nativeTypeSupported(type) || zeroArg; + loadServices.forEach(serviceLoadedFieldSerializerList::add); } private static boolean _nativeTypeSupported(Class type) { - return type.isPrimitive() - || type == String.class - || type == Class.class - || type.isEnum() - || List.class.isAssignableFrom(type) - || Set.class.isAssignableFrom(type) - || type.isArray() - || MethodReferenceReflection.class.isAssignableFrom(type) - ; + return type.isEnum() || type.isArray(); } public boolean typeSupported(Class type) { return classSerializerMap.containsKey(type) - || _nativeSerializerSupport(type) - || namingStrategies.stream().anyMatch(s -> s.typeSupported(type)); + || _nativeTypeSupported(type) + || Arrays.stream(type.getDeclaredConstructors()).anyMatch(c -> c.getParameterCount() == 0) + || classSerializerMap.keySet().stream().anyMatch(c -> c.isAssignableFrom(type)) + || serviceLoadedFieldSerializerList.stream().anyMatch(s -> s.typeSupported(type)); } + @Override public String mapToJavaSource(Object primitiveVal, List nodeFields, Set> importList) { Class primitiveValClass = primitiveVal.getClass(); + FieldContext f = new FieldContext(primitiveVal, nodeFields, importList, this); Function serializeFunction = classSerializerMap.get(primitiveValClass); - FieldContext f = new FieldContext(primitiveVal, nodeFields, importList); if (serializeFunction != null) { return serializeFunction.apply(f); } + Optional> matchingClass = classSerializerMap.keySet().stream().filter(clazz -> clazz.isAssignableFrom(primitiveValClass)).findFirst(); + if (matchingClass.isPresent()) { + return classSerializerMap.get(matchingClass.get()).apply(f); + } if (_nativeTypeSupported(primitiveValClass)) { return _mapToJavaSource(primitiveVal, nodeFields, importList); } - Optional optionalSerialise = namingStrategies.stream() - .filter(s -> s.typeSupported(primitiveValClass)) - .findFirst() - .map(m -> m.mapToSource(f)); + Optional optionalSerialise = serviceLoadedFieldSerializerList.stream().filter(s -> s.typeSupported(primitiveValClass)).findFirst().map(m -> m.mapToSource(f)); if (optionalSerialise.isPresent()) { return optionalSerialise.get(); } @@ -106,165 +81,20 @@ public boolean propertySupported(PropertyDescriptor property, Field field, List< return _propertySupported(property, field, nodeFields); } - public String mapPropertyToJavaSource(PropertyDescriptor property, Field field, List nodeFields, - Set> importList) { + public String mapPropertyToJavaSource(PropertyDescriptor property, Field field, List nodeFields, Set> importList) { return _mapPropertyToJavaSource(property, field, nodeFields, importList); } private String _mapToJavaSource(Object primitiveVal, List nodeFields, Set> importList) { - Class clazz = primitiveVal.getClass(); - String primitiveSuffix = ""; - String primitivePrefix = ""; + Class clazz = primitiveVal.getClass(); Object original = primitiveVal; - if (List.class.isAssignableFrom(clazz)) { - importList.add(Arrays.class); - List values = (List) primitiveVal; - List newList = new ArrayList(); - values.stream().forEach(item -> { - boolean foundMatch = false; - for (Field nodeField : nodeFields) { - if (nodeField.instance.equals(item)) { - newList.add(nodeField.instance); - foundMatch = true; - break; - } - } - if (!foundMatch) { - newList.add(item); - } - - }); - primitiveVal = newList.stream().map(f -> _mapToJavaSource(f, nodeFields, importList)).collect(Collectors.joining(", ", "Arrays.asList(", ")")); - } - if (Set.class.isAssignableFrom(clazz)) { - importList.add(Arrays.class); - importList.add(HashSet.class); - Set values = (Set) primitiveVal; - Set newList = new HashSet(); - values.stream().forEach(item -> { - boolean foundMatch = false; - for (Field nodeField : nodeFields) { - if (nodeField.instance.equals(item)) { - newList.add(nodeField.instance); - foundMatch = true; - break; - } - } - if (!foundMatch) { - newList.add(item); - } - - }); - primitiveVal = newList.stream() - .map(f -> _mapToJavaSource(f, nodeFields, importList)). - collect(Collectors.joining(", ", "new HashSet<>(Arrays.asList(", "))")); - } + boolean foundMatch = false; if (clazz.isArray()) { - Class arrayType = clazz.getComponentType(); - importList.add(arrayType); - - ArrayList strings = new ArrayList<>(); - int length = Array.getLength(primitiveVal); - for (int i = 0; i < length; i++) { - Object arrayElement = Array.get(primitiveVal, i); - for (Field nodeField : nodeFields) { - if (nodeField.instance.equals(arrayElement)) { - arrayElement = (nodeField.instance); - break; - } - } - strings.add(_mapToJavaSource(arrayElement, nodeFields, importList)); - } -// Object[] values = (Object[]) primitiveVal; - primitiveVal = strings.stream().collect(Collectors.joining(", ", "new " + arrayType.getSimpleName() + "[]{", "}")); - } - if (clazz.isEnum()) { - primitiveVal = clazz.getSimpleName() + "." + ((Enum) primitiveVal).name(); + primitiveVal = serializeArray(primitiveVal, nodeFields, importList, clazz); + } else if (clazz.isEnum()) { + primitiveVal = clazz.getSimpleName() + "." + ((Enum) primitiveVal).name(); importList.add(clazz); } - if (clazz == Float.class || clazz == float.class) { - primitiveSuffix = "f"; - } - if (clazz == Double.class || clazz == double.class) { - if (Double.isNaN((double) primitiveVal)) { - primitiveVal = "Double.NaN"; - } - } - if (clazz == Byte.class || clazz == byte.class) { - primitivePrefix = "(byte)"; - } - if (clazz == Short.class || clazz == short.class) { - primitivePrefix = "(short)"; - } - if (clazz == Long.class || clazz == long.class) { - primitiveSuffix = "L"; - } - if (clazz == Character.class || clazz == char.class) { - primitivePrefix = "'"; - primitiveSuffix = "'"; - primitiveVal = StringEscapeUtils.escapeJava(primitiveVal.toString()); - } - if (clazz == String.class) { - primitivePrefix = "\""; - primitiveSuffix = "\""; - primitiveVal = StringEscapeUtils.escapeJava(primitiveVal.toString()); - } - if (clazz == Class.class) { - importList.add((Class) primitiveVal); - primitiveVal = ((Class) primitiveVal).getSimpleName() + ".class"; - } - boolean foundMatch = false; - if (MergeProperty.class.isAssignableFrom(clazz)) { - importList.add(MergeProperty.class); - MergeProperty mergeProperty = (MergeProperty) primitiveVal; - LambdaReflection.SerializableBiConsumer setValue = mergeProperty.getSetValue(); - String containingClass = setValue.getContainingClass().getSimpleName(); - String methodName = setValue.method().getName(); - String lambda = containingClass + "::" + methodName; - String triggerName = "null"; - // - FlowFunction trigger = mergeProperty.getTrigger(); - for (Field nodeField : nodeFields) { - if (nodeField.instance == trigger) { - triggerName = nodeField.name; - break; - } - } - primitiveVal = "new MergeProperty<>(" - + triggerName + ", " + lambda + "," + mergeProperty.isTriggering() + "," + mergeProperty.isMandatory() + ")"; - } - if (MethodReferenceReflection.class.isAssignableFrom(clazz)) { - MethodReferenceReflection ref = (MethodReferenceReflection) primitiveVal; - importList.add(ref.getContainingClass()); - - if (ref.isDefaultConstructor()) { - primitiveVal = ref.getContainingClass().getSimpleName() + "::new"; - } else if (ref.captured().length > 0) { - //see if we can find the reference and set the instance - Object functionInstance = ref.captured()[0]; - for (Field nodeField : nodeFields) { - if (nodeField.instance == functionInstance) { - primitiveVal = nodeField.name + "::" + ref.method().getName(); - foundMatch = true; - break; - } - } - if (!foundMatch) { - primitiveVal = "new " + ref.getContainingClass().getSimpleName() + "()::" + ref.method().getName(); - } - } else { - if (ref.getContainingClass().getTypeParameters().length > 0) { - String typeParam = " nodeFields, Set break; } } - - if (!foundMatch && original == primitiveVal - && !org.apache.commons.lang3.ClassUtils.isPrimitiveOrWrapper(clazz) - && !String.class.isAssignableFrom(clazz) - && clazz.getCanonicalName() != null - ) { + if (!foundMatch && original == primitiveVal && clazz.getCanonicalName() != null) { importList.add(clazz); primitiveVal = "new " + (clazz).getSimpleName() + "()"; } - return primitivePrefix + primitiveVal.toString() + primitiveSuffix; + return primitiveVal.toString(); } - private String _mapPropertyToJavaSource(PropertyDescriptor property, Field field, List nodeFields, - Set> importList) { + @NotNull + private Object serializeArray(Object primitiveVal, List nodeFields, Set> importList, Class clazz) { + Class arrayType = clazz.getComponentType(); + importList.add(arrayType); + ArrayList strings = new ArrayList<>(); + int length = Array.getLength(primitiveVal); + for (int i = 0; i < length; i++) { + Object arrayElement = Array.get(primitiveVal, i); + for (Field nodeField : nodeFields) { + if (nodeField.instance.equals(arrayElement)) { + arrayElement = (nodeField.instance); + break; + } + } + strings.add(mapToJavaSource(arrayElement, nodeFields, importList)); + } + primitiveVal = strings.stream().collect(Collectors.joining(", ", "new " + arrayType.getSimpleName() + "[]{", "}")); + return primitiveVal; + } + + private String _mapPropertyToJavaSource(PropertyDescriptor property, Field field, List nodeFields, Set> importList) { String ret = null; if (!ClassUtils.isPropertyTransient(property, field)) { try { @@ -341,6 +185,10 @@ public String buildTypeDeclaration(Field field, Function, String> class String genericDeclaration = "<" + inputClass + ", " + returnType + ", ?, ?>"; return genericDeclaration; } + if (instance instanceof GroupByKey) { + GroupByKey groupByKey = (GroupByKey) instance; + return "<" + classNameConverter.apply(groupByKey.getValueClass()) + ">"; + } return ""; } diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/IoSerializer.java b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/IoSerializer.java index 9ee1a0d94..3b11e969b 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/IoSerializer.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/IoSerializer.java @@ -37,7 +37,7 @@ static String urlToSource(FieldContext fieldContext) { static String inetSocketAddressToSource(FieldContext fieldContext) { fieldContext.getImportList().add(InetSocketAddress.class); InetSocketAddress inetSockeAddress = fieldContext.getInstanceToMap(); - return "new InetSocketAddress(" + + return "InetSocketAddress.createUnresolved(" + "\"" + StringEscapeUtils.escapeJava(inetSockeAddress.getHostString()) + "\", " + inetSockeAddress.getPort() + ")"; } diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/MapFieldToJavaSource.java b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/MapFieldToJavaSource.java new file mode 100644 index 000000000..cdd002336 --- /dev/null +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/MapFieldToJavaSource.java @@ -0,0 +1,10 @@ +package com.fluxtion.compiler.generation.serialiser; + +import com.fluxtion.compiler.generation.model.Field; + +import java.util.List; +import java.util.Set; + +public interface MapFieldToJavaSource { + String mapToJavaSource(Object primitiveVal, List nodeFields, Set> importList); +} diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/MetaSerializer.java b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/MetaSerializer.java new file mode 100644 index 000000000..2af0d316c --- /dev/null +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/serialiser/MetaSerializer.java @@ -0,0 +1,72 @@ +package com.fluxtion.compiler.generation.serialiser; + +import com.fluxtion.compiler.generation.model.Field; +import com.fluxtion.runtime.dataflow.FlowFunction; +import com.fluxtion.runtime.dataflow.function.MergeProperty; +import com.fluxtion.runtime.partition.LambdaReflection; + +import java.io.File; + +public interface MetaSerializer { + + static String classToSource(FieldContext> fieldContext) { + fieldContext.getImportList().add(File.class); + Class clazz = fieldContext.getInstanceToMap(); + return clazz.getCanonicalName() + ".class"; + } + + static String mergePropertyToSource(FieldContext> fieldContext) { + fieldContext.getImportList().add(MergeProperty.class); + MergeProperty mergeProperty = fieldContext.getInstanceToMap(); + LambdaReflection.SerializableBiConsumer setValue = mergeProperty.getSetValue(); + String containingClass = setValue.getContainingClass().getSimpleName(); + String methodName = setValue.method().getName(); + String lambda = containingClass + "::" + methodName; + String triggerName = "null"; + // + FlowFunction trigger = mergeProperty.getTrigger(); + for (Field nodeField : fieldContext.getNodeFields()) { + if (nodeField.instance == trigger) { + triggerName = nodeField.name; + break; + } + } + return "new MergeProperty<>(" + + triggerName + ", " + lambda + "," + mergeProperty.isTriggering() + "," + mergeProperty.isMandatory() + ")"; + } + + static String methodReferenceToSource(FieldContext fieldContext) { + LambdaReflection.MethodReferenceReflection ref = fieldContext.getInstanceToMap(); + fieldContext.getImportList().add(ref.getContainingClass()); + String sourceString = ""; + boolean foundMatch = false; + if (ref.isDefaultConstructor()) { + sourceString = ref.getContainingClass().getSimpleName() + "::new"; + } else if (ref.captured().length > 0) { + //see if we can find the reference and set the instance + Object functionInstance = ref.captured()[0]; + for (Field nodeField : fieldContext.getNodeFields()) { + if (nodeField.instance == functionInstance) { + sourceString = nodeField.name + "::" + ref.method().getName(); + foundMatch = true; + break; + } + } + if (!foundMatch) { + sourceString = "new " + ref.getContainingClass().getSimpleName() + "()::" + ref.method().getName(); + } + } else { + if (ref.getContainingClass().getTypeParameters().length > 0) { + String typeParam = " fieldContext) { fieldContext.getImportList().add(ZonedDateTime.class); fieldContext.getImportList().add(ZoneId.class); ZonedDateTime zoneDateTime = fieldContext.getInstanceToMap(); - return "ZonedDateTime.of(%N)".replace( "%N", zoneDateTime.getYear() + "," + zoneDateTime.getMonthValue() + "," + zoneDateTime.getDayOfMonth() + "," + diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/targets/InMemoryEventProcessor.java b/compiler/src/main/java/com/fluxtion/compiler/generation/targets/InMemoryEventProcessor.java index 0db085996..5c423212d 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/targets/InMemoryEventProcessor.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/targets/InMemoryEventProcessor.java @@ -1,7 +1,10 @@ package com.fluxtion.compiler.generation.targets; +import com.fluxtion.compiler.EventProcessorConfig; import com.fluxtion.compiler.builder.filter.FilterDescription; +import com.fluxtion.compiler.generation.compiler.classcompiler.StringCompilation; import com.fluxtion.compiler.generation.model.CbMethodHandle; +import com.fluxtion.compiler.generation.model.ExportFunctionData; import com.fluxtion.compiler.generation.model.Field; import com.fluxtion.compiler.generation.model.SimpleEventProcessorModel; import com.fluxtion.compiler.generation.util.ClassUtils; @@ -12,7 +15,9 @@ import com.fluxtion.runtime.audit.Auditor; import com.fluxtion.runtime.callback.CallbackDispatcher; import com.fluxtion.runtime.callback.EventProcessorCallbackInternal; +import com.fluxtion.runtime.callback.ExportFunctionAuditEvent; import com.fluxtion.runtime.callback.InternalEventProcessor; +import com.fluxtion.runtime.dataflow.groupby.MutableTuple; import com.fluxtion.runtime.event.Event; import com.fluxtion.runtime.input.EventFeed; import com.fluxtion.runtime.input.SubscriptionManager; @@ -23,20 +28,13 @@ import com.fluxtion.runtime.node.MutableEventProcessorContext; import lombok.Data; import lombok.SneakyThrows; +import lombok.Value; import lombok.extern.slf4j.Slf4j; import org.reflections.ReflectionUtils; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.BitSet; -import java.util.Collections; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Set; +import java.util.*; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BooleanSupplier; import java.util.stream.Collectors; @@ -45,6 +43,7 @@ public class InMemoryEventProcessor implements EventProcessor, StaticEventProcessor, InternalEventProcessor, Lifecycle, BatchHandler { private final SimpleEventProcessorModel simpleEventProcessorModel; + private final EventProcessorConfig config; private final MutableEventProcessorContext context; private final EventProcessorCallbackInternal callbackDispatcher; private final SubscriptionManagerNode subscriptionManager; @@ -57,14 +56,17 @@ public class InMemoryEventProcessor implements EventProcessor, StaticEventProces private final Map, List> noFilterEventHandlerToBitsetMap = new HashMap<>(); private final Map> filteredEventHandlerToBitsetMap = new HashMap<>(); private final List auditors = new ArrayList<>(); - private boolean buffering = false; + private final Set> eventCompleteInvokeSet = new HashSet<>(); + public boolean buffering = false; private Object currentEvent; - private boolean processing = false; + public boolean processing = false; private boolean isDefaultHandling; private boolean initCalled = false; + private Object exportingWrapper; - public InMemoryEventProcessor(SimpleEventProcessorModel simpleEventProcessorModel) { + public InMemoryEventProcessor(SimpleEventProcessorModel simpleEventProcessorModel, EventProcessorConfig config) { this.simpleEventProcessorModel = simpleEventProcessorModel; + this.config = config; try { context = getNodeById(EventProcessorContext.DEFAULT_NODE_NAME); callbackDispatcher = getNodeById(CallbackDispatcher.DEFAULT_NODE_NAME); @@ -101,8 +103,7 @@ public void bufferEvent(Object event) { @Override public void triggerCalculation() { log.debug("dirtyBitset, after:{}", dirtyBitset); - log.debug("======== GRAPH CYCLE START BUFFER EVENT ========"); - log.debug("======== process event ========"); + log.debug("======== GRAPH CYCLE START TRIGGER ========"); for (int i = dirtyBitset.nextSetBit(0); i >= 0; i = dirtyBitset.nextSetBit(i + 1)) { log.debug("event dispatch bitset index[{}] handler[{}::{}]", i, @@ -111,15 +112,17 @@ public void triggerCalculation() { ); eventHandlers.get(i).onEvent(null); } - dirtyBitset.clear(); +// dirtyBitset.clear(); dirtyBitset.or(postProcessBufferingBitset); postProcessBufferingBitset.clear(); postEventProcessing(); buffering = false; + log.debug("======== GRAPH CYCLE END TRIGGER ========"); } public void postEventProcessing() { log.debug("======== eventComplete ========"); + eventCompleteInvokeSet.clear(); for (int i = dirtyBitset.length(); (i = dirtyBitset.previousSetBit(i - 1)) >= 0; ) { log.debug("check for postprocessing index[{}]", i); if (eventHandlers.get(i).willInvokeEventComplete()) { @@ -149,6 +152,10 @@ public void postEventProcessing() { currentEvent = null; } + public void dispatchQueuedCallbacks() { + callbackDispatcher.dispatchQueuedCallbacks(); + } + private void subclassDispatchSearch(BitSet updateBitset) { if (updateBitset.isEmpty()) { Set> eventClassSet = new HashSet<>(); @@ -159,7 +166,7 @@ private void subclassDispatchSearch(BitSet updateBitset) { .filter(c -> c.isInstance(currentEvent)) .findFirst() .ifPresent(c -> { - if (c.isAssignableFrom(Event.class)) { + if (Event.class.isAssignableFrom(c)) { FilterDescription filterDescription = FilterDescription.build(currentEvent); filterDescription.setEventClass((Class) c); filteredEventHandlerToBitsetMap.getOrDefault(filterDescription, Collections.emptyList()).forEach(updateBitset::set); @@ -261,7 +268,7 @@ private Object checkForDefaultEventHandling(Object event) { return mapped; } - private void auditNewEvent(Object event) { + public void auditNewEvent(Object event) { if (Event.class.isAssignableFrom(event.getClass())) { auditors.stream() .filter(a -> Auditor.FirstAfterEvent.class.isAssignableFrom(a.getClass())) @@ -279,14 +286,30 @@ private void auditNewEvent(Object event) { } } + public void nodeInvoked(Object node, String nodeName, String methodName, Object event) { + auditors.stream() + .filter(Auditor::auditInvocations) + .forEachOrdered(a -> a.nodeInvoked(node, nodeName, methodName, event)); + } + @Override public void batchPause() { + processing = true; + auditNewEvent(LifecycleEvent.BatchPause); simpleEventProcessorModel.getBatchPauseMethods().forEach(this::invokeRunnable); + postEventProcessing(); + callbackDispatcher.dispatchQueuedCallbacks(); + processing = false; } @Override public void batchEnd() { + processing = true; + auditNewEvent(LifecycleEvent.BatchEnd); simpleEventProcessorModel.getBatchEndMethods().forEach(this::invokeRunnable); + postEventProcessing(); + callbackDispatcher.dispatchQueuedCallbacks(); + processing = false; } @Override @@ -312,7 +335,9 @@ public void removeEventFeed(EventFeed eventProcessorFeed) { public void init() { initCalled = true; buildDispatch(); + auditNewEvent(LifecycleEvent.Init); simpleEventProcessorModel.getInitialiseMethods().forEach(this::invokeRunnable); + postEventProcessing(); } @Override @@ -320,7 +345,12 @@ public void start() { if (!initCalled) { throw new RuntimeException("init() must be called before start()"); } + processing = true; + auditNewEvent(LifecycleEvent.Start); simpleEventProcessorModel.getStartMethods().forEach(this::invokeRunnable); + postEventProcessing(); + callbackDispatcher.dispatchQueuedCallbacks(); + processing = false; } @Override @@ -328,13 +358,20 @@ public void stop() { if (!initCalled) { throw new RuntimeException("init() must be called before start()"); } + processing = true; + auditNewEvent(LifecycleEvent.Stop); simpleEventProcessorModel.getStopMethods().forEach(this::invokeRunnable); + postEventProcessing(); + callbackDispatcher.dispatchQueuedCallbacks(); + processing = false; } @Override public void tearDown() { initCalled = false; + auditNewEvent(LifecycleEvent.TearDown); simpleEventProcessorModel.getTearDownMethods().forEach(this::invokeRunnable); + postEventProcessing(); } @@ -467,7 +504,7 @@ private void buildDispatch() { //calculate event handler bitset id's for an event without filtering simpleEventProcessorModel.getDispatchMap().forEach((key, value) -> noFilterEventHandlerToBitsetMap.put( - key, + JavaGenHelper.mapPrimitiveToWrapper(key), value.getOrDefault(FilterDescription.DEFAULT_FILTER, Collections.emptyList()).stream() .filter(Objects::nonNull) .filter(CbMethodHandle::isEventHandler) @@ -476,11 +513,90 @@ private void buildDispatch() { ) ); eventHandlers.forEach(Node::init); - Set duplicatesOnEventComplete = new HashSet<>(); - eventHandlers.forEach(n -> n.deDuplicateOnEventComplete(duplicatesOnEventComplete)); registerAuditors(); } + @Value + private static class CallbackInstance { + Object instance; + String variableName; + + private CallbackInstance(CbMethodHandle cbMethodHandle) { + instance = cbMethodHandle.getInstance(); + variableName = cbMethodHandle.getVariableName(); + } + } + + @SneakyThrows + @SuppressWarnings("unchecked") + public T getExportedService() { + if (exportingWrapper != null) { + return (T) exportingWrapper; + } + String packageName = "com.fluxtion.compiler.generation.targets.temp"; + String className = "InMemoryExportWrapper_" + UUID.randomUUID().toString().replace("-", "_"); + String fqn = packageName + "." + className; + + String additionalInterfaces = ""; + if (!config.interfacesToImplement().isEmpty()) { + additionalInterfaces = config.interfacesToImplement().stream() + .map(Class::getCanonicalName) + .collect(Collectors.joining(", ", " implements ", "")); + } + + Map exportedFunctionMap = simpleEventProcessorModel.getExportedFunctionMap(); + Set exportCbSet = exportedFunctionMap.values().stream() + .map(ExportFunctionData::getFunctionCallBackList).flatMap(List::stream) + .map(CallbackInstance::new) + .collect(Collectors.toSet()); + + String declarations = exportCbSet.stream() + .map(c -> c.getInstance().getClass().getCanonicalName() + " " + c.getVariableName()) + .collect(Collectors.joining( + ";\n\t", "\tInMemoryEventProcessor processor;\n\t", + ";\n\tprivate ExportFunctionAuditEvent functionAudit = new ExportFunctionAuditEvent();\n")); + + String constructor = exportCbSet.stream() + .map(c -> c.getVariableName() + " = processor.getNodeById(\"" + c.getVariableName() + "\")") + .collect(Collectors.joining( + ";\n\t", + "public " + className + "(InMemoryEventProcessor processor) throws java.lang.NoSuchFieldException {\n" + + "\tthis.processor = processor;\n\t", + ";\n}\n")); + + String delegateOnEvent = "public void onEvent(Object o){\n\tprocessor.onEvent(o);\n}\n\n" + + "public void init(){\n\tprocessor.init();\n}\n\n" + + "public void start(){\n\tprocessor.start();\n}\n\n" + + "public void stop(){\n\tprocessor.stop();\n}\n\n" + + "public InMemoryEventProcessor processor(){\n\treturn processor;\n}\n\n" + + "public void tearDown(){\n\tprocessor.tearDown();\n}"; + + List keys = new ArrayList<>(exportedFunctionMap.keySet()); + keys.sort(Comparator.comparing(Method::toString)); + StringJoiner joiner = new StringJoiner("\n\n", "\n", ""); + joiner.setEmptyValue(""); + for (Method key : keys) { + if (!exportedFunctionMap.get(key).getFunctionCallBackList().isEmpty()) { + joiner.add(ClassUtils.wrapExportedFunctionCall(key, exportedFunctionMap.get(key), true)); + } + } + String exportedMethods = joiner.toString(); + + StringBuilder sb = new StringBuilder("package " + packageName + ";\n\n" + + "import " + this.getClass().getCanonicalName() + ";\n" + + "import " + ExportFunctionAuditEvent.class.getCanonicalName() + ";\n" + + "public class " + className + additionalInterfaces + " {\n" + + declarations + "\n" + + constructor + "\n" + + delegateOnEvent + "\n" + + exportedMethods + "\n" + + "}"); +// System.out.println(sb.toString()); + Class clazz = StringCompilation.compile(fqn, sb.toString()); + exportingWrapper = clazz.getConstructor(InMemoryEventProcessor.class).newInstance(this); + return (T) exportingWrapper; + } + private void registerAuditors() { //register auditors List auditorFields = simpleEventProcessorModel.getNodeRegistrationListenerFields().stream() @@ -582,6 +698,7 @@ private class Node implements StaticEventProcessor, Lifecycle { final List dependents = new ArrayList<>(); final List forkJoinParents = new ArrayList<>(); Method onEventCompleteMethod; + MutableTuple onEventCompleteMethodPointer; boolean dirty; @Override @@ -623,7 +740,7 @@ private void markDirty() { } public boolean willInvokeEventComplete() { - return dirty && onEventCompleteMethod != null; + return dirty && onEventCompleteMethod != null && !eventCompleteInvokeSet.contains(onEventCompleteMethodPointer); } @SneakyThrows @@ -635,6 +752,7 @@ public void eventComplete() { callbackHandle.instance, callbackHandle.getVariableName(), onEventCompleteMethod.getName(), currentEvent )); onEventCompleteMethod.invoke(callbackHandle.getInstance()); + eventCompleteInvokeSet.add(onEventCompleteMethodPointer); } dirty = false; } @@ -682,18 +800,10 @@ public void init() { ).stream().findFirst().orElse(null); if (onEventCompleteMethod != null) { onEventCompleteMethod.setAccessible(true); + onEventCompleteMethodPointer = new MutableTuple<>(callbackHandle.getInstance(), onEventCompleteMethod); } } - private void deDuplicateOnEventComplete(Set onCompleteCallbackSet) { - if (onCompleteCallbackSet.contains(callbackHandle.getInstance())) { - onEventCompleteMethod = null; - } else { - onCompleteCallbackSet.add(callbackHandle.getInstance()); - } - - } - @Override public void tearDown() { dirtyBitset.clear(position); @@ -715,4 +825,5 @@ public String toString() { '}'; } } + } diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/targets/JavaSourceGenerator.java b/compiler/src/main/java/com/fluxtion/compiler/generation/targets/JavaSourceGenerator.java index 32d6434ce..0e3be3d2c 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/targets/JavaSourceGenerator.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/targets/JavaSourceGenerator.java @@ -21,11 +21,7 @@ import com.fluxtion.compiler.EventProcessorConfig.DISPATCH_STRATEGY; import com.fluxtion.compiler.builder.filter.FilterDescription; import com.fluxtion.compiler.generation.GenerationContext; -import com.fluxtion.compiler.generation.model.CbMethodHandle; -import com.fluxtion.compiler.generation.model.DirtyFlag; -import com.fluxtion.compiler.generation.model.Field; -import com.fluxtion.compiler.generation.model.InvokerFilterTarget; -import com.fluxtion.compiler.generation.model.SimpleEventProcessorModel; +import com.fluxtion.compiler.generation.model.*; import com.fluxtion.compiler.generation.util.ClassUtils; import com.fluxtion.compiler.generation.util.NaturalOrderComparator; import com.fluxtion.runtime.EventProcessorContext; @@ -33,6 +29,7 @@ import com.fluxtion.runtime.annotations.OnParentUpdate; import com.fluxtion.runtime.audit.Auditor; import com.fluxtion.runtime.audit.EventLogManager; +import com.fluxtion.runtime.callback.ExportFunctionAuditEvent; import com.fluxtion.runtime.event.Event; import com.fluxtion.runtime.input.EventFeed; import com.fluxtion.runtime.node.ForkedTriggerTask; @@ -45,17 +42,8 @@ import java.lang.reflect.Array; import java.lang.reflect.Modifier; import java.lang.reflect.ParameterizedType; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Collections; -import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Objects; -import java.util.Set; import java.util.stream.Collectors; import static com.fluxtion.compiler.generation.targets.JavaGenHelper.mapPrimitiveToWrapper; @@ -211,6 +199,10 @@ public class JavaSourceGenerator { * event type, will include branching based on filterId. */ private String eventHandlers; + /** + * String representing exported events + */ + private String exportedMethods; /** * determines whether separate delegate eventHandling methods are generated. */ @@ -598,6 +590,7 @@ private void buildFilterConstantDeclarations() { private void buildEventDispatch() { generateClassBasedDispatcher(); + generateExportMethodDispatcher(); generateEventBufferedDispatcher(); addEventAsJavaDoc(); if (auditingEvent) { @@ -634,6 +627,7 @@ private void generateEventBufferedDispatcher() { isInlineEventHandling = true; //sort the classes and then loop through the sorted list List> sortedClasses = ClassUtils.sortClassHierarchy(handlerOnlyDispatchMap.keySet()); + sortedClasses.remove(ExportFunctionMarker.class); String elsePrefix = "if"; for (Class eventId : sortedClasses) { Map> m = handlerOnlyDispatchMap.get(eventId); @@ -688,10 +682,6 @@ private void generateEventBufferedDispatcher() { eventHandlers += bufferedTrigger; } - public void dispatchBufferedEvents() { - - } - /** * generates the implementation of the onEvent method, and writes to the * eventDispatch or debugEventDispatch string. This is the top level method @@ -712,6 +702,7 @@ private void generateClassBasedDispatcher() { Set> keySet = dispatchMap.keySet(); HashSet> classSet = new HashSet<>(keySet); classSet.addAll(postDispatchMap.keySet()); + classSet.remove(ExportFunctionMarker.class); List> clazzList = ClassUtils.sortClassHierarchy(classSet); String elsePrefix = "if"; for (Class eventId : clazzList) { @@ -756,6 +747,39 @@ private void generateClassBasedDispatcher() { //build a noIddispatchString - just copy method above and only //process .ID free events. eventDispatch = dispatchStringNoId + "\n"; + eventHandlers += " //EVENT DISPATCH - END\n"; + } + + private void generateExportMethodDispatcher() { + Map> eventDispatch = model.getDispatchMap().get(ExportFunctionMarker.class); + Map> postDispatch = model.getPostDispatchMap().get(ExportFunctionMarker.class); + final String audit; + List listenerFields = model.getNodeRegistrationListenerFields(); + if (listenerFields != null && !listenerFields.isEmpty()) { + audit = "beforeServiceCall(\"&&FUNC&&\");\n" + + "ExportFunctionAuditEvent typedEvent = functionAudit;\n"; + } else { + audit = "beforeServiceCall(\"&&FUNC&&\");\n"; + } + if (eventDispatch != null) { + eventHandlers += "\n//EXPORTED SERVICE FUNCTIONS - START\n"; + List list = new ArrayList<>(eventDispatch.keySet()); + list.sort(Comparator.comparing(FilterDescription::getStringValue)); + list.forEach(f -> { + String exportAudit = ""; + if (f.getExportFunction() != null) { + exportAudit = f.getExportFunction().toGenericString(); + } + StringBuilder sb = new StringBuilder(f.getStringValue() + "{\n") + .append(audit.replace("&&FUNC&&", exportAudit)); + buildDispatchForCbMethodHandles(eventDispatch.get(f), sb); + buildPostDispatchForCbMethodHandles(postDispatch.get(f), sb); + sb.append("afterServiceCall();\n"); + sb.append(f.getStringValue().contains("void") ? "}\n" : "return true;}\n"); + eventHandlers += sb.toString(); + }); + eventHandlers += "//EXPORTED SERVICE FUNCTIONS - END\n"; + } } private String buildFilteredSwitch(Map> cbMap, @@ -813,154 +837,166 @@ private String buildFilteredSwitch(Map> switchF.append(s20).append("case(").append(filterVariable).append("):\n"); } } - cbList = cbList == null ? Collections.emptyList() : cbList; ct.delete(0, ct.length()); - for (CbMethodHandle method : cbList) { - DirtyFlag dirtyFlagForUpdateCb = model.getDirtyFlagForUpdateCb(method); - String dirtyAssignment = ""; - if (dirtyFlagForUpdateCb != null) { - if (dirtyFlagForUpdateCb.alwaysDirty) { - dirtyAssignment = dirtyFlagForUpdateCb.name + " = true;\n" + s24; - } else { - dirtyAssignment = dirtyFlagForUpdateCb.name + " = "; - } - } - //protect with guards - Collection nodeGuardConditions = model.getNodeGuardConditions(method); - //HERE TO JOIN THINGS - String OR = ""; - if (nodeGuardConditions.size() > 0) { - ct.append(s24).append("if(guardCheck_" + method.getVariableName() + "()) {\n"); - } + buildDispatchForCbMethodHandles(cbList, ct); + buildPostDispatchForCbMethodHandles(cbMapPostEvent.get(filterDescription), ct); + invokerTarget.methodBody = ct.toString(); + if (!noFilter) { + ct.append(s24 + "afterEvent();\n"); + ct.append(s24 + "return;\n"); + } + switchF.append(ct); + } + } + return switchF.length() == 0 ? null : switchF.toString(); + } - //add audit - if (auditingInvocations) { - if (method.isForkExecution()) { - ct.append(s24).append("auditInvocation(") - .append(method.forkVariableName()) - .append(", \"").append(method.variableName).append("\"") - .append(", \"").append(method.method.getName()).append("\"") - .append(", typedEvent") - .append(");\n"); - } else { - ct.append(s24).append("auditInvocation(") - .append(method.variableName) - .append(", \"").append(method.variableName).append("\"") - .append(", \"").append(method.method.getName()).append("\"") - .append(", typedEvent") - .append(");\n"); - } - } - //assign return if appropriate - if (method.parameterClass == null) {//triggers - if (method.isForkExecution()) { - ct.append(method.forkVariableName() + ".onTrigger();\n"); - } else { - ct.append(s24).append(dirtyAssignment).append(method.getMethodTarget()).append(".").append(method.method.getName()).append("();\n"); - } - } else { - ct.append(s24).append(dirtyAssignment).append(method.getMethodTarget()).append(".").append(method.method.getName()).append("(typedEvent);\n"); - } - if (dirtyFlagForUpdateCb != null && dirtyFlagForUpdateCb.requiresInvert) { - ct.append(s24).append("not" + dirtyFlagForUpdateCb.name + " = !" + dirtyFlagForUpdateCb.name + ";\n"); - } - //child callbacks - listening to an individual parent change - //if guards are in operation for the parent node, conditionally invoke only on a change - final Map> listenerMethodMap = model.getParentUpdateListenerMethodMap(); - Object parent = method.instance; - String parentVar = method.variableName; - - //guard - DirtyFlag parentFlag = model.getDirtyFieldMap().get(model.getFieldForInstance(parent)); - //the parent listener map should be keyed on instance and event filter - //we carry out filtering here so that no propagate annotations on parents - //do not generate the parent callback - List updateListenerCbList = listenerMethodMap.get(parent); - final OnEventHandler handlerAnnotation = method.method.getAnnotation(OnEventHandler.class); - if (handlerAnnotation != null && (!handlerAnnotation.propagate())) { - } else if (model.getForkedTriggerInstances().contains(parent)) { - //close guards clause - if (nodeGuardConditions.size() > 0) { - //callTree += String.format("%16s}\n", ""); - ct.append(s16 + "}\n"); - } + private void buildDispatchForCbMethodHandles(List cbList, StringBuilder stringBuilder) { + if (cbList == null || cbList.isEmpty()) { + return; + } + for (CbMethodHandle method : cbList) { + DirtyFlag dirtyFlagForUpdateCb = model.getDirtyFlagForUpdateCb(method); + String dirtyAssignment = ""; + if (dirtyFlagForUpdateCb != null) { + if (dirtyFlagForUpdateCb.alwaysDirty) { + dirtyAssignment = dirtyFlagForUpdateCb.name + " = true;\n" + s24; + } else { + dirtyAssignment = dirtyFlagForUpdateCb.name + " = "; + } + } + //protect with guards + Collection nodeGuardConditions = model.getNodeGuardConditions(method); + //HERE TO JOIN THINGS + String OR = ""; + if (nodeGuardConditions.size() > 0) { + stringBuilder.append(s24).append("if(guardCheck_" + method.getVariableName() + "()) {\n"); + } + + //add audit + if (auditingInvocations) { + if (method.isForkExecution()) { + stringBuilder.append(s24).append("auditInvocation(") + .append(method.forkVariableName()) + .append(", \"").append(method.variableName).append("\"") + .append(", \"").append(method.method.getName()).append("\"") + .append(", typedEvent") + .append(");\n"); + } else { + stringBuilder.append(s24).append("auditInvocation(") + .append(method.variableName) + .append(", \"").append(method.variableName).append("\"") + .append(", \"").append(method.method.getName()).append("\"") + .append(", typedEvent") + .append(");\n"); + } + } + //assign return if appropriate + if (method.parameterClass == null) {//triggers + if (method.isForkExecution()) { + stringBuilder.append(method.forkVariableName() + ".onTrigger();\n"); + } else { + stringBuilder.append(s24).append(dirtyAssignment).append(method.getMethodTarget()).append(".").append(method.method.getName()).append("();\n"); + } + } else if (method.isExportedHandler()) { + StringJoiner sjInvoker = new StringJoiner(", ", "(", ");\n\t"); + for (int i = 0; i < method.getMethod().getParameterCount(); i++) { + sjInvoker.add("arg" + i); + } + if (dirtyFlagForUpdateCb != null) { + if (method.getMethod().getReturnType() == boolean.class) { + dirtyAssignment = dirtyFlagForUpdateCb.name + " = "; } else { - if (parentFlag != null && updateListenerCbList.size() > 0) { - //callTree += String.format("%20sif(%s) {\n", "", parentFlag.name); - ct.append(s20 + "if(").append(parentFlag.name).append(") {\n"); - } - //child callbacks - boolean unguarded = false; - StringBuilder sbUnguarded = new StringBuilder(); - for (CbMethodHandle cbMethod : updateListenerCbList) { - //callTree += String.format("%24s%s.%s(%s);%n", "", cbMethod.variableName, cbMethod.method.getName(), parentVar); - if (!cbMethod.method.getAnnotation(OnParentUpdate.class).guarded()) { - unguarded = true; - sbUnguarded.append(s20).append(cbMethod.variableName).append(".").append(cbMethod.method.getName()).append("(").append(parentVar).append(");\n"); - } else { - ct.append(s24).append(cbMethod.variableName).append(".").append(cbMethod.method.getName()).append("(").append(parentVar).append(");\n"); - } - } - if (parentFlag != null && updateListenerCbList.size() > 0) { - //callTree += String.format("%20s}\n", "", parentFlag.name); - ct.append(s20).append("}\n"); - if (unguarded) { - ct.append(sbUnguarded); - } - } - //close guards clause - if (nodeGuardConditions.size() > 0) { - //callTree += String.format("%16s}\n", ""); - ct.append(s16 + "}\n"); - } + //dirtyAssignment = dirtyFlagForUpdateCb.name + " = true;\n" + s24; } } - //chec for null on cbList and escape - cbList = cbMapPostEvent.get(filterDescription); - if (cbList == null || cbList.size() > 0) { - //callTree += String.format("%16s//event stack unwind callbacks\n", ""); - ct.append(s16 + "//event stack unwind callbacks\n"); + stringBuilder.append(s24).append(dirtyAssignment).append(method.getMethodTarget()).append(".").append(method.method.getName()).append(sjInvoker); + } else { + stringBuilder.append(s24).append(dirtyAssignment).append(method.getMethodTarget()).append(".").append(method.method.getName()).append("(typedEvent);\n"); + } + if (dirtyFlagForUpdateCb != null && dirtyFlagForUpdateCb.requiresInvert) { + stringBuilder.append(s24).append("not" + dirtyFlagForUpdateCb.name + " = !" + dirtyFlagForUpdateCb.name + ";\n"); + } + //child callbacks - listening to an individual parent change + //if guards are in operation for the parent node, conditionally invoke only on a change + final Map> listenerMethodMap = model.getParentUpdateListenerMethodMap(); + Object parent = method.instance; + String parentVar = method.variableName; + + //guard + DirtyFlag parentFlag = model.getDirtyFieldMap().get(model.getFieldForInstance(parent)); + //the parent listener map should be keyed on instance and event filter + //we carry out filtering here so that no propagate annotations on parents + //do not generate the parent callback + List updateListenerCbList = listenerMethodMap.get(parent); + final OnEventHandler handlerAnnotation = method.method.getAnnotation(OnEventHandler.class); + if (handlerAnnotation != null && (!handlerAnnotation.propagate())) { + } else if (model.getForkedTriggerInstances().contains(parent)) { + //close guards clause + if (nodeGuardConditions.size() > 0) { + //callTree += String.format("%16s}\n", ""); + stringBuilder.append(s16 + "}\n"); } - cbList = cbList == null ? Collections.EMPTY_LIST : cbList; - for (CbMethodHandle method : cbList) { - //protect with guards - Collection nodeGuardConditions = model.getNodeGuardConditions(method); - String OR = ""; - if (nodeGuardConditions.size() > 0) { - Set forkedTriggers = model.getForkedTriggerInstances(); - if (forkedTriggers.contains(method.getInstance())) { - ct.append(method.forkVariableName() + ".afterEvent();\n"); - } - ct.append(s24 + "if("); - for (DirtyFlag nodeGuardCondition : nodeGuardConditions) { - ct.append(OR).append(nodeGuardCondition.name); -// OR = " || "; - OR = " | "; - } - ct.append(") {\n"); - } - - //assign return if appropriate - if (method.parameterClass == null) { - ct.append(s24).append(method.variableName).append(".").append(method.method.getName()).append("();\n"); + } else { + if (parentFlag != null && updateListenerCbList.size() > 0) { + //callTree += String.format("%20sif(%s) {\n", "", parentFlag.name); + stringBuilder.append(s20 + "if(").append(parentFlag.name).append(") {\n"); + } + //child callbacks + boolean unguarded = false; + StringBuilder sbUnguarded = new StringBuilder(); + for (CbMethodHandle cbMethod : updateListenerCbList) { + //callTree += String.format("%24s%s.%s(%s);%n", "", cbMethod.variableName, cbMethod.method.getName(), parentVar); + if (!cbMethod.method.getAnnotation(OnParentUpdate.class).guarded()) { + unguarded = true; + sbUnguarded.append(s20).append(cbMethod.variableName).append(".").append(cbMethod.method.getName()).append("(").append(parentVar).append(");\n"); } else { - ct.append(s24).append(method.getMethodTarget()).append(".").append(method.method.getName()).append("(typedEvent);\n"); + stringBuilder.append(s24).append(cbMethod.variableName).append(".").append(cbMethod.method.getName()).append("(").append(parentVar).append(");\n"); } - //close guarded clause - if (nodeGuardConditions.size() > 0) { - ct.append(s16 + "}\n"); + } + if (parentFlag != null && updateListenerCbList.size() > 0) { + //callTree += String.format("%20s}\n", "", parentFlag.name); + stringBuilder.append(s20).append("}\n"); + if (unguarded) { + stringBuilder.append(sbUnguarded); } } - //INVOKETARGET - invokerTarget.methodBody = ct.toString(); - if (!noFilter) { - ct.append(s24 + "afterEvent();\n"); - ct.append(s24 + "return;\n"); + //close guards clause + if (nodeGuardConditions.size() > 0) { + //callTree += String.format("%16s}\n", ""); + stringBuilder.append(s16 + "}\n"); } - switchF.append(ct); } } - return switchF.length() == 0 ? null : switchF.toString(); + } + + private void buildPostDispatchForCbMethodHandles(List cbList, StringBuilder stringBuilder) { + if (cbList == null || cbList.isEmpty()) { + return; + } + stringBuilder.append(s16 + "//event stack unwind callbacks\n"); + for (CbMethodHandle method : cbList) { + //protect with guards + Collection nodeGuardConditions = model.getNodeGuardConditions(method); + String OR = ""; + if (!nodeGuardConditions.isEmpty()) { + Set forkedTriggers = model.getForkedTriggerInstances(); + if (forkedTriggers.contains(method.getInstance())) { + stringBuilder.append(method.forkVariableName()).append(".afterEvent();\n"); + } + stringBuilder.append(s24 + "if("); + for (DirtyFlag nodeGuardCondition : nodeGuardConditions) { + stringBuilder.append(OR).append(nodeGuardCondition.name); + OR = " | "; + } + stringBuilder.append(") {\n"); + } + stringBuilder.append(s24).append(method.variableName).append(".").append(method.method.getName()).append("();\n"); + //close guarded clause + if (!nodeGuardConditions.isEmpty()) { + stringBuilder.append(s16 + "}\n"); + } + } } private String buildFilteredDispatch(Map> cbMap, @@ -1269,6 +1305,10 @@ public String getEventHandlers() { return eventHandlers; } + public String getExportedMethods() { + return exportedMethods; + } + public String getNodeDeclarations() { return nodeDeclarations; } @@ -1326,9 +1366,10 @@ public String getResetForkTasks() { } public String getImports() { - Collections.sort(importList); + List dedupeList = new ArrayList<>(new HashSet<>(importList)); + Collections.sort(dedupeList); StringBuilder sb = new StringBuilder(2048); - importList.stream().forEach(s -> { + dedupeList.stream().forEach(s -> { sb.append("import ") .append(s) .append(";\n"); @@ -1367,6 +1408,7 @@ private void buildNodeRegistrationListeners() { importList.add(Map.class.getCanonicalName()); importList.add(EventFeed.class.getCanonicalName()); importList.add(EventLogManager.class.getCanonicalName()); + importList.add(ExportFunctionAuditEvent.class.getCanonicalName()); auditMethodString = ""; String auditObjet = "private void auditEvent(Object typedEvent){\n"; String auditEvent = String.format("private void auditEvent(%s typedEvent){\n", eventClassName); @@ -1430,6 +1472,7 @@ public void additionalInterfacesToImplement(Set> interfacesToImplement) if (!interfacesToImplement.isEmpty()) { additionalInterfaces = interfacesToImplement.stream() .map(this::getClassTypeName) + .sorted() .collect(Collectors.joining(", ", ", ", "")); } } diff --git a/compiler/src/main/java/com/fluxtion/compiler/generation/util/ClassUtils.java b/compiler/src/main/java/com/fluxtion/compiler/generation/util/ClassUtils.java index 2b1df6b47..5c37d8bc0 100644 --- a/compiler/src/main/java/com/fluxtion/compiler/generation/util/ClassUtils.java +++ b/compiler/src/main/java/com/fluxtion/compiler/generation/util/ClassUtils.java @@ -18,19 +18,21 @@ package com.fluxtion.compiler.generation.util; import com.fluxtion.compiler.generation.model.CbMethodHandle; +import com.fluxtion.compiler.generation.model.ExportFunctionData; import com.fluxtion.compiler.generation.model.Field; +import com.fluxtion.compiler.generation.model.SimpleEventProcessorModel; +import lombok.SneakyThrows; import net.vidageek.mirror.dsl.Mirror; import org.reflections.ReflectionUtils; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.beans.PropertyDescriptor; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Optional; -import java.util.Set; +import java.lang.reflect.Type; +import java.util.*; +import java.util.concurrent.atomic.LongAdder; import java.util.stream.Collectors; /** @@ -131,4 +133,130 @@ static List> sortClassHierarchy(Set> classSet) { }); return clazzSorted; } + + @SneakyThrows + static String wrapExportedFunctionCall(Method delegateMethod, String exportedMethodName, String instanceName) { + LongAdder argNumber = new LongAdder(); + StringBuilder signature = new StringBuilder("public void " + exportedMethodName); + signature.append('('); + StringJoiner sj = new StringJoiner(", "); + Type[] params = delegateMethod.getGenericParameterTypes(); + for (int j = 0; j < params.length; j++) { + String param = params[j].getTypeName(); + if (delegateMethod.isVarArgs() && (j == params.length - 1)) // replace T[] with T... + param = param.replaceFirst("\\[\\]$", "..."); + param += " arg" + argNumber.intValue(); + sj.add(param); + argNumber.increment(); + } + signature.append(sj.toString()); + signature.append(", String identifer"); + signature.append("){"); + signature.append("try {\n" + + " ExportingNode instance = getNodeById(identifer);"); + signature.append("\n instance." + delegateMethod.getName() + "("); + StringJoiner sjInvoker = new StringJoiner(", "); + for (int i = 0; i < argNumber.intValue(); i++) { + sjInvoker.add("arg" + i); + } + signature.append(sjInvoker.toString()); + signature.append(");\n"); + signature.append("} catch (NoSuchFieldException e) {\n" + + " throw new RuntimeException(e);\n" + + " }" + + " }"); + return signature.toString(); + } + + @SneakyThrows + static String wrapExportedFunctionCall(Method exportedMethod, List callBackList, SimpleEventProcessorModel model) { + String exportedMethodName = exportedMethod.getName(); + LongAdder argNumber = new LongAdder(); + Method delegateMethod = callBackList.get(0).getMethod(); + StringBuilder signature = new StringBuilder("public void " + exportedMethodName); + signature.append('('); + StringJoiner sj = new StringJoiner(", "); + Type[] params = delegateMethod.getGenericParameterTypes(); + for (int j = 0; j < params.length; j++) { + String param = params[j].getTypeName(); + if (delegateMethod.isVarArgs() && (j == params.length - 1)) // replace T[] with T... + param = param.replaceFirst("\\[\\]$", "..."); + param += " arg" + argNumber.intValue(); + sj.add(param); + argNumber.increment(); + } + signature.append(sj); + signature.append("){"); + //method calls + StringJoiner sjInvoker = new StringJoiner(", ", "(", "));"); + for (int i = 0; i < argNumber.intValue(); i++) { + sjInvoker.add("arg" + i); + } + callBackList.forEach(cb -> { + signature.append("setDirty(").append(cb.getVariableName()).append(", "). + append(cb.getVariableName()).append(".").append(cb.getMethod().getName()).append(sjInvoker); + }); + //close + signature.append(" triggerCalculation();\n }"); + return signature.toString(); + } + + @SneakyThrows + static String wrapExportedFunctionCall(Method exportedMethod, ExportFunctionData exportFunctionData, boolean onEventDispatch) { + String exportedMethodName = exportedMethod.getName(); + LongAdder argNumber = new LongAdder(); + List callBackList = exportFunctionData.getFunctionCallBackList(); + Method delegateMethod = callBackList.get(0).getMethod(); + boolean booleanReturn = exportFunctionData.isBooleanReturn(); + StringBuilder signature = booleanReturn ? new StringBuilder("public boolean " + exportedMethodName) : new StringBuilder("public void " + exportedMethodName); + signature.append('('); + StringJoiner sj = new StringJoiner(", "); + Type[] params = delegateMethod.getGenericParameterTypes(); + for (int j = 0; j < params.length; j++) { + String param = params[j].getTypeName() + .replace("$", ".") + .replace("java.lang.", ""); + if (delegateMethod.isVarArgs() && (j == params.length - 1)) // replace T[] with T... + param = param.replaceFirst("\\[\\]$", "..."); + param += " arg" + argNumber.intValue(); + sj.add(param); + argNumber.increment(); + } + signature.append(sj); + signature.append("){\n\t"); + // + signature.append("processor.auditNewEvent( functionAudit.setFunctionDescription(\"" + exportFunctionData.getExportedmethod().toGenericString() + "\"));\n" + + " if(processor.buffering){\n" + + " processor.triggerCalculation();\n" + + " }\n" + + " processor.processing = false;\n\t"); + //method calls + StringJoiner sjInvoker = new StringJoiner(", ", "(", "));\n\t"); + for (int i = 0; i < argNumber.intValue(); i++) { + sjInvoker.add("arg" + i); + } + callBackList.forEach(cb -> { + String variableName = cb.getVariableName(); + String methodName = cb.getMethod().getName(); + signature.append("processor.nodeInvoked(" + variableName + ", \"" + variableName + "\", \"" + methodName + "\", functionAudit);\n"); + if (cb.isNoPropagateFunction()) { + signature.append(variableName).append(".").append(methodName).append(sjInvoker.toString().replace("));", ");")); + } else if (cb.getMethod().getReturnType() == void.class) { + signature.append(variableName).append(".").append(methodName).append(sjInvoker.toString().replace("));", ");")); + signature.append("processor.setDirty(").append(variableName).append(", true);\n\t"); + } else { + signature.append("processor.setDirty("). + append(variableName).append(", ").append(variableName).append(".").append(methodName).append(sjInvoker); + } + }); + signature.append("processor.triggerCalculation();\n" + + " processor.dispatchQueuedCallbacks();\n" + + " processor.processing = false;\n"); + if (booleanReturn) { + signature.append(" return true;\n"); + } + signature.append("}"); + return signature.toString(); + } + } diff --git a/compiler/src/main/resources/META-INF/gradle/incremental.annotation.processors b/compiler/src/main/resources/META-INF/gradle/incremental.annotation.processors index 357915a4b..52a0b16fc 100644 --- a/compiler/src/main/resources/META-INF/gradle/incremental.annotation.processors +++ b/compiler/src/main/resources/META-INF/gradle/incremental.annotation.processors @@ -1,4 +1,5 @@ com.fluxtion.compiler.generation.annotationprocessor.ValidateEventHandlerAnnotations,isolating com.fluxtion.compiler.generation.annotationprocessor.ValidateOnTriggerAnnotations,isolating com.fluxtion.compiler.generation.annotationprocessor.ValidateOnParentUpdateHandlerAnnotations,isolating -com.fluxtion.compiler.generation.annotationprocessor.ValidateLifecycleAnnotations,isolating \ No newline at end of file +com.fluxtion.compiler.generation.annotationprocessor.ValidateLifecycleAnnotations,isolating +com.fluxtion.compiler.generation.annotationprocessor.ValidateExportFunctionAnnotations,isolating \ No newline at end of file diff --git a/compiler/src/main/resources/template/base/javaTemplate.vsl b/compiler/src/main/resources/template/base/javaTemplate.vsl index 0a8a19129..a69f99950 100644 --- a/compiler/src/main/resources/template/base/javaTemplate.vsl +++ b/compiler/src/main/resources/template/base/javaTemplate.vsl @@ -48,6 +48,7 @@ EventProcessor<${className}>, StaticEventProcessor, InternalEventProcessor, Batc //Node declarations ${MODEL.nodeDeclarations} + private ExportFunctionAuditEvent functionAudit = new ExportFunctionAuditEvent(); //Dirty flags private boolean initCalled = false; private boolean processing = false; @@ -70,6 +71,50 @@ private final IdentityHashMap> dirtyFlagUpdateMap = ne this(null); } + @Override + public void init() { + initCalled = true; + auditEvent(Lifecycle.LifecycleEvent.Init); + //initialise dirty lookup map + isDirty("test"); + ${MODEL.initialiseMethods} + afterEvent(); + } + + @Override + public void start() { + if(!initCalled){ + throw new RuntimeException("init() must be called before start()"); + } + processing = true; + auditEvent(Lifecycle.LifecycleEvent.Start); + ${MODEL.startMethods} + afterEvent(); + callbackDispatcher.dispatchQueuedCallbacks(); + processing = false; + } + + @Override + public void stop() { + if(!initCalled){ + throw new RuntimeException("init() must be called before stop()"); + } + processing = true; + auditEvent(Lifecycle.LifecycleEvent.Stop); + ${MODEL.stopMethods} + afterEvent(); + callbackDispatcher.dispatchQueuedCallbacks(); + processing = false; + } + + @Override + public void tearDown() { + initCalled = false; + auditEvent(Lifecycle.LifecycleEvent.TearDown); + ${MODEL.tearDownMethods} + afterEvent(); + } + @Override public void setContextParameterMap(Map newContextMapping){ context.replaceMappings(newContextMapping); @@ -81,6 +126,7 @@ private final IdentityHashMap> dirtyFlagUpdateMap = ne context.addMapping(key, value); } + //EVENT DISPATCH - START @Override public void onEvent(Object event) { if(buffering){ @@ -96,56 +142,52 @@ private final IdentityHashMap> dirtyFlagUpdateMap = ne } } + @Override public void onEventInternal(Object event) { ${MODEL.eventDispatch} } ${MODEL.eventHandlers} - private void afterEvent() { - ${MODEL.eventEndMethods} - ${MODEL.resetDirtyFlags} - ${MODEL.resetForkTasks} - } - - @Override - public void init() { - initCalled = true; - //initialise dirty lookup map - isDirty("test"); - ${MODEL.initialiseMethods} - } - - @Override - public void start() { - if(!initCalled){ - throw new RuntimeException("init() must be called before start()"); + private void beforeServiceCall(String functionDescription){ + functionAudit.setFunctionDescription(functionDescription); + auditEvent(functionAudit); + if (buffering) { + triggerCalculation(); } - ${MODEL.startMethods} + processing = true; } - @Override - public void stop() { - if(!initCalled){ - throw new RuntimeException("init() must be called before stop()"); - } - ${MODEL.stopMethods} + private void afterServiceCall(){ + afterEvent(); + callbackDispatcher.dispatchQueuedCallbacks(); + processing = false; } - @Override - public void tearDown() { - initCalled = false; - ${MODEL.tearDownMethods} + private void afterEvent() { + ${MODEL.eventEndMethods} + ${MODEL.resetDirtyFlags} + ${MODEL.resetForkTasks} } @Override public void batchPause() { - ${MODEL.batchPauseMethods} + auditEvent(Lifecycle.LifecycleEvent.BatchPause); + processing = true; + ${MODEL.batchPauseMethods} + afterEvent(); + callbackDispatcher.dispatchQueuedCallbacks(); + processing = false; } @Override public void batchEnd() { - ${MODEL.batchEndMethods} + auditEvent(Lifecycle.LifecycleEvent.BatchEnd); + processing = true; + ${MODEL.batchEndMethods} + afterEvent(); + callbackDispatcher.dispatchQueuedCallbacks(); + processing = false; } @Override @@ -153,6 +195,7 @@ private final IdentityHashMap> dirtyFlagUpdateMap = ne return dirtySupplier(node).getAsBoolean(); } + @Override public BooleanSupplier dirtySupplier(Object node) { if(dirtyFlagSupplierMap.isEmpty()){ ${MODEL.dirtyFlagLookup} diff --git a/compiler/src/test/java/com/fluxtion/compiler/annotationprocessor/ValidatingAnnotationProcessorTest.java b/compiler/src/test/java/com/fluxtion/compiler/annotationprocessor/ValidatingAnnotationProcessorTest.java index 85612318a..21f000d57 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/annotationprocessor/ValidatingAnnotationProcessorTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/annotationprocessor/ValidatingAnnotationProcessorTest.java @@ -148,4 +148,63 @@ public void noFailCompileString_OverrideGuardBooleanReturn() { "}"; StringCompilation.compile("MyStringHandler", source); } + + + @SneakyThrows + @Test(expected = RuntimeException.class) + public void failCompileString_ExportFunction() { + String source = " " + + "import com.fluxtion.runtime.annotations.OnEventHandler;\n" + + "import com.fluxtion.runtime.annotations.ExportFunction;\n" + + "import com.fluxtion.runtime.callback.ExportFunctionNode;\n" + + "\n" + + "public class MyStringHandler {\n" + + " String in;\n" + + "\n" + + " @ExportFunction\n" + + " public boolean stringUpdated(String in) {\n" + + " this.in = in;\n" + + " return true;\n" + + " }\n" + + "}"; + StringCompilation.compile("MyStringHandler", source); + } + + @SneakyThrows + @Test(expected = RuntimeException.class) + public void failCompileString_ExportFunction_NoBooleanOrVoidReturn() { + String source = " " + + "import com.fluxtion.runtime.annotations.OnEventHandler;\n" + + "import com.fluxtion.runtime.annotations.ExportFunction;\n" + + "import com.fluxtion.runtime.callback.ExportFunctionNode;\n" + + "\n" + + "public class MyStringHandler extends ExportFunctionNode{\n" + + " String in;\n" + + "\n" + + " @ExportFunction\n" + + " public int stringUpdated(String in) {\n" + + " this.in = in;\n" + + " return 0;\n" + + " }\n" + + "}"; + StringCompilation.compile("MyStringHandler", source); + } + + @SneakyThrows + public void success_ExportFunction_VoidReturn() { + String source = " " + + "import com.fluxtion.runtime.annotations.OnEventHandler;\n" + + "import com.fluxtion.runtime.annotations.ExportFunction;\n" + + "import com.fluxtion.runtime.callback.ExportFunctionNode;\n" + + "\n" + + "public class MyStringHandler extends ExportFunctionNode{\n" + + " String in;\n" + + "\n" + + " @ExportFunction\n" + + " public void stringUpdated(String in) {\n" + + " this.in = in;\n" + + " }\n" + + "}"; + StringCompilation.compile("MyStringHandler", source); + } } diff --git a/compiler/src/test/java/com/fluxtion/compiler/builder/PricerTest.java b/compiler/src/test/java/com/fluxtion/compiler/builder/PricerTest.java new file mode 100644 index 000000000..64fd53207 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/builder/PricerTest.java @@ -0,0 +1,108 @@ +package com.fluxtion.compiler.builder; + +import com.fluxtion.compiler.builder.dataflow.DataFlow; +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; +import com.fluxtion.compiler.generation.util.CompiledOnlySepTest; +import com.fluxtion.runtime.annotations.OnTrigger; +import com.fluxtion.runtime.time.FixedRateTrigger; +import lombok.Data; +import org.junit.Test; + +public class PricerTest extends CompiledOnlySepTest { + public PricerTest(SepTestConfig compile) { + super(compile); + } + + @Test + public void pricer() { +// writeOutputsToFile(true); + sep(c -> { + ParamsChecker checker = new ParamsChecker(); + PriceCalc priceCalc = new PriceCalc(); + StepAway stepAway = new StepAway(); + + + DataFlow.subscribe(MktData.class) + .filter(checker::paramsValid) + .map(priceCalc::marketUpdate) + .merge( + DataFlow.subscribe(Position.class) + .filter(checker::paramsValid) + .map(priceCalc::posUpdate) + ) + .filter(PricerTest::sanityCheck) + .map(stepAway::applyStepaway) + .filter(new ThrottlePublisher()::canPublish); + }); + + } + + public static boolean sanityCheck(PriceLadder priceLadder) { + return true; + } + + public static class StepAway { + + public FixedRateTrigger rollTrigger; + + public PriceLadder applyStepaway(PriceLadder priceLadder) { + return priceLadder; + } + + } + + public static class ThrottlePublisher { + public boolean canPublish(Object o) { + return true; + } + } + + @Data + public static class PriceCalc { + + MktData mktData; + Position position; + + public PriceLadder marketUpdate(MktData mktData) { + return null; + } + + public PriceLadder posUpdate(Position position) { + return null; + } + + public PriceLadder calc() { + return null; + } + + @OnTrigger + public boolean calcTrigger() { + return true; + } + + } + + @Data + public static class PriceLadder { + + } + + @Data + public static class MktData { + String symbol; + double mid; + } + + @Data + public static class Position { + String book; + double value; + } + + public static class ParamsChecker { + + public boolean paramsValid(Object o) { + return true; + } + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/BinaryMapTest.java b/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/BinaryMapTest.java index 0e4ee23f1..fbc2ee72c 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/BinaryMapTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/BinaryMapTest.java @@ -174,6 +174,25 @@ public void referenceTypesBiFunctionTest() { assertThat(getStreamed("formattedDate"), is("July-28-2022")); } + @Test + public void referenceTypesDataFlowBiFunctionTest() { + sep(c -> { + FlowBuilder stringStream = subscribe(String.class); + FlowBuilder dateStream = subscribe(Date.class); + DataFlow.mapBiFunction( + BinaryMapTest::dateFormat, stringStream, dateStream).id("formattedDate"); + }); + Calendar calendar = Calendar.getInstance(Locale.UK); + calendar.set(2022, 06, 28); + + onEvent("MMM-YYYY"); + onEvent(calendar.getTime()); + assertThat(getStreamed("formattedDate"), is("Jul-2022")); + + onEvent("MMMM-dd-YYYY"); + assertThat(getStreamed("formattedDate"), is("July-28-2022")); + } + @Value public static class Data_1 { int intValue; diff --git a/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/EventStreamBuildTest.java b/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/EventStreamBuildTest.java index 7114c7580..f0f3d6f80 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/EventStreamBuildTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/EventStreamBuildTest.java @@ -27,13 +27,7 @@ import org.junit.Test; import java.util.AbstractMap.SimpleEntry; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; +import java.util.*; import java.util.concurrent.atomic.LongAdder; import static com.fluxtion.compiler.builder.dataflow.DataFlow.*; @@ -494,7 +488,7 @@ public void aggregateTest() { @Test public void aggregateToLIstTest() { sep(c -> subscribe(String.class) - .aggregate(Collectors.toList(4)) + .aggregate(Collectors.listFactory(4)) .id("myList")); onEvent("A"); @@ -546,6 +540,111 @@ public void tumblingMap() { assertThat(getStreamed("sum"), is(0)); } + @Test + public void testMapToSet() { + sep(c -> DataFlow.subscribe(String.class).mapToSet().id("set")); + HashSet set = new HashSet<>(); + set.add("test"); + onEvent("test"); + onEvent("test"); + assertThat(getStreamed("set"), is(set)); + onEvent("test2"); + set.add("test2"); + assertThat(getStreamed("set"), is(set)); + onEvent("test"); + assertThat(getStreamed("set"), is(set)); + } + + @Test + public void testMapToSetFromProperty() { + sep(c -> DataFlow.subscribe(GroupByTest.Data.class).mapToSet(GroupByTest.Data::getName).id("set")); + HashSet set = new HashSet<>(); + set.add("test"); + onEvent(new GroupByTest.Data("test", 22)); + onEvent(new GroupByTest.Data("test", 31)); + assertThat(getStreamed("set"), is(set)); + onEvent(new GroupByTest.Data("test2", 2334)); + set.add("test2"); + assertThat(getStreamed("set"), is(set)); + onEvent(new GroupByTest.Data("test", 31)); + assertThat(getStreamed("set"), is(set)); + } + + @Test + public void testMapToList() { + sep(c -> DataFlow.subscribe(String.class).mapToList().id("list")); + List list = new ArrayList<>(); + list.add("test"); + list.add("test"); + onEvent("test"); + onEvent("test"); + assertThat(getStreamed("list"), is(list)); + onEvent("test2"); + list.add("test2"); + assertThat(getStreamed("list"), is(list)); + onEvent("test"); + list.add("test"); + assertThat(getStreamed("list"), is(list)); + } + + @Test + public void testMapToList_MaxElements() { + sep(c -> DataFlow.subscribe(String.class).mapToList(2).id("list")); + List list = new ArrayList<>(); + list.add("test"); + list.add("test"); + onEvent("test"); + onEvent("test"); + assertThat(getStreamed("list"), is(list)); + //deleting + onEvent("test2"); + list.add("test2"); + list.remove(0); + assertThat(getStreamed("list"), is(list)); + //deleting + onEvent("test"); + list.add("test"); + list.remove(0); + assertThat(getStreamed("list"), is(list)); + } + + @Test + public void testMapToListFromProperty() { + sep(c -> DataFlow.subscribe(GroupByTest.Data.class).mapToList(GroupByTest.Data::getName).id("list")); + List list = new ArrayList<>(); + list.add("test"); + list.add("test"); + onEvent(new GroupByTest.Data("test", 22)); + onEvent(new GroupByTest.Data("test", 31)); + assertThat(getStreamed("list"), is(list)); + onEvent(new GroupByTest.Data("test2", 2334)); + list.add("test2"); + assertThat(getStreamed("list"), is(list)); + onEvent(new GroupByTest.Data("test3", 3451)); + list.add("test3"); + assertThat(getStreamed("list"), is(list)); + } + + @Test + public void testMapToListFromProperty_MaxElements() { + sep(c -> DataFlow.subscribe(GroupByTest.Data.class).mapToList(GroupByTest.Data::getName, 2).id("list")); + List list = new ArrayList<>(); + list.add("test1"); + list.add("test2"); + onEvent(new GroupByTest.Data("test1", 22)); + onEvent(new GroupByTest.Data("test2", 31)); + assertThat(getStreamed("list"), is(list)); + //deleting + onEvent(new GroupByTest.Data("test3", 2334)); + list.add("test3"); + list.remove(0); + assertThat(getStreamed("list"), is(list)); + //deleting + onEvent(new GroupByTest.Data("tes4", 3451)); + list.add("tes4"); + list.remove(0); + assertThat(getStreamed("list"), is(list)); + } @Value public static class Person { @@ -554,12 +653,10 @@ public static class Person { String gender; } - public static int doubleInt(int value) { return value * 2; } - @Value public static class MergedType { int value; @@ -986,14 +1083,6 @@ public static PostMap mapToPostMap(PreMap preMap, String lookupValue) { return new PostMap(preMap.getName(), lookupValue); } - public static boolean isTrue(String in) { - return Boolean.parseBoolean(in); - } - - public static boolean gt5(int val) { - return val > 5; - } - public static int parseInt(String in) { return Integer.parseInt(in); } diff --git a/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/FilterTest.java b/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/FilterTest.java index 62be32e0e..14d63e969 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/FilterTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/FilterTest.java @@ -8,6 +8,9 @@ import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; import com.fluxtion.runtime.dataflow.helpers.Mappers; import com.fluxtion.runtime.dataflow.helpers.Predicates; +import com.fluxtion.runtime.node.SingleNamedNode; +import lombok.Getter; +import lombok.Setter; import org.junit.Assert; import org.junit.Test; @@ -24,7 +27,7 @@ public FilterTest(SepTestConfig compiledSep) { @Test public void filterTest() { sep(c -> subscribe(String.class) - .filter(EventStreamBuildTest::isTrue) + .filter(FilterTest::isTrue) .notify(new NotifyAndPushTarget()) ); NotifyAndPushTarget notifyTarget = getField("notifyTarget"); @@ -35,10 +38,43 @@ public void filterTest() { assertThat(notifyTarget.getOnEventCount(), is(1)); } + @Test + public void filterInstanceNoArgumentTest() { + sep(c -> subscribe(String.class) + .filter(new FilterNoArgs("filter")::isValid) + .notify(new NotifyAndPushTarget()) + ); + NotifyAndPushTarget notifyTarget = getField("notifyTarget"); + assertThat(notifyTarget.getOnEventCount(), is(0)); + onEvent("86"); + assertThat(notifyTarget.getOnEventCount(), is(0)); + onEvent("true"); + assertThat(notifyTarget.getOnEventCount(), is(0)); + getField("filter", FilterNoArgs.class).setValid(true); + onEvent("86"); + assertThat(notifyTarget.getOnEventCount(), is(1)); + onEvent("true"); + assertThat(notifyTarget.getOnEventCount(), is(2)); + } + + @Test + public void filterNoArgumentTest() { + sep(c -> subscribe(String.class) + .filter(FilterTest::alwaysTrue) + .notify(new NotifyAndPushTarget()) + ); + NotifyAndPushTarget notifyTarget = getField("notifyTarget"); + assertThat(notifyTarget.getOnEventCount(), is(0)); + onEvent("86"); + assertThat(notifyTarget.getOnEventCount(), is(1)); + onEvent("true"); + assertThat(notifyTarget.getOnEventCount(), is(2)); + } + @Test public void filterByPropertyTest() { sep(c -> subscribe(String.class) - .filterByProperty(String::length, EventStreamBuildTest::gt5) + .filterByProperty(String::length, FilterTest::gt5) .notify(new NotifyAndPushTarget()) ); NotifyAndPushTarget notifyTarget = getField("notifyTarget"); @@ -185,7 +221,30 @@ public void filterFunctionWithPrimitiveArgumentTest() { Assert.assertEquals(2, (int) getStreamed("count")); } + @Setter + @Getter + public static class FilterNoArgs extends SingleNamedNode { + private boolean valid = false; + + public FilterNoArgs(String name) { + super(name); + } + + } + public static boolean filterMutableNumber(MutableNumber number, int check) { return number.intValue() > check; } + + public static boolean isTrue(String in) { + return Boolean.parseBoolean(in); + } + + public static boolean alwaysTrue() { + return true; + } + + public static boolean gt5(int val) { + return val > 5; + } } diff --git a/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/GroupByTest.java b/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/GroupByTest.java index 0d3933589..d6b654bdd 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/GroupByTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/GroupByTest.java @@ -5,13 +5,16 @@ import com.fluxtion.compiler.builder.dataflow.EventStreamBuildTest.MyIntFilter; import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import com.fluxtion.runtime.dataflow.aggregate.AggregateFlowFunction; import com.fluxtion.runtime.dataflow.aggregate.function.primitive.DoubleSumFlowFunction; import com.fluxtion.runtime.dataflow.aggregate.function.primitive.IntSumFlowFunction; import com.fluxtion.runtime.dataflow.groupby.GroupBy; import com.fluxtion.runtime.dataflow.groupby.GroupBy.KeyValue; +import com.fluxtion.runtime.dataflow.groupby.GroupByKey; import com.fluxtion.runtime.dataflow.helpers.Aggregates; import com.fluxtion.runtime.dataflow.helpers.Mappers; import com.fluxtion.runtime.dataflow.helpers.Tuples; +import lombok.Getter; import lombok.Value; import lombok.val; import org.hamcrest.CoreMatchers; @@ -610,6 +613,163 @@ public void maintainModel() { onEvent(new MyEvent(SubSystem.REFERENCE, Change_type.DELETE, "greg-1")); } + @Value + public static class Data3 { + String name; + int value; + int x; + + + } + + @Getter + public static class Data3Aggregate implements AggregateFlowFunction { + int value; + + @Override + public Integer reset() { + return value; + } + + @Override + public Integer get() { + return value; + } + + @Override + public Integer aggregate(Data3 input) { + value += input.getX(); + return get(); + } + } + + @Test + public void groupingKey() { + Map, Data3> expected = new HashMap<>(); + sep(c -> { + subscribe(Data3.class) + .groupByFields(Data3::getName, Data3::getValue) + .map(GroupBy::toMap) + .id("results"); + }); + val keyFactory = GroupByKey.build(Data3::getName, Data3::getValue);//.apply(); + + onEvent(new Data3("A", 10, 1)); + expected.put(keyFactory.apply(new Data3("A", 10, 1)), new Data3("A", 10, 1)); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 data2 = new Data3("A", 10, 2); + onEvent(data2); + expected.put(keyFactory.apply(data2), data2); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 data3 = new Data3("A", 10, 3); + onEvent(data3); + expected.put(keyFactory.apply(data3), data3); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 data4 = new Data3("A", 15, 111); + onEvent(data4); + expected.put(keyFactory.apply(data4), data4); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 dataB1 = new Data3("B", 10, 1); + onEvent(dataB1); + expected.put(keyFactory.apply(dataB1), dataB1); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 dataB2 = new Data3("B", 10, 99); + onEvent(dataB2); + expected.put(keyFactory.apply(dataB2), dataB2); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + } + + @Test + public void aggregateCompoundField() { + Map, Integer> expected = new HashMap<>(); + sep(c -> { + subscribe(Data3.class) + .groupByFieldsAggregate(Data3Aggregate::new, Data3::getName, Data3::getValue) + .map(GroupBy::toMap) + .id("results"); + }); + val keyFactory = GroupByKey.build(Data3::getName, Data3::getValue);//.apply(); + + onEvent(new Data3("A", 10, 1)); + expected.put(keyFactory.apply(new Data3("A", 10, 1)), 1); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 data2 = new Data3("A", 10, 2); + onEvent(data2); + expected.put(keyFactory.apply(data2), 3); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 data3 = new Data3("A", 10, 3); + onEvent(data3); + expected.put(keyFactory.apply(data3), 6); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 data4 = new Data3("A", 15, 111); + onEvent(data4); + expected.put(keyFactory.apply(data4), 111); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 dataB1 = new Data3("B", 10, 1); + onEvent(dataB1); + expected.put(keyFactory.apply(dataB1), 1); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 dataB2 = new Data3("B", 10, 99); + onEvent(dataB2); + expected.put(keyFactory.apply(dataB2), 100); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + } + + @Test + public void aggregateExtractedPropertyCompoundField() { + Map, Integer> expected = new HashMap<>(); + sep(c -> { + subscribe(Data3.class) + .groupByFieldsGetAndAggregate( + Data3::getX, + Aggregates.intSumFactory(), + Data3::getName, Data3::getValue) + .map(GroupBy::toMap) + .id("results"); + }); + val keyFactory = GroupByKey.build(Data3::getName, Data3::getValue);//.apply(); + + onEvent(new Data3("A", 10, 1)); + expected.put(keyFactory.apply(new Data3("A", 10, 1)), 1); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 data2 = new Data3("A", 10, 2); + onEvent(data2); + expected.put(keyFactory.apply(data2), 3); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 data3 = new Data3("A", 10, 3); + onEvent(data3); + expected.put(keyFactory.apply(data3), 6); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 data4 = new Data3("A", 15, 111); + onEvent(data4); + expected.put(keyFactory.apply(data4), 111); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 dataB1 = new Data3("B", 10, 1); + onEvent(dataB1); + expected.put(keyFactory.apply(dataB1), 1); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + + Data3 dataB2 = new Data3("B", 10, 99); + onEvent(dataB2); + expected.put(keyFactory.apply(dataB2), 100); + MatcherAssert.assertThat(getStreamed("results"), is(expected)); + } + + public static MyModel updateItemScalar(MyModel model, MyEvent myEvent) { model.createItem(myEvent.getData()); return model; diff --git a/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/MergeTest.java b/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/MergeTest.java new file mode 100644 index 000000000..825d0d0d7 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/MergeTest.java @@ -0,0 +1,62 @@ +package com.fluxtion.compiler.builder.dataflow; + +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import org.junit.Test; + +import java.util.concurrent.atomic.LongAdder; + +import static com.fluxtion.compiler.builder.dataflow.DataFlow.subscribe; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class MergeTest extends MultipleSepTargetInProcessTest { + public MergeTest(SepTestConfig testConfig) { + super(testConfig); + } + + @Test + public void mergeTest() { + LongAdder adder = new LongAdder(); + sep(c -> subscribe(Long.class) + .merge(subscribe(String.class).map(EventStreamBuildTest::parseLong)) + .sink("integers")); + addSink("integers", adder::add); + onEvent(200L); + onEvent("300"); + assertThat(adder.intValue(), is(500)); + } + + @Test + public void mergeTestStaticMethod() { + LongAdder adder = new LongAdder(); + sep(c -> + DataFlow.merge( + subscribe(Long.class), + subscribe(String.class).map(EventStreamBuildTest::parseLong) + ) + .sink("integers")); + addSink("integers", adder::add); + onEvent(200L); + onEvent("300"); + assertThat(adder.intValue(), is(500)); + } + + @SuppressWarnings("unchecked") + @Test + public void mergeTestVarArgStaticMethod() { + LongAdder adder = new LongAdder(); + sep(c -> + DataFlow.merge( + subscribe(Long.class), + subscribe(String.class).map(EventStreamBuildTest::parseLong), + subscribe(Integer.class).map(Integer::longValue) + ) + .sink("integers")); + addSink("integers", adder::add); + onEvent(200L); + onEvent("300"); + onEvent(500); + assertThat(adder.intValue(), is(1_000)); + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/NestedGroupByTest.java b/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/NestedGroupByTest.java index 2dbe54c78..f0148d4a2 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/NestedGroupByTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/builder/dataflow/NestedGroupByTest.java @@ -120,7 +120,7 @@ public void nestedGroupByToList_WithHelper() { @Test public void nestedDataFlowGroupBy_toCollector() { sep(c -> { - DataFlow.groupBy(Person::getCountry, Collectors.groupingBy(Person::getGender, Collectors.toList())) + DataFlow.groupBy(Person::getCountry, Collectors.groupingBy(Person::getGender, Collectors.listFactory())) .sink("groupBy"); }); this.addSink("groupBy", this::convertToMapList); @@ -179,7 +179,7 @@ public void nestedGroupByToCollector_List_WithHelper() { subscribe(Person.class) .groupBy( Person::getCountry, - Collectors.groupingBy(Person::getGender, Collectors.toList())) + Collectors.groupingBy(Person::getGender, Collectors.listFactory())) .sink("groupBy"); }); this.addSink("groupBy", this::convertToMapList); diff --git a/compiler/src/test/java/com/fluxtion/compiler/builder/factory/RootNodeWithFactoryTest.java b/compiler/src/test/java/com/fluxtion/compiler/builder/factory/RootNodeWithFactoryTest.java index 36911af6f..c9f6a4882 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/builder/factory/RootNodeWithFactoryTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/builder/factory/RootNodeWithFactoryTest.java @@ -6,15 +6,12 @@ import com.fluxtion.runtime.annotations.OnEventHandler; import com.fluxtion.runtime.annotations.OnTrigger; import com.fluxtion.runtime.event.Signal.IntSignal; -import com.google.auto.service.AutoService; import com.google.common.collect.ImmutableMap; import lombok.Data; import org.junit.Test; import java.util.Arrays; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -74,14 +71,4 @@ public boolean calculate() { } } - @AutoService(NodeFactory.class) - public static class SignalGroupCalculatorFactory implements NodeFactory { - - @Override - public SignalGroupCalculator createNode(Map config, NodeRegistry registry) { - @SuppressWarnings("unchecked") - List keys = (List) config.get("keys"); - return new SignalGroupCalculator(keys.stream().map(SignalHandler::new).collect(Collectors.toList())); - } - } } diff --git a/compiler/src/test/java/com/fluxtion/compiler/builder/factory/SignalGroupCalculatorFactory.java b/compiler/src/test/java/com/fluxtion/compiler/builder/factory/SignalGroupCalculatorFactory.java new file mode 100644 index 000000000..1e11baed1 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/builder/factory/SignalGroupCalculatorFactory.java @@ -0,0 +1,18 @@ +package com.fluxtion.compiler.builder.factory; + +import com.google.auto.service.AutoService; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +@AutoService(NodeFactory.class) +public class SignalGroupCalculatorFactory implements NodeFactory { + + @Override + public RootNodeWithFactoryTest.SignalGroupCalculator createNode(Map config, NodeRegistry registry) { + @SuppressWarnings("unchecked") + List keys = (List) config.get("keys"); + return new RootNodeWithFactoryTest.SignalGroupCalculator(keys.stream().map(RootNodeWithFactoryTest.SignalHandler::new).collect(Collectors.toList())); + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/builder/imperative/FluxtionBuilderTest.java b/compiler/src/test/java/com/fluxtion/compiler/builder/imperative/FluxtionBuilderTest.java index 4f7f6a97d..699b16395 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/builder/imperative/FluxtionBuilderTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/builder/imperative/FluxtionBuilderTest.java @@ -142,6 +142,30 @@ public void generateToStringWriterAndCompileTest() { assertThat(processor.getNodeById("handler").in, is("HELLO")); } + @Test + @SneakyThrows + public void generateNoCompileTestDeleteBackup() { + String path = new File(OutputRegistry.JAVA_TESTGEN_DIR).getCanonicalPath(); + Fluxtion.compile( + processorCfg -> processorCfg.addNode(new MyStringHandler(), "handler"), + compilerConfig -> { + compilerConfig.setCompileSource(false); + compilerConfig.setPackageName("com.whatever"); + compilerConfig.setClassName("MYProcessor"); + compilerConfig.setGenerateDescription(false); + compilerConfig.setOutputDirectory(path); + }); + Fluxtion.compile( + processorCfg -> processorCfg.addNode(new MyStringHandler(), "handler"), + compilerConfig -> { + compilerConfig.setCompileSource(false); + compilerConfig.setPackageName("com.whatever"); + compilerConfig.setClassName("MYProcessor"); + compilerConfig.setGenerateDescription(false); + compilerConfig.setOutputDirectory(path); + }); + } + @Test public void buildFromFluxtionGraphBuilder() throws ClassNotFoundException, NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException, IOException, URISyntaxException, NoSuchFieldException { int generationCount = Fluxtion.scanAndCompileFluxtionBuilders( diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/afterevent/PostEventTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/afterevent/PostEventTest.java index d2771e073..5bb492169 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/afterevent/PostEventTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/afterevent/PostEventTest.java @@ -42,6 +42,8 @@ public void afterEventTest() { onEvent("helloWorld"); assertThat(postInvocationTrace, is( Arrays.asList( + "Child::afterEvent", + "Parent::afterEvent", "Parent::newEvent", "Child::onEvent", "Child::eventComplete", diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/audit/FactoryAuditorTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/audit/FactoryAuditorTest.java index b529b6735..638e0db95 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/audit/FactoryAuditorTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/audit/FactoryAuditorTest.java @@ -5,8 +5,6 @@ */ package com.fluxtion.compiler.generation.audit; -import com.fluxtion.compiler.builder.factory.NodeFactory; -import com.fluxtion.compiler.builder.factory.NodeRegistry; import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; import com.fluxtion.runtime.annotations.OnEventHandler; @@ -17,8 +15,6 @@ import org.junit.Assert; import org.junit.Test; -import java.util.Map; - import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -37,7 +33,7 @@ public void test() { MyNode myNode = getAuditor("myNode"); Assert.assertTrue(myNode.registerCalled); onEvent(new CharEvent('a')); - assertThat(myNode.eventAuditCount, is(1)); + assertThat(myNode.eventAuditCount, is(2)); } public static class ParentNode { @@ -73,15 +69,4 @@ public void nodeRegistered(Object node, String nodeName) { } - public static class MyNodeFactory implements NodeFactory { - - @Override - public MyNode createNode(Map config, NodeRegistry registry) { - final MyNode myNode = new MyNode(); - registry.registerAuditor(myNode, "myNode"); - return myNode; - } - - } - } diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/audit/LifecycleAuditTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/audit/LifecycleAuditTest.java new file mode 100644 index 000000000..a8eddf827 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/audit/LifecycleAuditTest.java @@ -0,0 +1,173 @@ +package com.fluxtion.compiler.generation.audit; + +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import com.fluxtion.runtime.annotations.*; +import com.fluxtion.runtime.audit.EventLogControlEvent; +import com.fluxtion.runtime.audit.EventLogNode; +import com.fluxtion.runtime.audit.LogRecord; +import org.junit.Assert; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class LifecycleAuditTest extends MultipleSepTargetInProcessTest { + public LifecycleAuditTest(SepTestConfig testConfig) { + super(testConfig); + } + + @Test + public void lifecycleLog() { + List logSink = new ArrayList<>(); + addAuditor(); + sep(c -> c.addNode(new MyNode(new Parent()))); + sep.setAuditLogProcessor(logSink::add); + logSink.clear(); + start(); + Assert.assertEquals(1, logSink.size()); + onEvent("test"); + Assert.assertEquals(2, logSink.size()); + onEvent("test2"); + Assert.assertEquals(3, logSink.size()); + stop(); + Assert.assertEquals(4, logSink.size()); + tearDown(); + Assert.assertEquals(5, logSink.size()); + } + + @Test + public void replaceLogRecord() { + List logSink = new ArrayList<>(); + MyLogRecord myLogRecord = new MyLogRecord(); + addAuditor(); + sep(c -> c.addNode(new MyNode(new Parent()))); + sep.setAuditLogProcessor(logSink::add); + sep.setAuditLogRecordEncoder(myLogRecord); + start(); + Assert.assertEquals("started", myLogRecord.logMap.get("lifecycle")); + Assert.assertEquals(2, myLogRecord.getTerminateCount()); + + onEvent("test"); + Assert.assertEquals("eventHandler", myLogRecord.logMap.get("lifecycle")); + Assert.assertEquals("test", myLogRecord.logMap.get("message")); + Assert.assertEquals(3, myLogRecord.getTerminateCount()); + + onEvent("test2"); + Assert.assertEquals("eventHandler", myLogRecord.logMap.get("lifecycle")); + Assert.assertEquals("test2", myLogRecord.logMap.get("message")); + Assert.assertNull(myLogRecord.logMap.get("debugMessage")); + Assert.assertEquals(4, myLogRecord.getTerminateCount()); + + sep.setAuditLogLevel(EventLogControlEvent.LogLevel.DEBUG); + Assert.assertEquals(5, myLogRecord.getTerminateCount()); + + onEvent("testDebug"); + Assert.assertEquals("eventHandler", myLogRecord.logMap.get("lifecycle")); + Assert.assertEquals("testDebug", myLogRecord.logMap.get("message")); + Assert.assertEquals("testDebug", myLogRecord.logMap.get("debugMessage")); + Assert.assertEquals(6, myLogRecord.getTerminateCount()); + + stop(); + Assert.assertEquals("stop", myLogRecord.logMap.get("lifecycle")); + Assert.assertEquals("testDebug", myLogRecord.logMap.get("message")); + Assert.assertEquals(7, myLogRecord.getTerminateCount()); + + tearDown(); + Assert.assertEquals("teardown", myLogRecord.logMap.get("lifecycle")); + Assert.assertEquals("testDebug", myLogRecord.logMap.get("message")); + Assert.assertEquals(8, myLogRecord.getTerminateCount()); + } + + public static class Parent extends EventLogNode { + @Initialise + public void init() { + auditLog.info("lifecycle", "init"); + } + + @Start + public void start() { + auditLog.info("lifecycle", "started"); + } + + @Stop + public void stop() { + auditLog.info("lifecycle", "stop"); + } + + @TearDown + public void teardown() { + auditLog.info("lifecycle", "teardown"); + } + + @OnEventHandler + public boolean eventHandler(String in) { + auditLog.info("lifecycle", "eventHandler"); + auditLog.info("message", in); + auditLog.debug("debugMessage", in); + return false; + } + } + + public static class MyNode extends EventLogNode { + + private final Parent parent; + + public MyNode(Parent parent) { + this.parent = parent; + } + + @Initialise + public void init() { + auditLog.info("lifecycle", "init"); + } + + @Start + public void start() { + auditLog.info("lifecycle", "started"); + } + + @Stop + public void stop() { + auditLog.info("lifecycle", "stop"); + } + + @TearDown + public void teardown() { + auditLog.info("lifecycle", "teardown"); + } + + @OnEventHandler + public boolean eventHandler(String in) { + auditLog.info("lifecycle", "eventHandler"); + return false; + } + + } + + public static class MyLogRecord extends LogRecord { + public int terminateCount; + public Map logMap = new HashMap<>(); + + public MyLogRecord() { + super(null); + } + + @Override + public void addRecord(String sourceId, String propertyKey, CharSequence value) { + logMap.put(propertyKey, value.toString()); + } + + @Override + public boolean terminateRecord() { + terminateCount++; + return super.terminateRecord(); + } + + public int getTerminateCount() { + return terminateCount; + } + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/audit/MyNodeFactory.java b/compiler/src/test/java/com/fluxtion/compiler/generation/audit/MyNodeFactory.java new file mode 100644 index 000000000..a709b6470 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/audit/MyNodeFactory.java @@ -0,0 +1,17 @@ +package com.fluxtion.compiler.generation.audit; + +import com.fluxtion.compiler.builder.factory.NodeFactory; +import com.fluxtion.compiler.builder.factory.NodeRegistry; + +import java.util.Map; + +public class MyNodeFactory implements NodeFactory { + + @Override + public FactoryAuditorTest.MyNode createNode(Map config, NodeRegistry registry) { + final FactoryAuditorTest.MyNode myNode = new FactoryAuditorTest.MyNode(); + registry.registerAuditor(myNode, "myNode"); + return myNode; + } + +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/bufferevent/BufferEventGeneratedTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/bufferevent/BufferEventGeneratedTest.java index b5d1f561b..16df7e377 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/bufferevent/BufferEventGeneratedTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/bufferevent/BufferEventGeneratedTest.java @@ -38,7 +38,7 @@ public void triggeredOnlyCbListTest() { MatcherAssert.assertThat(child.triggerCount, CoreMatchers.is(0)); //parent MatcherAssert.assertThat(eventHolder.eventCount, CoreMatchers.is(3)); - MatcherAssert.assertThat(eventHolder.afterEventCount, CoreMatchers.is(0)); + MatcherAssert.assertThat(eventHolder.afterEventCount, CoreMatchers.is(1)); MatcherAssert.assertThat(eventHolder.afterTriggerCount, CoreMatchers.is(0)); @@ -50,7 +50,7 @@ public void triggeredOnlyCbListTest() { MatcherAssert.assertThat(child.triggerCount, CoreMatchers.is(0)); //parent MatcherAssert.assertThat(eventHolder.eventCount, CoreMatchers.is(3)); - MatcherAssert.assertThat(eventHolder.afterEventCount, CoreMatchers.is(0)); + MatcherAssert.assertThat(eventHolder.afterEventCount, CoreMatchers.is(1)); MatcherAssert.assertThat(eventHolder.afterTriggerCount, CoreMatchers.is(0)); @@ -61,7 +61,7 @@ public void triggeredOnlyCbListTest() { MatcherAssert.assertThat(child.triggerCount, CoreMatchers.is(1)); //parent MatcherAssert.assertThat(eventHolder.eventCount, CoreMatchers.is(3)); - MatcherAssert.assertThat(eventHolder.afterEventCount, CoreMatchers.is(1)); + MatcherAssert.assertThat(eventHolder.afterEventCount, CoreMatchers.is(2)); MatcherAssert.assertThat(eventHolder.afterTriggerCount, CoreMatchers.is(1)); } @@ -82,12 +82,12 @@ public void noTriggerClassWithAfterTest() { bufferEvent("test"); EventHolder eventHolder = getField("eventHolder"); MatcherAssert.assertThat(eventHolder.eventCount, CoreMatchers.is(3)); - MatcherAssert.assertThat(eventHolder.afterEventCount, CoreMatchers.is(0)); + MatcherAssert.assertThat(eventHolder.afterEventCount, CoreMatchers.is(1)); MatcherAssert.assertThat(eventHolder.afterTriggerCount, CoreMatchers.is(0)); triggerCalculation(); MatcherAssert.assertThat(eventHolder.eventCount, CoreMatchers.is(3)); - MatcherAssert.assertThat(eventHolder.afterEventCount, CoreMatchers.is(1)); + MatcherAssert.assertThat(eventHolder.afterEventCount, CoreMatchers.is(2)); MatcherAssert.assertThat(eventHolder.afterTriggerCount, CoreMatchers.is(1)); } diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/callback/CallbackNodeTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/callback/CallbackNodeTest.java new file mode 100644 index 000000000..072350cca --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/callback/CallbackNodeTest.java @@ -0,0 +1,90 @@ +package com.fluxtion.compiler.generation.callback; + +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import com.fluxtion.runtime.annotations.OnTrigger; +import com.fluxtion.runtime.callback.CallBackNode; +import lombok.Data; +import lombok.Value; +import org.junit.Assert; +import org.junit.Test; + +public class CallbackNodeTest extends MultipleSepTargetInProcessTest { + public CallbackNodeTest(SepTestConfig testConfig) { + super(testConfig); + } + + @Test + public void directInvokeTest() { + //writeOutputsToFile(true); + sep(c -> { + c.addNode(new Child(new ExternalCallback("callback1")), "child1"); + c.addNode(new Child(new ExternalCallback("callback2")), "child2"); + }); + + Child child1 = getField("child1"); + Child child2 = getField("child2"); + + ExternalCallback callback1 = getField("callback1"); + ExternalCallback callback2 = getField("callback2"); + + + callback1.doubleEvent(new MyEvent<>(32.4)); + Assert.assertEquals(32.4, (Double) child1.getResult(), 0.0001); + Assert.assertNull(child2.getResult()); + + callback1.stringEvent(new MyEvent<>("jjjj")); + Assert.assertEquals("jjjj", child1.getResult()); + Assert.assertNull(child2.getResult()); + + child1.setResult(null); + child2.setResult(null); + + callback2.doubleEvent(new MyEvent<>(32.4)); + Assert.assertNull(child1.getResult()); + Assert.assertEquals(32.4, (Double) child2.getResult(), 0.0001); + + + callback2.stringEvent(new MyEvent<>("jjjj")); + Assert.assertNull(child1.getResult()); + Assert.assertEquals("jjjj", child2.getResult()); + } + + + public static class ExternalCallback extends CallBackNode { + + Object update; + + public ExternalCallback(String name) { + super(name); + } + + public void stringEvent(MyEvent myEvent) { + update = myEvent.getData(); + triggerGraphCycle(); + } + + public void doubleEvent(MyEvent myEvent) { + update = myEvent.getData(); + triggerGraphCycle(); + } + + } + + @Data + public static class Child { + private final ExternalCallback externalCallback; + private Object result; + + @OnTrigger + public boolean triggered() { + result = externalCallback.update; + return true; + } + } + + @Value + public static class MyEvent { + T data; + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/FormatSerializerTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/FormatSerializerTest.java deleted file mode 100644 index 45532804f..000000000 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/FormatSerializerTest.java +++ /dev/null @@ -1,83 +0,0 @@ -package com.fluxtion.compiler.generation.customfieldserializer; - -import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; -import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; -import com.fluxtion.runtime.annotations.builder.AssignToField; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.Value; -import org.junit.Test; - -import java.text.DateFormat; -import java.text.DecimalFormat; -import java.text.NumberFormat; -import java.text.SimpleDateFormat; - -public class FormatSerializerTest extends MultipleSepTargetInProcessTest { - public FormatSerializerTest(SepTestConfig testConfig) { - super(testConfig); - } - - @Test - public void testIpSerializersConstructor() { - sep(c -> { - c.addNode(FormatHolder.builder() - .dateFormat(SimpleDateFormat.getDateTimeInstance()) - .simpleDateFormat(new SimpleDateFormat()) - .decimalFormat(new DecimalFormat()) - .numberFormat(NumberFormat.getCurrencyInstance()) - .build()); - }); - } - - @Test - public void testIpSerializersProperty() { - sep(c -> { - c.addNode(FormatHolderProperty.builder() - .dateFormat(SimpleDateFormat.getDateTimeInstance()) - .simpleDateFormat(new SimpleDateFormat()) - .decimalFormat(new DecimalFormat()) - .numberFormat(NumberFormat.getCurrencyInstance()) - .build()); - }); - } - - - @Builder - @Value - public static class FormatHolder { - - SimpleDateFormat simpleDateFormat; - DateFormat dateFormat; - DecimalFormat decimalFormat; - NumberFormat numberFormat; - - public FormatHolder( - @AssignToField("simpleDateFormat") SimpleDateFormat simpleDateFormat, - @AssignToField("dateFormat") DateFormat dateFormat, - @AssignToField("decimalFormat") DecimalFormat decimalFormat, - NumberFormat numberFormat) { - this.simpleDateFormat = simpleDateFormat; - this.dateFormat = dateFormat; - this.decimalFormat = decimalFormat; - this.numberFormat = numberFormat; - } - } - - @Builder - @AllArgsConstructor - @NoArgsConstructor - @Data - public static class FormatHolderProperty { - - private SimpleDateFormat simpleDateFormat; - private DateFormat dateFormat; - private DecimalFormat decimalFormat; - private NumberFormat numberFormat; - - - } - -} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/IoSerializerTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/IoSerializerTest.java deleted file mode 100644 index 581a2dd98..000000000 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/IoSerializerTest.java +++ /dev/null @@ -1,82 +0,0 @@ -package com.fluxtion.compiler.generation.customfieldserializer; - -import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; -import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.Value; -import org.junit.Test; - -import java.io.File; -import java.net.InetSocketAddress; -import java.net.MalformedURLException; -import java.net.URI; -import java.net.URISyntaxException; -import java.net.URL; - -public class IoSerializerTest extends MultipleSepTargetInProcessTest { - public IoSerializerTest(SepTestConfig testConfig) { - super(testConfig); - } - - @Test - public void testIoSerializersConstructor() { - sep(c -> { - try { - c.addNode(IoHolder.builder() - .file(new File("c:\\my_made_up\\path")) - .url(new URL("http://www.example.com/docs/resource1.html")) - .uri(new URI("http://www.example.com/docs/resource2.html")) - .inetSocketAddress(InetSocketAddress.createUnresolved("localhost", 2020)).build()); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - }); - } - - @Test - public void testIoSerializersProperty() { - sep(c -> { - - try { - c.addNode(IoHolderProperty.builder() - .file(new File("c:\\my_made_up\\path")) - .url(new URL("http://www.example.com/docs/resource1.html")) - .uri(new URI("http://www.example.com/docs/resource2.html")) - .inetSocketAddress(InetSocketAddress.createUnresolved("localhost", 2020)).build()); - } catch (MalformedURLException e) { - throw new RuntimeException(e); - } catch (URISyntaxException e) { - throw new RuntimeException(e); - } - }); - } - - - @Builder - @AllArgsConstructor - @Value - public static class IoHolder { - File file; - URL url; - URI uri; - InetSocketAddress inetSocketAddress; - } - - @Builder - @AllArgsConstructor - @NoArgsConstructor - @Data - public static class IoHolderProperty { - File file; - URL url; - URI uri; - InetSocketAddress inetSocketAddress; - } - - -} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/TimeSerializerTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/TimeSerializerTest.java deleted file mode 100644 index cb96f6729..000000000 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/TimeSerializerTest.java +++ /dev/null @@ -1,90 +0,0 @@ -package com.fluxtion.compiler.generation.customfieldserializer; - -import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; -import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; -import lombok.AllArgsConstructor; -import lombok.Builder; -import lombok.Data; -import lombok.NoArgsConstructor; -import lombok.Value; -import org.junit.Test; - -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.Period; -import java.time.ZonedDateTime; -import java.util.Date; - -public class TimeSerializerTest extends MultipleSepTargetInProcessTest { - public TimeSerializerTest(SepTestConfig testConfig) { - super(testConfig); - } - - @Test - public void testTimeSerializersConstructor() { - sep(c -> { - c.addNode( - TimeHolder.builder() - .duration(Duration.ofSeconds(100)) - .instant(Instant.ofEpochSecond(400, 90)) - .localDate(LocalDate.of(1989, 10, 25)) - .localDateTime(LocalDateTime.now()) - .localTime(LocalTime.now()) - .period(Period.of(10, 3, 4)) - .zonedDateTime(ZonedDateTime.now()) - .date(new Date()) - .build() - ); - }); - } - - @Test - public void testTimeSerializersProperty() { - sep(c -> { - c.addNode( - TimeHolderProperty.builder() - .duration(Duration.ofSeconds(100)) - .instant(Instant.ofEpochSecond(400, 90)) - .localDate(LocalDate.of(1989, 10, 25)) - .localDateTime(LocalDateTime.now()) - .localTime(LocalTime.now()) - .period(Period.of(10, 3, 4)) - .zonedDateTime(ZonedDateTime.now()) - .date(new Date()) - .build() - ); - }); - } - - @Builder - @AllArgsConstructor - @Value - public static class TimeHolder { - Instant instant; - Duration duration; - LocalDate localDate; - LocalTime localTime; - LocalDateTime localDateTime; - ZonedDateTime zonedDateTime; - Period period; - Date date; - } - - @Builder - @NoArgsConstructor - @AllArgsConstructor - @Data - public static class TimeHolderProperty { - Instant instant; - Duration duration; - LocalDate localDate; - LocalTime localTime; - LocalDateTime localDateTime; - ZonedDateTime zonedDateTime; - Period period; - Date date; - } -} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/eventdispatch/CombinedTriggerAndEventHandlerTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/eventdispatch/CombinedTriggerAndEventHandlerTest.java index 019706101..0f14c7e09 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/eventdispatch/CombinedTriggerAndEventHandlerTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/eventdispatch/CombinedTriggerAndEventHandlerTest.java @@ -4,6 +4,7 @@ import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; import com.fluxtion.runtime.annotations.OnEventHandler; import com.fluxtion.runtime.annotations.OnTrigger; +import com.fluxtion.runtime.node.NamedNode; import lombok.Data; import org.junit.Test; @@ -17,7 +18,7 @@ public CombinedTriggerAndEventHandlerTest(SepTestConfig compiledSep) { @Test public void noRootClassTest() { - sep(c -> c.addNode(new CombinedTriggerAndEventHandler(), "node")); + sep(new CombinedTriggerAndEventHandler()); CombinedTriggerAndEventHandler node = getField("node"); assertFalse(node.isEventNotified()); assertFalse(node.isTriggerNotified()); @@ -29,22 +30,32 @@ public void noRootClassTest() { @Test public void withRootClassTest() { - sep(c -> c.addNode( - new Root(c.addNode(new CombinedTriggerAndEventHandler(), "node")))); + sep(new Root(new CombinedTriggerAndEventHandler())); CombinedTriggerAndEventHandler node = getField("node"); + Root root = getField("root"); assertFalse(node.isEventNotified()); assertFalse(node.isTriggerNotified()); + assertFalse(root.isTriggered()); onEvent("hello"); assertTrue(node.isEventNotified()); assertFalse(node.isTriggerNotified()); + assertTrue(root.isTriggered()); + + root.triggered = false; + node.triggerNotified = false; + node.eventNotified = false; + + onEvent(22); + assertTrue(node.isEventNotified()); + assertFalse(node.isTriggerNotified()); + assertFalse(root.isTriggered()); } @Test public void withRootNoTriggerClassTest() { - sep(c -> c.addNode( - new RootNoTrigger(c.addNode(new CombinedTriggerAndEventHandler(), "node")))); + sep(new RootNoTrigger(new CombinedTriggerAndEventHandler())); CombinedTriggerAndEventHandler node = getField("node"); assertFalse(node.isEventNotified()); assertFalse(node.isTriggerNotified()); @@ -55,7 +66,7 @@ public void withRootNoTriggerClassTest() { } @Data - public static class CombinedTriggerAndEventHandler { + public static class CombinedTriggerAndEventHandler implements NamedNode { private boolean eventNotified; private boolean triggerNotified; @@ -65,15 +76,26 @@ public boolean stringUpdate(String in) { return true; } + @OnEventHandler(propagate = false) + public boolean intUpdate(int newValue) { + eventNotified = true; + return true; + } + @OnTrigger public boolean triggered() { triggerNotified = true; return true; } + + @Override + public String getName() { + return "node"; + } } @Data - public static class Root { + public static class Root implements NamedNode { private final Object parent; private boolean triggered; @@ -82,6 +104,11 @@ public boolean parentTriggered() { triggered = true; return true; } + + @Override + public String getName() { + return "root"; + } } @Data diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/eventdispatch/EventDispatchTraceTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/eventdispatch/EventDispatchTraceTest.java index 52d76367b..83d50544e 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/eventdispatch/EventDispatchTraceTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/eventdispatch/EventDispatchTraceTest.java @@ -6,21 +6,7 @@ import com.fluxtion.test.event.TestEventNoId; import com.fluxtion.test.event.TestEventNoIdFilteredHandler; import com.fluxtion.test.event.TestEventNoIdHandler; -import com.fluxtion.test.tracking.Extends_Handler_TraceEvent_InFilter_0; -import com.fluxtion.test.tracking.HandlerNoFilter_TraceEvent_InFilter_0; -import com.fluxtion.test.tracking.Handler_TraceEvent_0; -import com.fluxtion.test.tracking.Handler_TraceEvent_InFilter_0; -import com.fluxtion.test.tracking.Handler_UnMatchedFilter_TraceEvent_InFilter_0; -import com.fluxtion.test.tracking.Node_DirtyFilter_TraceEvent; -import com.fluxtion.test.tracking.Node_TraceEventHolder_Aggregator; -import com.fluxtion.test.tracking.Node_TraceEvent_0; -import com.fluxtion.test.tracking.Node_TraceEvent_Aggregator; -import com.fluxtion.test.tracking.Node_TraceEvent_IntFilter_0; -import com.fluxtion.test.tracking.TraceEvent; -import com.fluxtion.test.tracking.TraceEventHolder; -import com.fluxtion.test.tracking.TraceEventHolderChild; -import com.fluxtion.test.tracking.TraceEvent_0; -import com.fluxtion.test.tracking.TraceEvent_InFilter_0; +import com.fluxtion.test.tracking.*; import org.junit.Test; import static org.junit.Assert.assertEquals; @@ -392,6 +378,4 @@ public void eventLifeCycleTest() { JavaTestGeneratorHelper.testTraceIdOrder(te_2.getTraceAfterEventIdList(), "B3", "B1"); JavaTestGeneratorHelper.testTraceIdOrder(te_2.getTraceEventCompleteIdList(), "B2"); } - - } diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/exportservice/ExportMultipleServiceTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/exportservice/ExportMultipleServiceTest.java new file mode 100644 index 000000000..f277260e6 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/exportservice/ExportMultipleServiceTest.java @@ -0,0 +1,196 @@ +package com.fluxtion.compiler.generation.exportservice; + +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import com.fluxtion.runtime.annotations.AfterEvent; +import com.fluxtion.runtime.annotations.AfterTrigger; +import com.fluxtion.runtime.annotations.ExportService; +import com.fluxtion.runtime.annotations.OnTrigger; +import com.fluxtion.runtime.node.NamedNode; +import org.junit.Assert; +import org.junit.Test; + +public class ExportMultipleServiceTest extends MultipleSepTargetInProcessTest { + + + public ExportMultipleServiceTest(CompiledAndInterpretedSepTest.SepTestConfig testConfig) { + super(testConfig); + } + + + @Test + public void multiServiceExportAuditTest() { + sep(new BottomNode()); + sep.setAuditLogProcessor(l -> { + }); + Top top = sep.getExportedService(); + top.notifyTop(10); + top.notifyTopNoArgs(); + } + + @Test + public void multiServiceExportTest() { + sep(new BottomNode()); + //services + Top top = sep.getExportedService(); + Middle middle = sep.getExportedService(); + Bottom bottom = sep.getExportedService(); + //nodes + TopNode topNode = getField("top"); + MiddleNode middleNode = getField("middle"); + BottomNode bottomNode = getField("bottom"); + Assert.assertEquals(1, bottomNode.afterEventCount); + // + top.notifyTop(10); + Assert.assertEquals(1, topNode.functionCount); + Assert.assertEquals(0, middleNode.functionCount); + Assert.assertEquals(1, middleNode.triggerCount); + Assert.assertEquals(0, bottomNode.functionCount); + Assert.assertEquals(1, bottomNode.triggerCount); + Assert.assertEquals(1, bottomNode.afterTriggerCount); + Assert.assertEquals(2, bottomNode.afterEventCount); + + // + bottom.notifyBottom(10); + Assert.assertEquals(1, topNode.functionCount); + Assert.assertEquals(0, middleNode.functionCount); + Assert.assertEquals(1, middleNode.triggerCount); + Assert.assertEquals(1, bottomNode.functionCount); + Assert.assertEquals(1, bottomNode.triggerCount); + Assert.assertEquals(1, bottomNode.afterTriggerCount); + Assert.assertEquals(3, bottomNode.afterEventCount); + + //no trigger bottom + middle.notifyMiddle(-10); + Assert.assertEquals(1, topNode.functionCount); + Assert.assertEquals(1, middleNode.functionCount); + Assert.assertEquals(1, middleNode.triggerCount);//? + Assert.assertEquals(1, bottomNode.functionCount); + Assert.assertEquals(1, bottomNode.triggerCount); + Assert.assertEquals(1, bottomNode.afterTriggerCount); + Assert.assertEquals(4, bottomNode.afterEventCount); + + //trigger middle + middle.notifyMiddle(10); + Assert.assertEquals(1, topNode.functionCount); + Assert.assertEquals(2, middleNode.functionCount); + Assert.assertEquals(1, middleNode.triggerCount); + Assert.assertEquals(1, bottomNode.functionCount); + Assert.assertEquals(2, bottomNode.triggerCount); + Assert.assertEquals(2, bottomNode.afterTriggerCount); + Assert.assertEquals(5, bottomNode.afterEventCount); + } + + public interface Top { + void notifyTop(int arg); + + void notifyTopNoArgs(); + } + + public interface Middle { + boolean notifyMiddle(int arg); + } + + public interface Bottom { + boolean notifyBottom(int arg); + } + + public static class TopNode implements @ExportService Top, NamedNode { + + int functionCount = 0; + + @Override + public void notifyTop(int arg) { + functionCount++; + } + + public void notifyTopNoArgs() { + + } + + public boolean trigger() { + return true; + } + + @Override + public String getName() { + return "top"; + } + } + + public static class MiddleNode implements @ExportService Middle, NamedNode { + private final TopNode topNode; + int triggerCount = 0; + int functionCount = 0; + + public MiddleNode(TopNode topNode) { + this.topNode = topNode; + } + + public MiddleNode() { + this(new TopNode()); + } + + @Override + public boolean notifyMiddle(int arg) { + functionCount++; + return arg > 0; + } + + @OnTrigger + public boolean triggered() { + triggerCount++; + return true; + } + + @Override + public String getName() { + return "middle"; + } + } + + public static class BottomNode implements @ExportService Bottom, NamedNode { + + private final MiddleNode middleNode; + int triggerCount = 0; + int functionCount = 0; + int afterEventCount = 0; + int afterTriggerCount = 0; + + public BottomNode(MiddleNode middleNode) { + this.middleNode = middleNode; + } + + public BottomNode() { + this(new MiddleNode()); + } + + @Override + public boolean notifyBottom(int arg) { + functionCount++; + return false; + } + + @OnTrigger + public boolean triggered() { + triggerCount++; + return true; + } + + @AfterEvent + public void afterEvent() { + afterEventCount++; + } + + @AfterTrigger + public void afterTrigger() { + afterTriggerCount++; + } + + @Override + public String getName() { + return "bottom"; + } + } + +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/exportservice/ExportedServiceTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/exportservice/ExportedServiceTest.java new file mode 100644 index 000000000..eea63fe1d --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/exportservice/ExportedServiceTest.java @@ -0,0 +1,404 @@ +package com.fluxtion.compiler.generation.exportservice; + +import com.fluxtion.compiler.builder.dataflow.DataFlow; +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import com.fluxtion.runtime.annotations.ExportService; +import com.fluxtion.runtime.annotations.NoPropagateFunction; +import com.fluxtion.runtime.annotations.OnEventHandler; +import com.fluxtion.runtime.annotations.OnTrigger; +import com.fluxtion.runtime.annotations.builder.Inject; +import com.fluxtion.runtime.callback.Callback; +import com.fluxtion.runtime.dataflow.helpers.Mappers; +import com.fluxtion.runtime.node.NamedNode; +import org.junit.Assert; +import org.junit.Test; + +import java.util.concurrent.atomic.LongAdder; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +public class ExportedServiceTest extends MultipleSepTargetInProcessTest { + + public ExportedServiceTest(CompiledAndInterpretedSepTest.SepTestConfig testConfig) { + super(testConfig); + } + + @Test + public void exportVoidReturn() { + sep(new MyExportingServiceNode()); + init(); + MyService mySvc = sep.getExportedService(); + mySvc.testAdd(23, 50); + MyExportingServiceNode myNode = getField("myService"); + Assert.assertEquals(73, myNode.result); + } + + @Test + public void serviceIsExported() { + sep(new MyExportingServiceNode()); + init(); + Assert.assertFalse(sep.exportsService(MyMissingService.class)); + Assert.assertTrue(sep.exportsService(MyService.class)); + } + + @Test + public void serviceGetExportedByClass() { + sep(new MyExportingServiceNode()); + init(); + Assert.assertNull(sep.getExportedService(MyMissingService.class)); + Assert.assertNotNull(sep.getExportedService(MyService.class)); + } + + @Test + public void serviceDefaultGetExportedByClass() { + sep(new MyExportingServiceNode()); + init(); + Assert.assertNotNull(sep.getExportedService(MyMissingService.class, new MyMissingService() { + })); + Assert.assertNotNull(sep.getExportedService(MyService.class)); + } + + @Test + public void consumeExportedByClass() { + LongAdder counter = new LongAdder(); + sep(new MyExportingServiceNode()); + init(); + sep.consumeServiceIfExported(MyMissingService.class, svc -> counter.increment()); + Assert.assertEquals(0, counter.intValue()); + sep.consumeServiceIfExported(MyService.class, svc -> counter.increment()); + Assert.assertEquals(1, counter.intValue()); + } + + @Test + public void exportBooleanReturn() { + sep(new MyExportingTriggerServiceNode()); + init(); + MyTriggeringService mySvc = sep.getExportedService(); + mySvc.testAdd(23, 50); + MyExportingTriggerServiceNode myNode = getField("myService"); + Assert.assertEquals(73, myNode.result); + } + + @Test + public void exportVoidAndAlwaysTrigger() { + sep(new MyResultHolder()); + init(); + MyService mySvc = sep.getExportedService(); + MyResultHolder myResultHolder = getField("myResultHolder"); + mySvc.testAdd(23, 50); + MyExportingServiceNode myNode = getField("myService"); + Assert.assertEquals(73, myNode.result); + Assert.assertEquals(1, myResultHolder.triggerCount); + + mySvc.testSubtract(23, 8); + Assert.assertEquals(15, myNode.result); + Assert.assertEquals(2, myResultHolder.triggerCount); + } + + @Test + public void exportBooleanTriggerWhenPositive() { + sep(new MyResultHolderTrigger()); + init(); + MyTriggeringService mySvc = sep.getExportedService(); + MyResultHolderTrigger myResultHolder = getField("myResultHolder"); + mySvc.testAdd(23, 50); + MyExportingTriggerServiceNode myNode = getField("myService"); + Assert.assertEquals(73, myNode.result); + Assert.assertEquals(1, myResultHolder.triggerCount); + + mySvc.testSubtract(23, 8); + Assert.assertEquals(15, myNode.result); + Assert.assertEquals(2, myResultHolder.triggerCount); + + mySvc.triggerPositive(10); + Assert.assertEquals(3, myResultHolder.triggerCount); + + mySvc.triggerPositive(-10); + Assert.assertEquals(3, myResultHolder.triggerCount); + } + + @Test + public void exportServiceAndParentNotification() { + sep(c -> { + MyResultHolderTrigger resultHolderTrigger = c.addNode(new MyResultHolderTrigger()); + resultHolderTrigger.myExportingServiceNode.triggerObject = DataFlow.subscribe(String.class).flowSupplier(); + }); + init(); + MyTriggeringService mySvc = sep.getExportedService(); + MyResultHolderTrigger myResultHolder = getField("myResultHolder"); + mySvc.testAdd(23, 50); + MyExportingTriggerServiceNode myNode = getField("myService"); + Assert.assertEquals(73, myNode.result); + Assert.assertEquals(1, myResultHolder.triggerCount); + + mySvc.testSubtract(23, 8); + Assert.assertEquals(15, myNode.result); + Assert.assertEquals(2, myResultHolder.triggerCount); + + mySvc.triggerPositive(10); + Assert.assertEquals(3, myResultHolder.triggerCount); + + mySvc.triggerPositive(-10); + Assert.assertEquals(3, myResultHolder.triggerCount); + + onEvent("Hello"); + Assert.assertEquals(4, myResultHolder.triggerCount); + } + + @Test + public void serviceWithCallBack() { + sep(new ServiceWithCallback()); + MyTriggeringService mySvc = sep.getExportedService(); + ServiceWithCallback svcNode = getField("myService"); + Assert.assertEquals(0, svcNode.triggerCount); + + mySvc.triggerPositive(10); + Assert.assertEquals(1, svcNode.triggerCount); + + mySvc.triggerPositive(-10); + Assert.assertEquals(1, svcNode.triggerCount); + } + + @Test + public void noPropagateFunctionTest() { + sep(c -> DataFlow.subscribeToNode(new NoPropagateSomeMethodsMySvc()) + .mapToInt(Mappers.count()).id("count")); + MyTriggeringService triggeringService = sep.getExportedService(); + triggeringService.triggerPositive(10); + assertThat(getStreamed("count"), is(1)); + triggeringService.testAdd(10, 10); + assertThat(getStreamed("count"), is(1)); + triggeringService.testSubtract(10, 10); + assertThat(getStreamed("count"), is(2)); + } + + @Test + public void noPropagateAnyFunctionTest() { + writeOutputsToFile(true); + sep(c -> { + NoPropagateAnyMethodsMySvc noPropagateAnyMethodsMySvc = new NoPropagateAnyMethodsMySvc(); + noPropagateAnyMethodsMySvc.triggerObject = new StringHandler(); + DataFlow.subscribeToNode(noPropagateAnyMethodsMySvc) + .mapToInt(Mappers.count()).id("count"); + }); + onEvent("test"); + assertThat(getStreamed("count"), is(0)); + + MyService triggeringService = sep.getExportedService(); + triggeringService.testAdd(10, 10); + assertThat(getStreamed("count"), is(0)); + onEvent("test"); + assertThat(getStreamed("count"), is(1)); + + + triggeringService.testSubtract(10, 10); + assertThat(getStreamed("count"), is(1)); + onEvent("test"); + assertThat(getStreamed("count"), is(1)); + + triggeringService.testAdd(10, 10); + assertThat(getStreamed("count"), is(1)); + onEvent("test"); + assertThat(getStreamed("count"), is(2)); + } + + public interface MyTriggeringService extends MyService { + boolean triggerPositive(int x); + + } + + public interface MyService { + + void testAdd(int a, int b); + + void testSubtract(int a, int b); + } + + public interface MyMissingService { + } + + public static class StringHandler { + @OnEventHandler + public boolean onString(String id) { + return true; + } + } + + public static class MyExportingServiceNode implements @ExportService MyService, NamedNode { + int result; + + @Override + public void testAdd(int a, int b) { + result = a + b; + } + + @Override + public void testSubtract(int a, int b) { + result = a - b; + } + + @Override + public String getName() { + return "myService"; + } + } + + public static class MyExportingTriggerServiceNode implements @ExportService MyTriggeringService, NamedNode { + int result; + + public Object triggerObject; + + @Override + public void testAdd(int a, int b) { + result = a + b; + } + + @Override + public void testSubtract(int a, int b) { + result = a - b; + } + + @Override + public String getName() { + return "myService"; + } + + @Override + public boolean triggerPositive(int x) { + return x > 0; + } + + @OnTrigger + public boolean propagateParentNotification() { + return true; + } + } + + public static class NoPropagateSomeMethodsMySvc implements @ExportService MyTriggeringService { + + @Override + @NoPropagateFunction + public void testAdd(int a, int b) { + + } + + @Override + public void testSubtract(int a, int b) { + + } + + @Override + public boolean triggerPositive(int x) { + return x > 0; + } + } + + public static class NoPropagateAnyMethodsMySvc implements @ExportService MyService { + public Object triggerObject; + int sum = 0; + + @Override + @NoPropagateFunction + public void testAdd(int a, int b) { + sum = a + b; + } + + @Override + @NoPropagateFunction + public void testSubtract(int a, int b) { + sum = a - b; + } + + @OnTrigger + public boolean trigger() { + return sum > 0; + } + } + + public static class MyResultHolder implements NamedNode { + private final MyExportingServiceNode myExportingServiceNode; + private int triggerCount; + + public MyResultHolder() { + this(new MyExportingServiceNode()); + } + + public MyResultHolder(MyExportingServiceNode myExportingServiceNode) { + this.myExportingServiceNode = myExportingServiceNode; + } + + @OnTrigger + public boolean triggered() { + triggerCount++; + return true; + } + + @Override + public String getName() { + return "myResultHolder"; + } + } + + public static class MyResultHolderTrigger implements NamedNode { + private final MyExportingTriggerServiceNode myExportingServiceNode; + private int triggerCount; + + public MyResultHolderTrigger() { + this(new MyExportingTriggerServiceNode()); + } + + public MyResultHolderTrigger(MyExportingTriggerServiceNode myExportingServiceNode) { + this.myExportingServiceNode = myExportingServiceNode; + } + + @OnTrigger + public boolean triggered() { + triggerCount++; + return true; + } + + @Override + public String getName() { + return "myResultHolder"; + } + } + + public static class ServiceWithCallback implements @ExportService MyTriggeringService, NamedNode { + int result; + int triggerCount; + @Inject + public Callback callback; + + @Override + public void testAdd(int a, int b) { + result = a + b; + } + + @Override + public void testSubtract(int a, int b) { + result = a - b; + } + + @Override + public boolean triggerPositive(int x) { + boolean b = x > 0; + if (b) { + callback.fireCallback(); + } + return b; + } + + @OnTrigger + public boolean triggered() { + triggerCount++; + return true; + } + + @Override + public String getName() { + return "myService"; + } + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/exportservice/MethodEquivalentTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/exportservice/MethodEquivalentTest.java new file mode 100644 index 000000000..8558472cb --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/exportservice/MethodEquivalentTest.java @@ -0,0 +1,67 @@ +package com.fluxtion.compiler.generation.exportservice; + +import com.fluxtion.runtime.partition.LambdaReflection; +import org.junit.Test; + +import java.lang.reflect.Method; + +public class MethodEquivalentTest { + public static boolean equal(Method method1, Method other) { + if (method1.getName().equals(other.getName())) { + if (!method1.getReturnType().equals(other.getReturnType())) + return false; + return equalParamTypes(method1.getParameterTypes(), other.getParameterTypes()); + } + return false; + } + + static boolean equalParamTypes(Class[] params1, Class[] params2) { + /* Avoid unnecessary cloning */ + if (params1.length == params2.length) { + for (int i = 0; i < params1.length; i++) { + if (params1[i] != params2[i]) + return false; + } + return true; + } + return false; + } + + @Test + public void testMethod() { + LambdaReflection.SerializableBiConsumer aMethod = ClassA::method1; + LambdaReflection.SerializableBiConsumer bMethod = ClassB::method1; + equal(aMethod.method(), bMethod.method()); + } + + + public interface IntShared { + void method1(String a); + + void method2(T a); + } + + public static class ClassA implements IntShared { + @Override + public void method1(String a) { + + } + + @Override + public void method2(Integer a) { + + } + } + + public static class ClassB implements IntShared { + @Override + public void method1(String a) { + + } + + @Override + public void method2(Number a) { + + } + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/exportservice/OverloadedExportedFunctionTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/exportservice/OverloadedExportedFunctionTest.java new file mode 100644 index 000000000..4297ec0b5 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/exportservice/OverloadedExportedFunctionTest.java @@ -0,0 +1,53 @@ +package com.fluxtion.compiler.generation.exportservice; + +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import com.fluxtion.runtime.annotations.ExportService; +import org.apache.commons.lang3.mutable.MutableInt; +import org.junit.Assert; +import org.junit.Test; + +public class OverloadedExportedFunctionTest extends MultipleSepTargetInProcessTest { + public OverloadedExportedFunctionTest(CompiledAndInterpretedSepTest.SepTestConfig testConfig) { + super(testConfig); + } + + @Test + public void overloadMethodTest() { + sep(new Incrementer()); + OverloadedService overloadedSvc = sep.getExportedService(); + MutableInt mutableInt = new MutableInt(); + overloadedSvc.increment(); + overloadedSvc.increment(100); + overloadedSvc.writeValue(mutableInt); + Assert.assertEquals(101, mutableInt.intValue()); + } + + public interface OverloadedService { + void increment(); + + void increment(int amount); + + void writeValue(MutableInt mutableInt); + } + + public static class Incrementer implements @ExportService OverloadedService { + + private int currentValue; + + @Override + public void increment() { + currentValue++; + } + + @Override + public void increment(int amount) { + currentValue += amount; + } + + @Override + public void writeValue(MutableInt mutableInt) { + mutableInt.setValue(currentValue); + } + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/BasicSerializerTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/BasicSerializerTest.java new file mode 100644 index 000000000..b8e5c2b5a --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/BasicSerializerTest.java @@ -0,0 +1,78 @@ +package com.fluxtion.compiler.generation.fieldserializer; + +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import com.fluxtion.runtime.annotations.builder.AssignToField; +import com.fluxtion.runtime.node.NamedNode; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.junit.Assert; +import org.junit.Test; + +import java.lang.reflect.Array; + +public class BasicSerializerTest extends MultipleSepTargetInProcessTest { + + public BasicSerializerTest(CompiledAndInterpretedSepTest.SepTestConfig testConfig) { + super(testConfig); + } + + public enum SampleEnum {VAL1, VAL2, VALX} + + @Test + public void basicSerializerTest() { + + String[] t = new String[]{"test"}; + System.out.println(t.getClass()); + if (Array.class.isAssignableFrom(t.getClass())) { + System.out.println("TEST"); + } + + sep(buildTestHolder()); + Assert.assertEquals(buildTestHolder(), getField("holder")); + } + + private BasicTypeHolder buildTestHolder() { + return BasicTypeHolder.builder() + .cId("cid") + .name("holder") + .myChar('$') + .longVal(2334L) + .intVal(12) + .shortVal((short) 45) + .byteVal((byte) 12) + .doubleVal(35.8) + .doubleVal2(Double.NaN) + .floatVal(898.24f) + .boolean1Val(true) + .boolean2Val(false) + .classVal(String.class) + .enumVal(SampleEnum.VAL2) + .build(); + } + + + @Data + @Builder + @AllArgsConstructor + @RequiredArgsConstructor + public static class BasicTypeHolder implements NamedNode { + private String name; + @AssignToField("cId") + private final String cId; + private char myChar; + private long longVal; + private int intVal; + private short shortVal; + private byte byteVal; + private double doubleVal; + private double doubleVal2; + private float floatVal; + private boolean boolean1Val; + private boolean boolean2Val; + private Class classVal; + private SampleEnum enumVal; + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/CollectionSerializerTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/CollectionSerializerTest.java new file mode 100644 index 000000000..e58a25312 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/CollectionSerializerTest.java @@ -0,0 +1,47 @@ +package com.fluxtion.compiler.generation.fieldserializer; + +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import com.fluxtion.runtime.serializer.MapBuilder; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.junit.Assert; +import org.junit.Test; + +import java.util.Map; + +public class CollectionSerializerTest extends MultipleSepTargetInProcessTest { + public CollectionSerializerTest(CompiledAndInterpretedSepTest.SepTestConfig testConfig) { + super(testConfig); + } + + @Test + public void collectionTest() { + writeSourceFile = true; + CollectionHolder collectionHolder = CollectionHolder.builder() + .string2IdMap(MapBuilder.builder() + .put("id1", 1) + .put("id2", 2) + .build() + ) + .string2IdMapFinal(MapBuilder.builder() + .put("idXXX", 34) + .put("idYYY", 112) + .build()) + .build(); + + sep(c -> c.addNode(collectionHolder, "collectionHolder")); + Assert.assertEquals(collectionHolder, getField("collectionHolder")); + } + + @Data + @Builder + @AllArgsConstructor + @RequiredArgsConstructor + public static class CollectionHolder { + private Map string2IdMap; + private final Map string2IdMapFinal; + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/CustomSerializerTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/CustomSerializerTest.java similarity index 98% rename from compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/CustomSerializerTest.java rename to compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/CustomSerializerTest.java index a1e4dacdc..ee9d44574 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/CustomSerializerTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/CustomSerializerTest.java @@ -1,4 +1,4 @@ -package com.fluxtion.compiler.generation.customfieldserializer; +package com.fluxtion.compiler.generation.fieldserializer; import com.fluxtion.compiler.generation.serialiser.FieldContext; import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/constructor/EscapeStringTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/EscapeStringTest.java similarity index 98% rename from compiler/src/test/java/com/fluxtion/compiler/generation/constructor/EscapeStringTest.java rename to compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/EscapeStringTest.java index 1d7a5359f..641d2a17d 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/constructor/EscapeStringTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/EscapeStringTest.java @@ -15,7 +15,7 @@ * along with this program. If not, see * . */ -package com.fluxtion.compiler.generation.constructor; +package com.fluxtion.compiler.generation.fieldserializer; import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/FormatSerializerTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/FormatSerializerTest.java new file mode 100644 index 000000000..be70513db --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/FormatSerializerTest.java @@ -0,0 +1,71 @@ +package com.fluxtion.compiler.generation.fieldserializer; + +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import com.fluxtion.runtime.annotations.builder.AssignToField; +import lombok.*; +import org.junit.Assert; +import org.junit.Test; + +import java.text.DateFormat; +import java.text.DecimalFormat; +import java.text.NumberFormat; +import java.text.SimpleDateFormat; + +public class FormatSerializerTest extends MultipleSepTargetInProcessTest { + public FormatSerializerTest(SepTestConfig testConfig) { + super(testConfig); + } + + @Test + public void testIpSerializersConstructor() { + FormatHolder formatHolder = FormatHolder.builder() + .dateFormat(SimpleDateFormat.getDateTimeInstance()) + .simpleDateFormat(new SimpleDateFormat()) + .decimalFormat(new DecimalFormat()) + .numberFormat(NumberFormat.getCurrencyInstance()) + .build(); + sep(c -> c.addNode(formatHolder, "formatHolder")); + Assert.assertEquals(formatHolder, getField("formatHolder")); + } + + @Test + public void testIpSerializersProperty() { + FormatHolderProperty formatHolder = FormatHolderProperty.builder() + .dateFormat(SimpleDateFormat.getDateTimeInstance()) + .simpleDateFormat(new SimpleDateFormat()) + .decimalFormat(new DecimalFormat()) + .numberFormat(NumberFormat.getCurrencyInstance()) + .build(); + sep(c -> c.addNode(formatHolder, "formatHolder")); + Assert.assertEquals(formatHolder, getField("formatHolder")); + } + + @Builder + @Value + @AllArgsConstructor + public static class FormatHolder { + + @AssignToField("simpleDateFormat") + SimpleDateFormat simpleDateFormat; + @AssignToField("dateFormat") + DateFormat dateFormat; + @AssignToField("decimalFormat") + DecimalFormat decimalFormat; + @AssignToField("numberFormat") + NumberFormat numberFormat; + + } + + @Builder + @AllArgsConstructor + @NoArgsConstructor + @Data + public static class FormatHolderProperty { + private SimpleDateFormat simpleDateFormat; + private DateFormat dateFormat; + private DecimalFormat decimalFormat; + private NumberFormat numberFormat; + } + +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/IoSerializerTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/IoSerializerTest.java new file mode 100644 index 000000000..62a867e98 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/IoSerializerTest.java @@ -0,0 +1,86 @@ +package com.fluxtion.compiler.generation.fieldserializer; + +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import lombok.*; +import org.junit.Assert; +import org.junit.Test; + +import java.io.File; +import java.net.InetSocketAddress; +import java.net.URI; +import java.net.URL; + +public class IoSerializerTest extends MultipleSepTargetInProcessTest { + public IoSerializerTest(SepTestConfig testConfig) { + super(testConfig); + } + + @Test + @SneakyThrows + public void testIoSerializersConstructor() { +// writeSourceFile = true; + IoHolder ioData = IoHolder.builder() + .file(new File("c:\\my_made_up\\path")) + .url(new URL("http://www.example.com/docs/resource1.html")) + .uri(new URI("http://www.example.com/docs/resource2.html")) + .inetSocketAddress(InetSocketAddress.createUnresolved("localhost", 2020)).build(); + sep(c -> c.addNode(ioData, "ioData")); + Assert.assertEquals(ioData, getField("ioData")); + } + + @Test + @SneakyThrows + public void testIoSerializersProperty() { + IoHolderProperty ioData = IoHolderProperty.builder() + .file(new File("c:\\my_made_up\\path")) + .url(new URL("http://www.example.com/docs/resource1.html")) + .uri(new URI("http://www.example.com/docs/resource2.html")) + .inetSocketAddress(InetSocketAddress.createUnresolved("localhost", 2020)).build(); + sep(c -> c.addNode(ioData, "ioData")); + Assert.assertEquals(ioData, getField("ioData")); + } + + @Test + @SneakyThrows + public void serializeFieldTest() { + IoHolderFieldProperty ioData = new IoHolderFieldProperty(); + ioData.setFile(new File("c:\\my_made_up\\path")); + ioData.setUrl(new URL("http://www.example.com/docs/resource1.html")); + sep(c -> c.addNode(ioData, "ioData")); + Assert.assertEquals(ioData, getField("ioData")); + } + + + @Builder + @AllArgsConstructor + @Value + public static class IoHolder { + File file; + URL url; + URI uri; + InetSocketAddress inetSocketAddress; + } + + @Builder + @AllArgsConstructor + @NoArgsConstructor + @Data + public static class IoHolderProperty { + File file; + URL url; + URI uri; + InetSocketAddress inetSocketAddress; + } + + + @Data + public static class IoHolderFieldProperty { + File file; + URL url; + URI uri; + InetSocketAddress inetSocketAddress; + } + + +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/MetaSerializerTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/MetaSerializerTest.java new file mode 100644 index 000000000..34cbea76c --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/MetaSerializerTest.java @@ -0,0 +1,72 @@ +package com.fluxtion.compiler.generation.fieldserializer; + +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import com.fluxtion.runtime.callback.InstanceCallbackEvent; +import com.fluxtion.runtime.node.SingleNamedNode; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.Value; +import org.junit.Test; + +public class MetaSerializerTest extends MultipleSepTargetInProcessTest { + public MetaSerializerTest(CompiledAndInterpretedSepTest.SepTestConfig testConfig) { + super(testConfig); + } + + @Test + public void serializeFieldTest() { + sep(new ClassFieldHolder(String.class)); + } + + @Test + public void serializeConstructorTest() { + sep(new ClassFieldHolder(String.class)); + } + + @Test + public void serializeInnerClassConstructorTest() { + sep(new ClassFieldHolder(MyInnerClass.class)); + } + + @Test + public void serializeField_WithSingleNamedNodeTest() { + InstanceCallbackEvent.reset(); + sep(new CbSample("test")); + } + + @Data + @AllArgsConstructor + @NoArgsConstructor + public static class ClassFieldHolder { + private Class myClass; + } + + @Value + public static class ClassConstructorHolder { + private Class myClass; + } + + public static class MyInnerClass { + } + + public static class CbSample extends SingleNamedNode { + + private Class cbClass; + + public CbSample(String name) { + super(name); + cbClass = InstanceCallbackEvent.cbClassList.remove(0); + } + + + public Class getCbClass() { + return cbClass; + } + + public void setCbClass(Class cbClass) { + this.cbClass = cbClass; + } + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/util/MethodRefSerialisationTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/MethodRefSerialisationTest.java similarity index 98% rename from compiler/src/test/java/com/fluxtion/compiler/generation/util/MethodRefSerialisationTest.java rename to compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/MethodRefSerialisationTest.java index 8ef123d82..a22b65364 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/util/MethodRefSerialisationTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/MethodRefSerialisationTest.java @@ -15,9 +15,10 @@ * along with this program. If not, see * . */ -package com.fluxtion.compiler.generation.util; +package com.fluxtion.compiler.generation.fieldserializer; import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; import com.fluxtion.runtime.annotations.OnEventHandler; import com.fluxtion.runtime.annotations.OnTrigger; import com.fluxtion.runtime.annotations.PushReference; diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/TimeSerializerTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/TimeSerializerTest.java new file mode 100644 index 000000000..592ea8def --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/TimeSerializerTest.java @@ -0,0 +1,97 @@ +package com.fluxtion.compiler.generation.fieldserializer; + +import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; +import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import lombok.*; +import org.junit.Assert; +import org.junit.Before; +import org.junit.Test; + +import java.time.*; +import java.util.Date; + +public class TimeSerializerTest extends MultipleSepTargetInProcessTest { + private Date date; + private LocalDateTime localDateTime; + private LocalTime localTime; + private ZonedDateTime zonedDateTime; + + public TimeSerializerTest(SepTestConfig testConfig) { + super(testConfig); + } + + @Before + public void setTimes() { + localDateTime = LocalDateTime.now(); + localTime = LocalTime.now(); + zonedDateTime = ZonedDateTime.now(); + date = new Date(1258, 05, 06); + } + + @Test + public void testTimeSerializersConstructor() { + sep(c -> c.addNode(buildTimeAsConstructor(), "timeNode")); + Assert.assertEquals(buildTimeAsConstructor(), getField("timeNode")); + } + + @Test + public void testTimeSerializersProperty() { + sep(c -> c.addNode(buildTimeAsProperty(), "timeNode")); + Assert.assertEquals(buildTimeAsProperty(), getField("timeNode")); + } + + private TimeHolderProperty buildTimeAsProperty() { + return TimeHolderProperty.builder() + .duration(Duration.ofSeconds(100)) + .instant(Instant.ofEpochSecond(400, 90)) + .localDate(LocalDate.of(1989, 10, 25)) + .localDateTime(localDateTime) + .localTime(localTime) + .period(Period.of(10, 3, 4)) + .zonedDateTime(zonedDateTime) + .date(date) + .build(); + } + + private TimeHolder buildTimeAsConstructor() { + return TimeHolder.builder() + .duration(Duration.ofSeconds(100)) + .instant(Instant.ofEpochSecond(400, 90)) + .localDate(LocalDate.of(1989, 10, 25)) + .localDateTime(localDateTime) + .localTime(localTime) + .period(Period.of(10, 3, 4)) + .zonedDateTime(zonedDateTime) + .date(date) + .build(); + } + + @Builder + @AllArgsConstructor + @Value + public static class TimeHolder { + Instant instant; + Duration duration; + LocalDate localDate; + LocalTime localTime; + LocalDateTime localDateTime; + ZonedDateTime zonedDateTime; + Period period; + Date date; + } + + @Builder + @NoArgsConstructor + @AllArgsConstructor + @Data + public static class TimeHolderProperty { + Instant instant; + Duration duration; + LocalDate localDate; + LocalTime localTime; + LocalDateTime localDateTime; + ZonedDateTime zonedDateTime; + Period period; + Date date; + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/WithBuilderFactorySerializer.java b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/WithBuilderFactorySerializer.java similarity index 83% rename from compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/WithBuilderFactorySerializer.java rename to compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/WithBuilderFactorySerializer.java index 3fea93e39..f47d3130f 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/customfieldserializer/WithBuilderFactorySerializer.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/fieldserializer/WithBuilderFactorySerializer.java @@ -1,6 +1,6 @@ -package com.fluxtion.compiler.generation.customfieldserializer; +package com.fluxtion.compiler.generation.fieldserializer; -import com.fluxtion.compiler.generation.customfieldserializer.CustomSerializerTest.WithBuilderFactory; +import com.fluxtion.compiler.generation.fieldserializer.CustomSerializerTest.WithBuilderFactory; import com.fluxtion.compiler.generation.serialiser.FieldContext; import com.fluxtion.compiler.generation.serialiser.FieldToSourceSerializer; import com.google.auto.service.AutoService; diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/forkjoin/ForkJoinDataFlowTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/forkjoin/ForkJoinDataFlowTest.java index 85d167078..5c2e8fcb9 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/forkjoin/ForkJoinDataFlowTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/forkjoin/ForkJoinDataFlowTest.java @@ -34,7 +34,6 @@ public static String toUpper(Object in) { @Test public void testSimple() { - writeSourceFile = true; sep(c -> { AsyncProcess asynch1 = new AsyncProcess("asynch_1", 200); AsyncProcess asynch2 = new AsyncProcess("asynch_1", 100); @@ -53,8 +52,7 @@ public void testSimple() { @Test public void testSimple2() { - writeSourceFile = true; - writeOutputsToFile(true); +// writeOutputsToFile(true); sep(c -> { c.addNode(SyncCollectorMulti.builder().name("multiCollector") .parent(new AsyncProcess("asynch_1", 45)) @@ -74,8 +72,7 @@ public void log(LogRecord logRecord) { @Test public void parallelMap() { - writeSourceFile = true; - writeOutputsToFile(true); +// writeOutputsToFile(true); // addAuditor(); sep(c -> { c.addNode(SyncCollectorMulti.builder().name("multiCollector") diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/inject/Char2IntFactory.java b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/Char2IntFactory.java new file mode 100644 index 000000000..bdb2894d3 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/Char2IntFactory.java @@ -0,0 +1,14 @@ +package com.fluxtion.compiler.generation.inject; + +import com.fluxtion.compiler.builder.factory.NodeFactory; +import com.fluxtion.compiler.builder.factory.NodeRegistry; + +import java.util.Map; + +public class Char2IntFactory implements NodeFactory { + + @Override + public InjectionTest.Char2Int createNode(Map config, NodeRegistry registry) { + return new InjectionTest.Char2Int(); + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/inject/CharHandlerFactory.java b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/CharHandlerFactory.java new file mode 100644 index 000000000..9003456cd --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/CharHandlerFactory.java @@ -0,0 +1,18 @@ +package com.fluxtion.compiler.generation.inject; + +import com.fluxtion.compiler.builder.factory.NodeFactory; +import com.fluxtion.compiler.builder.factory.NodeRegistry; + +import java.util.Map; + +public class CharHandlerFactory implements NodeFactory { + + @Override + public InjectionTest.CharHandler createNode(Map arg0, NodeRegistry arg1) { + if (arg0.containsKey("char")) { + return new InjectionTest.CharHandler(((String) arg0.get("char")).charAt(0)); + } + return new InjectionTest.CharHandler(); + } + +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/inject/InjectFactoryByNameTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/InjectFactoryByNameTest.java index 6bb3d7e3b..8069acbf1 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/inject/InjectFactoryByNameTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/InjectFactoryByNameTest.java @@ -1,20 +1,13 @@ package com.fluxtion.compiler.generation.inject; -import com.fluxtion.compiler.builder.factory.NodeFactory; -import com.fluxtion.compiler.builder.factory.NodeRegistry; import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; import com.fluxtion.runtime.annotations.OnEventHandler; import com.fluxtion.runtime.annotations.builder.Inject; -import com.google.auto.service.AutoService; import org.junit.Assert; import org.junit.Test; -import java.lang.reflect.Field; -import java.lang.reflect.ParameterizedType; -import java.lang.reflect.Type; import java.util.Date; -import java.util.Map; public class InjectFactoryByNameTest extends MultipleSepTargetInProcessTest { @@ -100,48 +93,4 @@ public ServiceInjected(String className) { } } - @AutoService(NodeFactory.class) - public static class MyGenericServiceFactory implements NodeFactory { - @Override - public ServiceInjected createNode(Map config, NodeRegistry registry) { - Field field = (Field) config.get(NodeFactory.FIELD_KEY); - Type genericFieldType = field.getGenericType(); - final String typeName; - if (genericFieldType instanceof ParameterizedType) { - ParameterizedType aType = (ParameterizedType) genericFieldType; - Type[] fieldArgTypes = aType.getActualTypeArguments(); - typeName = ((Class) fieldArgTypes[0]).getCanonicalName(); - } else { - typeName = ""; - } - return new ServiceInjected<>(typeName); - } - - } - - @AutoService(NodeFactory.class) - public static class MyUniqueDataGreenFactory implements NodeFactory { - @Override - public MyUniqueData createNode(Map config, NodeRegistry registry) { - return new MyUniqueData("green"); - } - - @Override - public String factoryName() { - return "green"; - } - } - - @AutoService(NodeFactory.class) - public static class MyUniqueDataBlueFactory implements NodeFactory { - @Override - public MyUniqueData createNode(Map config, NodeRegistry registry) { - return new MyUniqueData("blue"); - } - - @Override - public String factoryName() { - return "blue"; - } - } } diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/inject/InjectFromContext.java b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/InjectFromContext.java index 1a9e9b268..d7ed663ce 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/inject/InjectFromContext.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/InjectFromContext.java @@ -105,6 +105,7 @@ public void addLambdaAsInjectedService() { @Test public void addNamedLambda() { +// writeSourceFile = true; enableInitCheck(false); sep(c -> { c.addNode(new InjectNamedInterfaceType(), "injectionHolder"); diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/inject/InjectionTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/InjectionTest.java index cd4124225..ea76dea1d 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/inject/InjectionTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/InjectionTest.java @@ -17,8 +17,6 @@ */ package com.fluxtion.compiler.generation.inject; -import com.fluxtion.compiler.builder.factory.NodeFactory; -import com.fluxtion.compiler.builder.factory.NodeRegistry; import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; import com.fluxtion.runtime.annotations.FilterId; @@ -36,8 +34,6 @@ import org.junit.Assert; import org.junit.Test; -import java.util.Map; - import static org.junit.Assert.assertTrue; /** @@ -259,24 +255,4 @@ public boolean onChar(CharEvent charEvent) { } } - public static class Char2IntFactory implements NodeFactory { - - @Override - public Char2Int createNode(Map config, NodeRegistry registry) { - return new Char2Int(); - } - } - - public static class CharHandlerFactory implements NodeFactory { - - @Override - public CharHandler createNode(Map arg0, NodeRegistry arg1) { - if (arg0.containsKey("char")) { - return new CharHandler(((String) arg0.get("char")).charAt(0)); - } - return new CharHandler(); - } - - } - } diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/inject/MyGenericServiceFactory.java b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/MyGenericServiceFactory.java new file mode 100644 index 000000000..45bcd1e5e --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/MyGenericServiceFactory.java @@ -0,0 +1,29 @@ +package com.fluxtion.compiler.generation.inject; + +import com.fluxtion.compiler.builder.factory.NodeFactory; +import com.fluxtion.compiler.builder.factory.NodeRegistry; +import com.google.auto.service.AutoService; + +import java.lang.reflect.Field; +import java.lang.reflect.ParameterizedType; +import java.lang.reflect.Type; +import java.util.Map; + +@AutoService(NodeFactory.class) +public class MyGenericServiceFactory implements NodeFactory { + @Override + public InjectFactoryByNameTest.ServiceInjected createNode(Map config, NodeRegistry registry) { + Field field = (Field) config.get(NodeFactory.FIELD_KEY); + Type genericFieldType = field.getGenericType(); + final String typeName; + if (genericFieldType instanceof ParameterizedType) { + ParameterizedType aType = (ParameterizedType) genericFieldType; + Type[] fieldArgTypes = aType.getActualTypeArguments(); + typeName = ((Class) fieldArgTypes[0]).getCanonicalName(); + } else { + typeName = ""; + } + return new InjectFactoryByNameTest.ServiceInjected<>(typeName); + } + +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/inject/MyUniqueDataBlueFactory.java b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/MyUniqueDataBlueFactory.java new file mode 100644 index 000000000..30f654a7d --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/MyUniqueDataBlueFactory.java @@ -0,0 +1,20 @@ +package com.fluxtion.compiler.generation.inject; + +import com.fluxtion.compiler.builder.factory.NodeFactory; +import com.fluxtion.compiler.builder.factory.NodeRegistry; +import com.google.auto.service.AutoService; + +import java.util.Map; + +@AutoService(NodeFactory.class) +public class MyUniqueDataBlueFactory implements NodeFactory { + @Override + public InjectFactoryByNameTest.MyUniqueData createNode(Map config, NodeRegistry registry) { + return new InjectFactoryByNameTest.MyUniqueData("blue"); + } + + @Override + public String factoryName() { + return "blue"; + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/inject/MyUniqueDataGreenFactory.java b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/MyUniqueDataGreenFactory.java new file mode 100644 index 000000000..6294b0de2 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/inject/MyUniqueDataGreenFactory.java @@ -0,0 +1,20 @@ +package com.fluxtion.compiler.generation.inject; + +import com.fluxtion.compiler.builder.factory.NodeFactory; +import com.fluxtion.compiler.builder.factory.NodeRegistry; +import com.google.auto.service.AutoService; + +import java.util.Map; + +@AutoService(NodeFactory.class) +public class MyUniqueDataGreenFactory implements NodeFactory { + @Override + public InjectFactoryByNameTest.MyUniqueData createNode(Map config, NodeRegistry registry) { + return new InjectFactoryByNameTest.MyUniqueData("green"); + } + + @Override + public String factoryName() { + return "green"; + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/reentrant/ReEntrantTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/reentrant/ReEntrantTest.java index cdd6155c4..7bd805fae 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/reentrant/ReEntrantTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/reentrant/ReEntrantTest.java @@ -3,6 +3,12 @@ import com.fluxtion.compiler.builder.dataflow.DataFlow; import com.fluxtion.compiler.generation.util.CompiledAndInterpretedSepTest.SepTestConfig; import com.fluxtion.compiler.generation.util.MultipleSepTargetInProcessTest; +import com.fluxtion.runtime.annotations.OnEventHandler; +import com.fluxtion.runtime.annotations.Start; +import com.fluxtion.runtime.annotations.Stop; +import com.fluxtion.runtime.annotations.builder.Inject; +import com.fluxtion.runtime.callback.EventDispatcher; +import com.fluxtion.runtime.output.SinkPublisher; import org.junit.Test; import java.util.ArrayList; @@ -46,4 +52,49 @@ public void queueEventsInOrderTest() { assertThat(results, is(expected)); } + + @Test + public void queueStartReentrantTest() { + List results = new ArrayList<>(); + sep(c -> { + c.addNode(new StartClass()); + }); + addSink("lifecycleSink", (String s) -> results.add(s)); + onEvent("event1"); + start(); + stop(); + + List expected = Arrays.asList( + "event1", + "started", "reentrant-start", + "stopped", "reentrant-stop" + ); + + assertThat(results, is(expected)); + } + + public static class StartClass { + public SinkPublisher publisher = new SinkPublisher<>("lifecycleSink"); + @Inject + public EventDispatcher dispatcher; + + @Start + public void start() { + dispatcher.processAsNewEventCycle("reentrant-start"); + publisher.publish("started"); + } + + @Stop + public void stop() { + dispatcher.processAsNewEventCycle("reentrant-stop"); + publisher.publish("stopped"); + } + + @OnEventHandler + public boolean stringUpdate(String in) { + publisher.publish(in); + return false; + } + + } } diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/subclass/EventHandlerSubClassTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/subclass/EventHandlerSubClassTest.java index 8156cb3ac..717977570 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/subclass/EventHandlerSubClassTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/subclass/EventHandlerSubClassTest.java @@ -49,19 +49,19 @@ public void testSubClassOnEvent() { //init assertThat(node.eventCount, is(0)); assertThat(node.initCount, is(1)); - assertThat(node.afterEvent, is(0)); + assertThat(node.afterEvent, is(1)); assertThat(node.batchEnd, is(0)); assertThat(node.batchPause, is(0)); assertThat(node.tearDownCount, is(0)); //event + after event onEvent("test string"); assertThat(node.eventCount, is(1)); - assertThat(node.afterEvent, is(1)); + assertThat(node.afterEvent, is(2)); //batch pause batchPause(); assertThat(node.eventCount, is(1)); assertThat(node.initCount, is(1)); - assertThat(node.afterEvent, is(1)); + assertThat(node.afterEvent, is(3)); assertThat(node.batchEnd, is(0)); assertThat(node.batchPause, is(1)); assertThat(node.tearDownCount, is(0)); @@ -69,7 +69,7 @@ public void testSubClassOnEvent() { batchEnd(); assertThat(node.eventCount, is(1)); assertThat(node.initCount, is(1)); - assertThat(node.afterEvent, is(1)); + assertThat(node.afterEvent, is(4)); assertThat(node.batchEnd, is(1)); assertThat(node.batchPause, is(1)); assertThat(node.tearDownCount, is(0)); @@ -77,7 +77,7 @@ public void testSubClassOnEvent() { tearDown(); assertThat(node.eventCount, is(1)); assertThat(node.initCount, is(1)); - assertThat(node.afterEvent, is(1)); + assertThat(node.afterEvent, is(5)); assertThat(node.batchEnd, is(1)); assertThat(node.batchPause, is(1)); assertThat(node.tearDownCount, is(1)); diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/subclass/EventSubclassTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/subclass/EventSubclassTest.java index c7fee0731..13d278030 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/subclass/EventSubclassTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/subclass/EventSubclassTest.java @@ -21,11 +21,9 @@ import com.fluxtion.runtime.annotations.OnEventHandler; import com.fluxtion.runtime.annotations.OnTrigger; import com.fluxtion.runtime.event.Event; +import com.fluxtion.runtime.node.SingleNamedNode; +import lombok.Value; import org.junit.Test; -import org.junit.runners.Parameterized; - -import java.util.Arrays; -import java.util.Collection; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -39,13 +37,29 @@ public EventSubclassTest(SepTestConfig compiledSep) { super(compiledSep); } + @Test + public void singleFilteredHandler() { + sep(new AnyTimeHandler("node")); + AnyTimeHandler handler = getField("node"); + onEvent(new ImplEvent()); + assertThat(handler.timeEvent, is(0)); + onEvent(new FilteredImplEvent("test")); + assertThat(handler.timeEvent, is(1)); + } - @Parameterized.Parameters - public static Collection compiledSepStrategy() { - return Arrays.asList( - SepTestConfig.COMPILED_METHOD_PER_EVENT, - SepTestConfig.INTERPRETED - ); + public static class AnyTimeHandler extends SingleNamedNode { + private int timeEvent; + + public AnyTimeHandler(String name) { + super(name); + } + + @OnEventHandler(filterString = "test") + public boolean anyTimeEvent(TimeEvent e) { + timeEvent++; + System.out.println("time event " + timeEvent); + return true; + } } @Test @@ -118,6 +132,17 @@ public static class ImplEvent extends TimeEvent { } + @Value + public static class FilteredImplEvent extends TimeEvent { + + String filterString; + + @Override + public String filterString() { + return filterString; + } + } + public static class ExtendTimeEvent extends TimeEvent { } diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/util/ClassUtilsTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/util/ClassUtilsTest.java index 7d0500663..1646c34b1 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/util/ClassUtilsTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/util/ClassUtilsTest.java @@ -18,6 +18,7 @@ package com.fluxtion.compiler.generation.util; import com.fluxtion.compiler.generation.model.CbMethodHandle; +import org.apache.commons.lang3.StringUtils; import org.junit.Assert; import org.junit.Test; @@ -26,6 +27,7 @@ import java.util.Arrays; import java.util.HashSet; import java.util.List; +import java.util.Map; import static org.junit.Assert.assertEquals; @@ -34,37 +36,6 @@ */ public class ClassUtilsTest { - - public static class A { - } - - public static class B { - } - - public static class B1 extends B { - } - - public static class B2 extends B1 { - } - - - public static class Handler1 { - public void handleA(A a) { - } - - public void handleB(B a) { - } - - public void handleB1(B1 a) { - } - - public void handleB2(B2 a) { - } - - public void handleObject(Object o) { - } - } - @Test public void testCbLocate() throws NoSuchMethodException { Handler1 h = new Handler1(); @@ -114,4 +85,56 @@ public void testClassHierarchySort() { Assert.assertTrue(sortClassHierarchy.indexOf(Object.class) > sortClassHierarchy.indexOf(A.class)); } + + public void crazyMethod(String a, List x, Map>, ?> map) { + + } + + @Test + public void printSignature() { + String generated = Arrays.stream(ClassUtilsTest.class.getDeclaredMethods()) + .filter(m -> m.getName().contains("crazyMethod")) + .findAny() + .map(m -> ClassUtils.wrapExportedFunctionCall(m, "wrappedCrazy", "instanceA")) + .get(); + String expected = "" + + "public void wrappedCrazy(java.lang.String arg0, java.util.List arg1, java.util.Map>, ?> arg2, String identifer){" + + " try {\n" + + " ExportingNode instance = getNodeById(identifer);\n" + + " instance.crazyMethod(arg0, arg1, arg2);\n" + + " } catch (NoSuchFieldException e) {\n" + + " throw new RuntimeException(e);\n" + + " }" + + "}"; + assertEquals(StringUtils.deleteWhitespace(expected), StringUtils.deleteWhitespace(generated)); + } + + public static class A { + } + + public static class B { + } + + public static class B1 extends B { + } + + public static class B2 extends B1 { + } + + public static class Handler1 { + public void handleA(A a) { + } + + public void handleB(B a) { + } + + public void handleB1(B1 a) { + } + + public void handleB2(B2 a) { + } + + public void handleObject(Object o) { + } + } } diff --git a/compiler/src/test/java/com/fluxtion/compiler/generation/util/MultipleSepTargetInProcessTest.java b/compiler/src/test/java/com/fluxtion/compiler/generation/util/MultipleSepTargetInProcessTest.java index c3d5080eb..150db0f65 100644 --- a/compiler/src/test/java/com/fluxtion/compiler/generation/util/MultipleSepTargetInProcessTest.java +++ b/compiler/src/test/java/com/fluxtion/compiler/generation/util/MultipleSepTargetInProcessTest.java @@ -29,6 +29,7 @@ import com.fluxtion.runtime.StaticEventProcessor; import com.fluxtion.runtime.audit.EventLogControlEvent; import com.fluxtion.runtime.audit.JULLogRecordListener; +import com.fluxtion.runtime.callback.InstanceCallbackEvent; import com.fluxtion.runtime.dataflow.FlowFunction; import com.fluxtion.runtime.lifecycle.BatchHandler; import com.fluxtion.runtime.lifecycle.Lifecycle; @@ -103,6 +104,7 @@ public void beforeTest() { addAuditor = false; reuseSep = false; callInit = true; + InstanceCallbackEvent.reset(); } @After @@ -136,6 +138,14 @@ protected StaticEventProcessor sep(Consumer cfgBuilder) { return sep(cfgBuilder, new HashMap<>()); } + protected StaticEventProcessor sep(Object... nodes) { + return sep(c -> { + for (int i = 0; i < nodes.length; i++) { + c.addNode(nodes[i]); + } + }); + } + protected StaticEventProcessor sep(Consumer cfgBuilder, Map contextMap) { Consumer wrappedBuilder = cfgBuilder; if (addAuditor || inlineCompiled || !instanceOfDispatch) { diff --git a/compiler/src/test/java/com/fluxtion/compiler/spring/extern/Account.java b/compiler/src/test/java/com/fluxtion/compiler/spring/extern/Account.java new file mode 100644 index 000000000..9b7ed3f40 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/spring/extern/Account.java @@ -0,0 +1,7 @@ +package com.fluxtion.compiler.spring.extern; + +public interface Account { + void debit(double debitAmount); + + void credit(double creditAmount); +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/spring/extern/AccountNode.java b/compiler/src/test/java/com/fluxtion/compiler/spring/extern/AccountNode.java new file mode 100644 index 000000000..f59b14157 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/spring/extern/AccountNode.java @@ -0,0 +1,28 @@ +package com.fluxtion.compiler.spring.extern; + +import com.fluxtion.runtime.annotations.ExportService; +import lombok.Getter; +import lombok.ToString; + +@Getter +@ToString +public class AccountNode implements @ExportService Account { + + private double debitAmount; + private double creditAmount; + + @Override + public void debit(double debitAmount) { + this.debitAmount = debitAmount; + } + + @Override + public void credit(double creditAmount) { + this.creditAmount = creditAmount; + } + + void clearTransaction() { + creditAmount = Double.NaN; + debitAmount = Double.NaN; + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/spring/extern/BankTransactionStore.java b/compiler/src/test/java/com/fluxtion/compiler/spring/extern/BankTransactionStore.java new file mode 100644 index 000000000..af8f5ab19 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/spring/extern/BankTransactionStore.java @@ -0,0 +1,15 @@ +package com.fluxtion.compiler.spring.extern; + +import com.fluxtion.runtime.annotations.OnTrigger; +import lombok.Data; + +@Data +public class BankTransactionStore { + private AccountNode accountNode; + + @OnTrigger + public boolean updateAccounts() { + System.out.println("updating account:" + accountNode); + return false; + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/spring/extern/EventBean.java b/compiler/src/test/java/com/fluxtion/compiler/spring/extern/EventBean.java new file mode 100644 index 000000000..506f05a52 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/spring/extern/EventBean.java @@ -0,0 +1,14 @@ +package com.fluxtion.compiler.spring.extern; + +import com.fluxtion.runtime.annotations.OnEventHandler; + +public class EventBean { + + public String input; + + @OnEventHandler + public boolean stringUpdate(String in) { + input = in; + return true; + } +} diff --git a/compiler/src/test/java/com/fluxtion/compiler/spring/extern/SpringLoaderTest.java b/compiler/src/test/java/com/fluxtion/compiler/spring/extern/SpringLoaderTest.java new file mode 100644 index 000000000..2f873fc89 --- /dev/null +++ b/compiler/src/test/java/com/fluxtion/compiler/spring/extern/SpringLoaderTest.java @@ -0,0 +1,81 @@ +package com.fluxtion.compiler.spring.extern; + +import com.fluxtion.compiler.FluxtionCompilerConfig; +import com.fluxtion.compiler.builder.dataflow.DataFlow; +import com.fluxtion.compiler.extern.spring.FluxtionSpring; +import com.fluxtion.compiler.generation.OutputRegistry; +import com.fluxtion.runtime.EventProcessor; +import com.fluxtion.runtime.dataflow.helpers.Mappers; +import org.junit.Assert; +import org.junit.Test; + +import java.nio.file.FileSystems; +import java.nio.file.Path; + +public class SpringLoaderTest { + + @Test + public void loadSingleSpringBeanInterpret() throws NoSuchFieldException { + Path path = FileSystems.getDefault().getPath("src/test/resources/spring/application-context-test-1.xml"); + EventProcessor eventProcessor = FluxtionSpring.interpret(path); + eventProcessor.init(); + eventProcessor.onEvent("HELLO WORLD"); + EventBean eventBean = eventProcessor.getNodeById("eventBean"); + Assert.assertEquals("HELLO WORLD", eventBean.input); + } + + @Test + public void loadSingleSpringBeanCompile() throws NoSuchFieldException { + Path path = FileSystems.getDefault().getPath("src/test/resources/spring/application-context-test-1.xml"); + EventProcessor eventProcessor = FluxtionSpring.compile(path); + eventProcessor.init(); + eventProcessor.onEvent("HELLO WORLD"); + EventBean eventBean = eventProcessor.getNodeById("eventBean"); + Assert.assertEquals("HELLO WORLD", eventBean.input); + } + + @Test + public void loadSingleSpringBeanCompileAot() throws NoSuchFieldException { + Path path = FileSystems.getDefault().getPath("src/test/resources/spring/application-context-test-1.xml"); + EventProcessor eventProcessor = FluxtionSpring.compileAot(path, (FluxtionCompilerConfig c) -> { + c.setOutputDirectory(OutputRegistry.JAVA_TESTGEN_DIR); + c.setGenerateDescription(false); + c.setWriteSourceToFile(false); + }); + eventProcessor.init(); + eventProcessor.onEvent("HELLO WORLD"); + EventBean eventBean = eventProcessor.getNodeById("eventBean"); + Assert.assertEquals("HELLO WORLD", eventBean.input); + } + + @Test + public void customiseConfig() throws NoSuchFieldException { + EventProcessor eventProcessor = FluxtionSpring.interpret( + FileSystems.getDefault().getPath("src/test/resources/spring/application-context-test-1.xml"), + c -> { + c.addNode(new EventBean(), "customBean"); + DataFlow.subscribeToNode(c.getNode("eventBean")) + .mapToInt(Mappers.count()) + .id("springBeanCount"); + } + ); + eventProcessor.init(); + eventProcessor.onEvent("HELLO WORLD"); + EventBean eventBean = eventProcessor.getNodeById("eventBean"); + Assert.assertEquals("HELLO WORLD", eventBean.input); + // + eventBean = eventProcessor.getNodeById("customBean"); + Assert.assertEquals("HELLO WORLD", eventBean.input); + Assert.assertEquals(1, (int) eventProcessor.getStreamed("springBeanCount")); + } + + @Test + public void loadGraphSpringInterpret() throws NoSuchFieldException { + Path path = FileSystems.getDefault().getPath("src/test/resources/spring/application-context-test-accountgraph.xml"); + EventProcessor eventProcessor = FluxtionSpring.interpret(path); + eventProcessor.init(); + Account account = eventProcessor.getExportedService(); + account.credit(12.4); + account.debit(31.6); + } +} diff --git a/compiler/src/test/resources/META-INF/services/com.fluxtion.compiler.builder.factory.NodeFactory b/compiler/src/test/resources/META-INF/services/com.fluxtion.compiler.builder.factory.NodeFactory index ae15b6419..36b9bff8d 100644 --- a/compiler/src/test/resources/META-INF/services/com.fluxtion.compiler.builder.factory.NodeFactory +++ b/compiler/src/test/resources/META-INF/services/com.fluxtion.compiler.builder.factory.NodeFactory @@ -1,4 +1,9 @@ -com.fluxtion.compiler.generation.inject.InjectionTest$Char2IntFactory -com.fluxtion.compiler.generation.inject.InjectionTest$CharHandlerFactory -com.fluxtion.compiler.generation.audit.FactoryAuditorTest$MyNodeFactory +com.fluxtion.compiler.builder.factory.SignalGroupCalculatorFactory +com.fluxtion.compiler.generation.audit.MyNodeFactory +com.fluxtion.compiler.generation.inject.Char2IntFactory +com.fluxtion.compiler.generation.inject.CharHandlerFactory +com.fluxtion.compiler.generation.inject.MyGenericServiceFactory +com.fluxtion.compiler.generation.inject.MyUniqueDataBlueFactory +com.fluxtion.compiler.generation.inject.MyUniqueDataGreenFactory +com.fluxtion.test.nodes.KeyProcessorFactory diff --git a/compiler/src/test/resources/META-INF/services/com.fluxtion.runtime.annotations.builder.ClassProcessor b/compiler/src/test/resources/META-INF/services/com.fluxtion.runtime.annotations.builder.ClassProcessor deleted file mode 100644 index b34333856..000000000 --- a/compiler/src/test/resources/META-INF/services/com.fluxtion.runtime.annotations.builder.ClassProcessor +++ /dev/null @@ -1 +0,0 @@ -com.fluxtion.compiler.generation.AnnotatedCompilerTest$MyClassProcessor diff --git a/compiler/src/test/resources/spring/application-context-test-1.xml b/compiler/src/test/resources/spring/application-context-test-1.xml new file mode 100644 index 000000000..4ae01d872 --- /dev/null +++ b/compiler/src/test/resources/spring/application-context-test-1.xml @@ -0,0 +1,11 @@ + + + + + + + \ No newline at end of file diff --git a/compiler/src/test/resources/spring/application-context-test-accountgraph.xml b/compiler/src/test/resources/spring/application-context-test-accountgraph.xml new file mode 100644 index 000000000..d8f03f8ee --- /dev/null +++ b/compiler/src/test/resources/spring/application-context-test-accountgraph.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/docs/Gemfile b/docs/Gemfile index e786b102c..26fe023a2 100644 --- a/docs/Gemfile +++ b/docs/Gemfile @@ -16,6 +16,8 @@ gem "github-pages", "~> 226", group: :jekyll_plugins # If you have any plugins, put them here! group :jekyll_plugins do gem "jekyll-feed", "~> 0.12" + gem "just-the-docs", "0.5.4" # pinned to the current release + # gem "just-the-docs" # always download the latest release end # Windows and JRuby does not include zoneinfo files, so bundle the tzinfo-data gem @@ -33,5 +35,3 @@ gem "wdm", "~> 0.1.1", :platforms => [:mingw, :x64_mingw, :mswin] gem "http_parser.rb", "~> 0.6.0", :platforms => [:jruby] gem "webrick", "~> 1.8" - -gem "webrick", "~> 1.8" diff --git a/docs/Gemfile.lock b/docs/Gemfile.lock index d0aabf3f8..0aca42b17 100644 --- a/docs/Gemfile.lock +++ b/docs/Gemfile.lock @@ -197,6 +197,10 @@ GEM gemoji (~> 3.0) html-pipeline (~> 2.2) jekyll (>= 3.0, < 5.0) + just-the-docs (0.5.4) + jekyll (>= 3.8.5) + jekyll-seo-tag (>= 2.0) + rake (>= 12.3.1) kramdown (2.3.2) rexml kramdown-parser-gfm (1.1.0) @@ -224,6 +228,7 @@ GEM forwardable-extended (~> 2.6) public_suffix (4.0.7) racc (1.6.0) + rake (13.0.6) rb-fsevent (0.11.1) rb-inotify (0.10.1) ffi (~> 1.0) @@ -265,6 +270,7 @@ DEPENDENCIES github-pages (~> 226) http_parser.rb (~> 0.6.0) jekyll-feed (~> 0.12) + just-the-docs (= 0.5.4) minima (~> 2.5) tzinfo (~> 1.2) tzinfo-data diff --git a/docs/_config.yml b/docs/_config.yml index 8f95a38be..35810ce24 100644 --- a/docs/_config.yml +++ b/docs/_config.yml @@ -18,13 +18,15 @@ # You can create any custom variable you would like, and they will be accessible # in the templates via {{ site.myvariable }}. -theme: jekyll-theme-cayman +#theme: just-the-docs + +#url: https://just-the-docs.github.io title: Fluxtion streaming description: Documentation for Fluxtion event processor remote_theme: pmarsceill/just-the-docs aux_links: - "release 9.0.1": - - "//github.com/v12technology/fluxtion/tree/9.0.1" + "release 9.1.9": + - "//github.com/v12technology/fluxtion/tree/9.1.9" heading_anchors: true # Color scheme supports "light" (default) and "dark" @@ -39,13 +41,14 @@ kramdown: block: line_numbers: true -fluxtion_version: 9.0.1 -fluxtion_src: https://github.com/v12technology/fluxtion/blob/9.0.1 -fluxtion_src_runtime: https://github.com/v12technology/fluxtion/blob/9.0.1/runtime/src/main/java/com/fluxtion/runtime -fluxtion_src_compiler: https://github.com/v12technology/fluxtion/blob/9.0.1/compiler/src/main/java/com/fluxtion/compiler -EventProcessor_link: https://github.com/v12technology/fluxtion/blob/9.0.1/runtime/src/main/java/com/fluxtion/runtime/EventProcessor.java -Fluxtion_link: https://github.com/v12technology/fluxtion/blob/9.0.1/compiler/src/main/java/com/fluxtion/compiler/Fluxtion.java -cookbook_src: https://github.com/v12technology/fluxtion-examples/tree/cook_subscription_example/cookbook/src/main/java/com/fluxtion/example/cookbook/ +fluxtion_version: 9.1.9 +fluxtion_src: https://github.com/v12technology/fluxtion/blob/9.1.9 +fluxtion_src_runtime: https://github.com/v12technology/fluxtion/blob/9.1.9/runtime/src/main/java/com/fluxtion/runtime +fluxtion_src_compiler: https://github.com/v12technology/fluxtion/blob/9.1.9/compiler/src/main/java/com/fluxtion/compiler +EventProcessor_link: https://github.com/v12technology/fluxtion/blob/9.1.9/runtime/src/main/java/com/fluxtion/runtime/EventProcessor.java +Fluxtion_link: https://github.com/v12technology/fluxtion/blob/9.1.9/compiler/src/main/java/com/fluxtion/compiler/Fluxtion.java +cookbook_src: https://github.com/v12technology/fluxtion-examples/tree/main/cookbook/src/main/java/com/fluxtion/example/cookbook + ga_tracking: G-NGDV621P1P ga_tracking_anonymize_ip: true # Use GDPR compliant Google Analytics settings (true by default) @@ -56,6 +59,13 @@ logo: "/images/fluxtion_logo_small.png" plugins: - jekyll-feed +callouts: + info: + color: blue + note: + title: NOTE + color: blue + # Exclude from processing. # The following items will not be processed, by default. # Any item listed under the `exclude:` key here will be automatically added to diff --git a/docs/_sass/color_schemes/fluxtion_style.scss b/docs/_sass/color_schemes/fluxtion_style.scss index dee8725a5..772e91374 100644 --- a/docs/_sass/color_schemes/fluxtion_style.scss +++ b/docs/_sass/color_schemes/fluxtion_style.scss @@ -1,3 +1,3 @@ -@import "./color_schemes/dark"; +@import "./color_schemes/light"; -$content-width: 1000px; +$content-width: 70rem; \ No newline at end of file diff --git a/docs/_sass/custom/custom.scss b/docs/_sass/custom/custom.scss index fd80779b0..cdca6f9d5 100644 --- a/docs/_sass/custom/custom.scss +++ b/docs/_sass/custom/custom.scss @@ -40,3 +40,18 @@ to {opacity: 1;} } +html { font-size: 1.05rem !important; scroll-behavior: smooth; } +@media (min-width: 31.25rem) { html { font-size: 1.05rem !important; } } + +//body { line-height: 1.3;} + +.grid { + display: flex; +} +.col-1-2 { + flex: 1; +} +.cole-1-2:last-child { + margin-left: 20px; +} + diff --git a/docs/index.md b/docs/index.md index c3129b935..182570ee9 100644 --- a/docs/index.md +++ b/docs/index.md @@ -1,69 +1,114 @@ --- title: Overview -has_children: true +has_children: false nav_order: 1 published: true --- -# Introduction to Fluxtion +# Fluxtion is event driven Java -Welcome to Fluxtion, and thanks for coming, hope you enjoy exploring :) +--- + +Fluxtion is a java development productivity tool that makes writing and maintaining event driven business logic cheaper +and quicker. The Fluxtion dependency injection container exposes user beans as event driven service endpoints. A +container instance can be connected to any event delivery system freeing the business logic from messaging vendor lock-in. + +{: .info } +Fluxtion minimises the cost of developing and maintaining event driven business logic +{: .fs-4 } + +Developers concentrate on developing and extending business logic, dependency injection and realtime event dispatch is +handled by the container. The container supports: + +
    +
    +
    +
      +
    • Streaming event processing
    • +
    • AOT compilation for fast start
    • +
    • Spring integration
    • +
    +
    +
    +
    +
    +
      +
    • Low latency microsecond response
    • +
    • Event sourcing compatible
    • +
    • Functional and imperative construction
    • +
    +
    +
    +
    + +# The cost of complexity problem + +Increasing system complexity makes delivery of new features expensive and time-consuming to deliver. Efficiently managing +complexity reduces both operational costs and time to market for new functionality, critical for a business to remain +profitable in a competitive environment. -Fluxtion is a java library utility that builds embeddable, reactive complex event processors for data in motion -streaming -applications. Suitable use cases include: +Event driven systems have two types of complexity to manage: -- **Real-time applications** processing multiple event streams -- **Embedding within an existing system** No middleware vendor lock-in -- **Edge processing** executing on edge devices zero dependency -- **Low latency** response time in microseconds -- **Fast start times** supports ahead of time compilation +- Delivering events to application components in a fault-tolerant predictable fashion. +- Developing application logic responses to events that meets business requirements -Write simple clean Java code to create real-time applications. programs are quick to build, test, deploy and debug ,with -no dependencies. Data streams can be merged, filtered, aggregated, joined, grouped and enriched. Windowing of data is -fully supported. +Initially all the project complexity centres on the event delivery system, but over time this system becomes stable and +the complexity demands are minimal. Pre-packaged event delivery systems are a common solution to control complexity and +cost of event distribution. The opposite is true for event driven application logic, functional requirements increase +over time and developing application logic becomes ever more complex and expensive to deliver. -A full set of tools for debugging, tracing, auditing and visualisation are provided to reduce development and support -costs. +{: .info } +Fluxtion combines dependency injection and event dispatch increasing developer productivity +{: .fs-4 } -### Cant wait? [Dive into our 5 minute Fluxtion hello world](overview/detailpages/helloworld_imperative.html) +# Combining dependency injection and event processing -## What is Fluxtion +The introduction of dependency injection gave developers a consistent approach to linking application components. +Fluxtion extends dependency injection to support container managed event driven beans. Extending a familiar development +pattern has the following benefits: +- Shallow learning curve for developers to use Fluxtion effectively +- Consistent programming model for event driven logic increases developer productivity +- Re-use of industrial quality and predictable event dispatch model -Fluxtion is a library that employs incremental computation and data flow programming for realtime data. Basically -Fluxtion is like a spreadsheet on steroids for realtime processing. When a cell or node changes the whole Fluxtion graph -is re-evaluated only recalculating nodes that are updated. +{: .info } +Fluxtion's familiar dependency injection programming model simplifies integration +{: .fs-4 } -Incremental computing is an approach that aims to minimize the amount of work required to compute a result by reusing as -much of the previous computation as possible. This is achieved by computing only the parts of the result that have -changed since the last time the computation was performed. This approach is often used in interactive systems, such as -spreadsheets, where computations need to be performed quickly and in response to user input. +## Dependency injection container -Data flow programming, on the other hand, is an approach that focuses on the flow of data through a system. In a data -flow system, data is represented as streams of values that flow through a network of processing nodes. Each node -performs a specific operation on the data, and the results are passed on to the next node in the network. This approach -is often used in systems that process large amounts of data, such as signal processing and data analysis. +Fluxtion builds a dependency injection container from configuration information given by the programmer. Functions +supported by the container include: creating instances, injecting references between beans, setting properties, calling +lifecycle methods, factory methods, singleton injection, named references, constructor and setter injection. +Configuration data can be programmatic, spring xml config, yaml or custom data format. -A combined approach of incremental computation and data flow programming can offer several benefits over using either -approach alone. This combined approach can be particularly useful in systems that process large amounts of data in -real-time. +There are three options for building a container: -In this approach, the data is represented as streams of values that flow through a network of processing nodes, as in -data flow programming. However, each node performs incremental computation, meaning it only processes the parts of the -stream that have changed since the last time the computation was performed. +- Interpreted - built and run in process, uses dynamic dispatch can handle millions of nodes +- Compiled - static analysis, code generated and compiled in process. handles thousands of nodes +- Compiled AOT - code generated at build time, zero cost start time when deployed -This approach can help reduce the computational cost of processing large amounts of data by avoiding redundant -computations. Instead of recomputing the entire data stream every time a new input arrives, the system only computes the -parts of the stream that have changed. +Fluxtion DI containers are very lightweight and designed to be run within an application. Multiple containers can be +used within a single application each container providing specialised business processing logic. -Additionally, this approach can improve the responsiveness of the system by processing the data in real-time as it -arrives, rather than waiting for the entire data stream to be processed before producing an output. +## Automatic event dispatch -Overall, a combined approach of incremental computation and data flow programming can provide a powerful and efficient -solution for processing large amounts of data in real-time, making it suitable for a wide range of applications, -including real-time data analysis, streaming video and audio processing, and more. +The container exposes event consumer end-points, routing events as methods calls to beans within the container +via an internal dispatcher. The internal dispatcher propagates event notification through the object graph. -## Fluxtion dependencies +Fluxtion leverages the familiar dependency injection workflow for constructing the object graph. Annotated +event handler and trigger methods are dispatch targets. When building a container Fluxtion uses the annotations to +calculate the dispatch call trees for the internal dispatcher. A bean can export multiple service interfaces or just a +single method. For exported interfaces the container generates proxies that routes calls from the proxy handler methods +to the container's dispatcher. + +# Latest release + +| component | maven central | +|-----------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------| +| Runtime | [![Fluxtion runtime](https://maven-badges.herokuapp.com/maven-central/com.fluxtion/runtime/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.fluxtion/runtime) | +| Compiler | [![Fluxtion compiler](https://maven-badges.herokuapp.com/maven-central/com.fluxtion/compiler/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.fluxtion/compiler) | + +## Build dependencies
    @@ -96,85 +141,6 @@ implementation 'com.fluxtion:compiler:{{site.fluxtion_version}}'
    -Fluxtion dependency description - -| Fluxtion dependency | Example use | Description | 3rd party
    dependencies | -|---------------------|-----------------------------------------|-------------------------------------------------------|-----------------------------| -| Compiler | Fluxtion#interpret
    Fluxtion#compile | Generates the EventProcessor
    from a description | Many | -| Runtime | EventProcessor#onEvent | Runtime dispatch of events and helper libraries | None | - -It is possible to use ```Fluxtion#compile``` to create an EventProcessor ahead of time and then only the runtime -library is required on the running classpath to support the source code generated EventProcessor. In this case -set the scope to provided in maven. - -## Key terms - -| Term | Description | -|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| -| Event | An event is any valid java instance that is submitted to the event processor | -| Stream | A stream is a set of events | -| EventProcessor | Processes the event stream with user defined logic. An EventProcessor instance is generated by Fluxtion | -| Node | A pojo instance that is node within an EventProcessor | -| Event propagation | Invoking methods in a topological order on nodes within an EventProcessor | -| Event handler method | A method on a node that is an entry point for processing an external event | -| Trigger method | A method on a node that is triggered once all parent nodes have completed their trigger methods | -| Event notification | Trigger or event handler methods notify a change by returning a boolean flag to control event propagation | -| Graph space | Construction of the meta model occurs in graph space, before generating the EventProcessor and after the user has provided all node information | - -## Steps to create an EventProcessor instance - -1. **Describe** the processing logic in user code -2. **Generate** the EventProcessor by supplying a description to the Fluxtion eventProcessorGenerator -3. **Process** events in the Fluxtion generated EventProcessor instance from (2) - -# Processing events in a stream processor - -There are three main steps to building and running a stream processor application using Fluxtion - -## Step 1: Describe processing logic - -Describe the values that are calculated and actions invoked in response to an incoming event. Fluxtion provides two -api's to describe the processing logic: - -1. [A set of annotations]({{site.fluxtion_src_runtime}}/annotations) - that mark members of user written classes as being managed by the event processor -2. [A java 8 stream like api]({{site.fluxtion_src_compiler}}/builder/stream) - , that can describe processing with a fluent functional style - -## Step 2: Build an EventProcessor - -Fluxtion provides a eventProcessorGenerator that converts the description into an executable -[EventProcessor]({{site.fluxtion_src_runtime}}/EventProcessor.java) -instance. The eventProcessorGenerator -is invoked from -[Fluxtion]({{site.fluxtion_src_compiler}}/Fluxtion.java) -with one of two utility methods: - -1. **compile**: this generates a java source code version of the EventProcessor. The file is compiled in process and - used - to handle events. Total nodes are limited to the number of elements a source file can handle -2. **interpret**: Creates an in memory model of the processing backed with data structures. Can support millions of - nodes - -## Step 3: Process events - -Once the -[EventProcessor]({{site.fluxtion_src_runtime}}/EventProcessor.java) -has been generated the instance is ready to consume events. The EventProcessor has a lifecycle so **init must be called -before sending any events for processing**. - -The application pulls events from any source and invokes ```EventProcessor#onEvent``` - -# Application integration ---- - -![](images/integration-overview.png) - -A Fluxtion event processor embeds within a user application, processing events, -publishing events to sinks or interacting with user classes. Events are feed from -the application directly into the processor or into a pipeline. A pipeline provides -additional capabilities such as threading, scheduling, auditing, access control. - \ No newline at end of file diff --git a/docs/overview/detailpages/helloworld_imperative.md b/docs/overview/detailpages/helloworld_imperative.md deleted file mode 100644 index 294471d46..000000000 --- a/docs/overview/detailpages/helloworld_imperative.md +++ /dev/null @@ -1,190 +0,0 @@ ---- -title: Helloworld imperative -parent: Overview -has_children: false -nav_order: 1 -published: true -example_src: https://github.com/v12technology/fluxtion-examples/tree/main/imperative-helloworld/src/main/java/com/fluxtion/example/imperative/helloworld ---- - -# 5 minute hello world - imperative - -Fluxtion hello world stream example. Add two numbers from different event streams and log when the sum > 100. -The sum is the addition of the current value from each event stream. - -This example creates an event processor, initialises it and fires data events at the processor. If a breach occurs -a warning will be logged to console. - -Code is available as a [maven project]({{page.example_src}}) - -## Steps to build an EventProcessor -All projects that build a Fluxtion [EventProcessor]({{site.EventProcessor_link}}) at runtime follow similar steps -- Create a maven or gradle project adding the Fluxtion compiler dependency to the project runtime classpath -- Write pojo's that will be nodes in the graph -- [Annotate]({{site.fluxtion_src_runtime}}/annotations/) a method to indicate it is an event handling callback -- Create a collection of instances of the pojo's that will act as nodes in the EvenProcessor -- Set references between the pojos as per normal java. Constructor, getter/setter, public access etc. -- Use one of the [Fluxtion]({{site.Fluxtion_link}}) compile/interpret methods passing in a -builder method that accepts [EventProcessorConfig]({{site.fluxtion_src_compiler}}/EventProcessorConfig.java) -- Add your the root node/s of your object instance graph using EventProcessorConfig.addNode in your builder method -- An EventProcessor instance is returned ready to be used -- Call EventProcessor.init() to ensure the graph is ready to process events -- To publish events to the processor call EventProcessor.onEvent(object) -- Fluxtion guarantees the dispatch of notifications to your pojo's is in topological order -- When you process ends you can optionally call EventProcessor.tearDown() - -## Processing graph - -Graphical representation of the processing graph that Fluxtion will generate. - -![](../../images/helloworld/helloworld_imperative.png) - -## Dependencies - -
    - - -
    -
    -
    -{% highlight xml %} - - - com.fluxtion - compiler - {{site.fluxtion_version}} - - -{% endhighlight %} -
    -
    -
    -
    -{% highlight groovy %} -implementation 'com.fluxtion:compiler:{{site.fluxtion_version}}' -{% endhighlight %} -
    -
    - - -## Java code - -All the elements are joined together using an imperative style in the Fluxtion builder. There are two style of class in -the example, pojo nodes that hold processing logic and events that notify the EventProcessor of a change. - -The example [Main method]({{page.example_src}}/Main.java) constructs an EvenProcessor, initialises it and fires events -to the processor for processing - -### Pojo classes - -| Name | Event handler | Description | -|-------------------|---------------|------------------------------------------------------------------| -| Data1Handler | yes | Handles incoming events of type InputDataEvent_1 | -| Data2Handler | yes | Handles incoming events of type InputDataEvent_2 | -| DataSumCalculator | no | References DataHandler nodes and calcultes the current sum | -| BreachNotifier | no | References the DataSumCalculator and logs a warning if sum > 100 | - -#### [Data1Handler]({{page.example_src}}/Data1handler.java) -An entry point for processing events of type InputDataEvent_1 and stores the latest value as a member variable. -Annotate the event handler method as follows: - -{% highlight java %} -@OnEventHandler -public boolean data1Update(InputDataEvent_1 data1) { - value = data1.value(); - return true; -} -{% endhighlight %} - -#### [Data2Handler]({{page.example_src}}/Data2handler.java) -An entry point for processing events of type InputDataEvent_2 and stores the latest value as a member variable. -Annotate the event handler method as follows: - -{% highlight java %} -@OnEventHandler -public boolean data1Update(InputDataEvent_1 data1) { - value = data1.value(); - return true; -} -{% endhighlight %} - -#### [DataSumCalculator]({{page.example_src}}/DataSumCalculator.java) -Calculates the current sum adding the values of Data1Handler and Data2Handler. Will be triggered when either handler -has its updated method invoked. Annotate the trigger method as follows: - -{% highlight java %} -@OnTrigger -public boolean calculate() { - sum = data1handler.getValue() + data2handler.getValue(); - System.out.println("sum:" + sum); - return sum > 100; -} -{% endhighlight %} - -The return flag indicates that the event notification should be propagated and any child nodes trigger methods -should be invoked. - -#### [BreachNotifier]({{page.example_src}}/BreachNotifier.java) -Logs to console when the sum breaches a value, BreachNotifier holds a reference to the DataSumCalculator instance. -The trigger method is only invoked if the DataSumCalculator propagates the notification, by returning true from its trigger -method. Annotate the trigger method as follows: - -{% highlight java %} -@OnTrigger -public void printWarning() { - System.out.println("WARNING DataSumCalculator value is greater than 100 sum = " + dataAddition.getSum()); -} -{% endhighlight %} - -### Event classes -Java records as used to implement events, pojos are suitable to use as events in the Fluxtion. - -- [InputDataEvent_1]({{page.example_src}}/InputDataEvent_1.java) -- [InputDataEvent_2]({{page.example_src}}/InputDataEvent_2.java) - -### Building the EventProcessor - -See [Main]({{page.example_src}}/Main.java) - -Building the EventProcessor is simply giving the root node instance, BreachNotifier to a [Fluxtion]({{site.Fluxtion_link}}) builder method. The Fluxtion.interpret() -method provides a [EventProcessorConfig]({{site.fluxtion_src_compiler}}/EventProcessorConfig.java) that the client adds -root nodes to as follows: - -{% highlight java %} -var eventProcessor = Fluxtion.interpret(cfg -> cfg.addNode(new BreachNotifier())); -{% endhighlight %} - -Fluxtion inspects all the references from the root node(s) and constructs the EventProcessor with instances of Data1Handler, -Data2Handler and DataSumCalculator all included. - -### Publishing events -Publishing events is simply a case of calling init on the EventProcessor and then call onEvent() with instances of InputDataEvent_1 -or InputDataEvent_2. The code for building and sending events follows: - -{% highlight java %} -public class Main { - public static void main(String[] args) { - var eventProcessor = Fluxtion.interpret(cfg -> cfg.addNode(new BreachNotifier())); - eventProcessor.init(); - eventProcessor.onEvent(new InputDataEvent_1(34.4)); - eventProcessor.onEvent(new InputDataEvent_2(52.1)); - eventProcessor.onEvent(new InputDataEvent_1(105));//should create a breach warning - eventProcessor.onEvent(new InputDataEvent_1(12.4)); - } -} -{% endhighlight %} - -## Example execution output - -{% highlight console %} -sum:34.4 -sum:86.5 -sum:157.1 -WARNING DataSumCalculator value is greater than 100 sum = 157.1 -sum:64.5 -{% endhighlight %} - - - \ No newline at end of file diff --git a/docs/sections.md b/docs/sections.md deleted file mode 100644 index 67065aaf7..000000000 --- a/docs/sections.md +++ /dev/null @@ -1,16 +0,0 @@ ---- -title: Sections proposal -has_children: false -nav_order: 2000 -published: true ---- - - -## Proposed sections -1. **Introduction** -2. **Process events** -3. **Generate stream processor** -4. **Describe processing logic** -5. **Testing** -6. **Monitoring/audit** -7. **Examples** \ No newline at end of file diff --git a/docs/sections/core-technology.md b/docs/sections/core-technology.md new file mode 100644 index 000000000..ba7d03d81 --- /dev/null +++ b/docs/sections/core-technology.md @@ -0,0 +1,160 @@ +--- +title: Core technology +has_children: true +nav_order: 3 +published: true +--- + +# Introduction + +Fluxtion is a DI container that combines dependency injection with event dispatch for realtime data processing. +The Fluxtion container can be thought of as a spreadsheet on steroids, each bean is like a formula cell, if any input +to the formula changes the spreadsheet forces a recalculation. As the container receives an event Fluxtion evaluates +which beans are connected to the handler and only triggers the connected beans for recalculation. + +Following the spreadsheet analogy, the programmer provides the formula cells and immediate dependencies. The spreadsheet +calculates the global set of dependencies and manages the recalculation of formulas when any cell is updated. Delegating +the mechanical but difficult task of calculating global dependencies to an algorithm allows us to build spreadsheets +that are complex but very predictable. Enabling non programmers to solve computational problems that would be out of +their reach without the support of the spreadsheet engine. + +Fluxtion brings this spreadsheet like paradigm and efficiency benefits to the realtime processing world. Methods on +beans are the formula cells, references between beans are the formula dependencies. The Fluxtion dependency analyser +uses the dependency information to calculate the global set of dependencies. When any bean is updated from an external +event dependent recalculation methods are called. + +With Fluxtion a single junior developer can build and maintain event driven logic that would have previously required a +team of specialist experienced developers +{: .info } + +## Key terms + +| Term | Description | +|----------------------|-------------------------------------------------------------------------------------------------------------------------------------------------| +| Event | An event is any valid java instance that is submitted to the event processor | +| Stream | A stream is a set of events | +| EventProcessor | Processes the event stream with user defined logic. An EventProcessor instance is generated by Fluxtion | +| DI container | interchangeable term for EventProcessor | +| Node | A pojo instance that is managed within an EventProcessor | +| Managed bean | interchangeable term for node | +| Event propagation | Invoking methods in a topological order on nodes within an EventProcessor | +| Event handler method | A method on a node that is an entry point for processing an external event | +| Trigger method | A method on a node that is triggered once all parent nodes have completed their trigger methods | +| Event notification | Trigger or event handler methods notify a change by returning a boolean flag to control event propagation | +| Graph space | Construction of the meta model occurs in graph space, before generating the EventProcessor and after the user has provided all node information | + +In formal terms Fluxtion can be classified as a combination of incremental computation and data flow programming. + +## Event sourcing + +If an event sourcing architectural style is followed the application behaviour will be completely reflected in the +test environment. Data driven clocks and audit logs tracing method call stacks are supported, when combined with event +replay this gives the developer a powerful and easy to use toolset for supporting a deployed system. + +## Fluxtion dependencies + +
    + + +
    +
    +
    +{% highlight xml %} + + + com.fluxtion + runtime + {{site.fluxtion_version}} + + + com.fluxtion + compiler + {{site.fluxtion_version}} + + +{% endhighlight %} +
    +
    +
    +
    +{% highlight groovy %} +implementation 'com.fluxtion:runtime:{{site.fluxtion_version}}' +implementation 'com.fluxtion:compiler:{{site.fluxtion_version}}' +{% endhighlight %} +
    +
    + +### Dependency description + +| Fluxtion dependency | Example use | Description | 3rd party
    dependencies | +|---------------------|-----------------------------------------|-------------------------------------------------------|-----------------------------| +| Compiler | Fluxtion#interpret
    Fluxtion#compile | Generates the EventProcessor
    from a description | Many | +| Runtime | EventProcessor#onEvent | Runtime dispatch of events and helper libraries | None | + +It is possible to use one of the [Fluxtion]({{site.fluxtion_src_compiler}}/Fluxtion.java) compileAOT methods to generate an +EventProcessor ahead of time. An aot generated event processor only requires the runtime library on the classpath. In +this case set the scope of the compiler dependency to provided in maven. + + +# Event dispatch +Notification connections between beans are calculated at construction time using the same data that is used to add beans +to the DI container and annotations that mark recalculation methods. + +When the proxy event handler method is called on the container it dispatches with the following with logic: + +- Any top level event handler is invoked with the arguments provided +- The event handler method indicates whether child instances should be notified with a Boolean return type +- Any child reference defined in the DI structure is conditionally notified of the parent event handler completing + processing +- A child instance can only be notified if all of its parents have finished processing their notifications +- The trigger method of the child returns a Boolean indicating whether the event notification should propagate +- The container recursively works through the child references and trigger methods in the container +- Dispatch callbacks are in strict topological order, with the event handler the root of the call tree +- Each instance is guaranteed to be invoked at maximum once per event processing cycle +- Any instances not connected to an executing root event handler will not be triggered in the cycle +- Connections can be either direct or through a reference chain + +# Processing events in a stream processor + +There are three main steps to building and running a stream processor application using Fluxtion + +## Step 1: Describe processing logic + +Describe the values that are calculated and actions invoked in response to an incoming event. Fluxtion provides two +api's to describe the processing logic: + +1. [A set of annotations]({{site.fluxtion_src_runtime}}/annotations) + that mark members of user written classes as being managed by the event processor +2. [A java 8 stream like api]({{site.fluxtion_src_compiler}}/builder/stream) + , that can describe processing with a fluent functional style + +## Step 2: Build the event processor container + +Fluxtion provides a eventProcessorGenerator that converts the description into an executable +[EventProcessor]({{site.fluxtion_src_runtime}}/EventProcessor.java) +instance. The eventProcessorGenerator +is invoked from +[Fluxtion]({{site.fluxtion_src_compiler}}/Fluxtion.java) +with one of two utility methods: + +1. **compile**: this generates a java source code version of the EventProcessor. The file is compiled in process and + used + to handle events. Total nodes are limited to the number of elements a source file can handle +2. **interpret**: Creates an in memory model of the processing backed with data structures. Can support millions of + nodes + +## Step 3: Process events + +Once the +[EventProcessor]({{site.fluxtion_src_runtime}}/EventProcessor.java) +has been generated the instance is ready to consume events. The EventProcessor has a lifecycle so **init must be called +before sending any events for processing**. + +The application pulls events from any source and invokes ```EventProcessor#onEvent``` + +![](../../images/integration-overview.png) + + + \ No newline at end of file diff --git a/docs/sections/deploying-testing.md b/docs/sections/deploying-testing.md new file mode 100644 index 000000000..d48c0311c --- /dev/null +++ b/docs/sections/deploying-testing.md @@ -0,0 +1,10 @@ +--- +title: Deploying and testing +has_children: true +nav_order: 4 +published: true +--- + +# Introduction + +Hello \ No newline at end of file diff --git a/docs/sections/examples.md b/docs/sections/examples.md new file mode 100644 index 000000000..c989025e8 --- /dev/null +++ b/docs/sections/examples.md @@ -0,0 +1,11 @@ +--- +title: Examples +has_children: true +nav_order: 6 +published: true +--- + +# Introduction + +A set of examples that explore the usage of Fluxtion in a variety of scenarios. All examples are on github as a single +project, cloning the repo will help the reader explore the code locally and improve the learning experience. \ No newline at end of file diff --git a/docs/sections/examples/spring_integration.md b/docs/sections/examples/spring_integration.md new file mode 100644 index 000000000..fbc711f4c --- /dev/null +++ b/docs/sections/examples/spring_integration.md @@ -0,0 +1,363 @@ +--- +title: Spring integration +parent: Examples +#grand_parent: Old stuff +has_children: false +nav_order: 1 +published: true +example_src: https://github.com/v12technology/fluxtion-examples/tree/main/cookbook/src/main/java/com/fluxtion/example/cookbook/spring +resources_src: https://github.com/v12technology/fluxtion-examples/tree/main/cookbook/src/main/resources/com/fluxtion/example/cookbook/spring +--- + +## Introduction + +Fluxtion is a dependency injection container specialised for event driven application deployments. The container +exposes event consumer end-points, routing events as methods calls to beans within the running container. A bean +registers a method as an event-handler by using Fluxtion annotations. Any beans referencing an event-handler bean will +be triggered by the container as the internal dispatcher propagates an event notification through the object graph. + +All methods on an interface can be exported by annotating the interface in an implementing bean, the container exports +the interface methods as a single service. A client can look up an exported service by interface type using the +container apis. All method calls on the service proxy are routed through the container's internal dispatcher. + +Spring is a popular DI container in the java world, this tutorial demonstrates how the construction logic of spring can +be combined with the dispatching logic of Fluxtion to simplify building event driven applications. The goal is to allow +the developer to concentrate on developing application logic while the container automatically builds the object graph +and constructs event dispatch logic. + +This example builds a small banking application, that supports credit, debit, account query, credit checking, +opening hours and persistence functions. The methods are grouped into service interfaces that are exposed by the +container. + +The steps to combine spring and fluxtion: + +- Create service interfaces that define the api of the banking app +- Create implementing classes for the service interfaces +- Create a spring config file declaring instances the DI container will manage +- Use Fluxtion annotations to export services and define event notification methods +- Pass the spring config file to the Fluxtion compiler and generate the DI container AOT +- Create an instance of the DI container and locate the service interfaces +- Use the service interfaces in the sample application + +## Application structure + +[See the example on GitHUb]({{page.example_src}}), top level package is `com.fluxtion.example.cookbook.spring.service`. + +Package structure: + +- **top level**: Banking app and a sample main +- **service**: interfaces the sample main and banking app invoke +- **node**: implementations of the service interfaces +- **data**: data types used by services +- **generation**: location of the Fluxtion ahead of time generated DI container + +## Spring beans +Fluxtion provides support for building the DI container using spring configuration. The example uses [a spring +configuration]({{page.resources_src}}/spring-account.xml) file to declare the beans that will be managed by the Fluxtion DI container: + +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + +Once the file is created the file location can be passed to Fluxtion to build the container: + +{% highlight java %} +ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("com/fluxtion/example/cookbook/spring/spring-account.xml"); +eventProcessor = FluxtionSpring.interpret(context); +{% endhighlight %} + +## Invoking a service + +The BankingApp instance creates an instance of the AOT generated Fluxtion DI container and provides access to container +exported services. The service reference the client code receives is a proxy the DI container creates, the +proxy handler routes method calls to instances managed by the container. + +Services the DI container exposes are event driven, they are designed to be invoked asynchronously and do not return +application values to client code. A service method can optionally return a boolean value that is used by the container +as an event propagation flag. If the flag is true then child references are notified the parent has changed due to an +external event. Child instances are notified of event propagation by the container calling a trigger method. A trigger +method is any zero argument method marked with an `OnTrigger` annotation. `OnTrigger` methods return an event +propagation flag to control event notification dispatch in the same was as exported service methods. + +The fluxtion DI container manages all the proxy creation, event dispatch to services, monitoring dirty flags and +propagating event notifications to child references. To access an exported service client code calls: + +{% highlight java %} +T exportedService = eventProcessor.getExportedService(); +{% endhighlight %} + +### Main method execution + +The main method creates an instance of the BankingApp, rerieves service interfaces and invokes application methods on +the interfaces. It is expected the BankingApp would be instantiated and used within a larger application that marshalls +client requests from the network and then invokes the BankingApp appropiately. + +{% highlight java %} +public class Main { + + public static void main(String[] args) { + BankingApp bankingApp = new BankingApp(GenerationStrategy.USE_AOT); + //get services + Account accountService = bankingApp.getBankAccount(); + BankingOperations bankControllerService = bankingApp.getBankingOperations(); + CreditCheck creditCheckService = bankingApp.getCreditCheck(); + //persistence + FileDataStore fileDataStore = new FileDataStore(Paths.get("data/spring/bank")); + bankControllerService.setDataStore(fileDataStore); + //replay state + fileDataStore.replay(bankingApp.getEventConsumer()); + + bankingApp.start(); + //should reject unknown account + accountService.deposit(999, 250.12); + + //get opening balance for acc 100 + accountService.publishBalance(100); + + //should reject bank closed + accountService.openAccount(100); + accountService.deposit(100, 250.12); + + //open bank + bankControllerService.openForBusiness(); + accountService.deposit(100, 250.12); + + //blacklist an account + creditCheckService.blackListAccount(100); + accountService.deposit(100, 46.90); + + //remove account from blacklist + creditCheckService.whiteListAccount(100); + accountService.deposit(100, 46.90); + + //close bank + bankControllerService.closedForBusiness(); + accountService.deposit(100, 13); + } + +} +{% endhighlight %} + +running the main method prints the following to the console: + +{% highlight text %} +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - deposit request:Transaction[accountNumber=999, amount=250.12, debit=false] +[INFO] AccountNode - reject unknown account:999 +[INFO] ResponsePublisher - response reject:Transaction[accountNumber=999, amount=250.12, debit=false] +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[INFO] AccountNode - ------------------------------------------------------ +[INFO] ResponsePublisher - account:100, balance:3267.22 +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - opened account:100 +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - deposit request:Transaction[accountNumber=100, amount=250.12, debit=false] +[INFO] CreditCheckNode - credit check passed +[WARN] CentralTransactionProcessor - reject bank closed +[INFO] ResponsePublisher - response reject:Transaction[accountNumber=100, amount=250.12, debit=false] +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[INFO] CentralTransactionProcessor - open accepting transactions +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - deposit request:Transaction[accountNumber=100, amount=250.12, debit=false] +[INFO] CreditCheckNode - credit check passed +[INFO] CentralTransactionProcessor - accept bank open +[INFO] AccountNode - updated balance:3517.3399999999997 account:100 +[INFO] ResponsePublisher - response accept:Transaction[accountNumber=100, amount=250.12, debit=false] +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[INFO] CreditCheckNode - credit check blacklisted:100 +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - deposit request:Transaction[accountNumber=100, amount=46.9, debit=false] +[WARN] CreditCheckNode - credit check failed +[INFO] ResponsePublisher - response reject:Transaction[accountNumber=100, amount=46.9, debit=false] +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[INFO] CreditCheckNode - credit check whitelisted:100 +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - deposit request:Transaction[accountNumber=100, amount=46.9, debit=false] +[INFO] CreditCheckNode - credit check passed +[INFO] CentralTransactionProcessor - accept bank open +[INFO] AccountNode - updated balance:3564.24 account:100 +[INFO] ResponsePublisher - response accept:Transaction[accountNumber=100, amount=46.9, debit=false] +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[WARN] CentralTransactionProcessor - closed rejecting all transactions +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - deposit request:Transaction[accountNumber=100, amount=13.0, debit=false] +[INFO] CreditCheckNode - credit check passed +[WARN] CentralTransactionProcessor - reject bank closed +[INFO] ResponsePublisher - response reject:Transaction[accountNumber=100, amount=13.0, debit=false] +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + + +Process finished with exit code 0 +{% endhighlight %} + +## Exporting a service + +To export a service the following steps are required: + +- Create an interface and then implement the interface with a concrete class +- The implementation class must extend ```ExportFunctionNode``` +- Mark the interface to export with ```@ExportService``` annotation + +For example to export the CreditCheck service: + +### CreditCheck interface + +{% highlight java %} +public interface CreditCheck { + void blackListAccount(int accountNumber); + void whiteListAccount(int accountNumber); +} + +{% endhighlight %} + +### CreditCheckNode concrete class + +The CreditCheckNode implements two interfaces CreditCheck and TransactionProcessor. Only the CreditCheck interface +methods are exported as this is only interface marked with ```@ExportService``` + +{% highlight java %} +public class CreditCheckNode extends ExportFunctionNode implements @ExportService CreditCheck, TransactionProcessor { + + private transient Set blackListedAccounts = new HashSet<>(); + private TransactionProcessor transactionSource; + private ResponsePublisher responsePublisher; + + @Override + @NoPropagateFunction + public void blackListAccount(int accountNumber) { + log.[INFO]("credit check blacklisted:{}", accountNumber); + blackListedAccounts.add(accountNumber); + } + + @Override + @NoPropagateFunction + public void whiteListAccount(int accountNumber) { + log.[INFO]("credit check whitelisted:{}", accountNumber); + blackListedAccounts.remove(accountNumber); + } + + public boolean propagateParentNotification(){ + Transaction transaction = transactionSource.currentTransactionRequest(); + int accountNumber = transaction.accountNumber(); + if(blackListedAccounts.contains(accountNumber)){ + log.[WARN]("credit check failed"); + transactionSource.rollbackTransaction(); + responsePublisher.rejectTransaction(transaction); + return false; + } + log.[INFO]("credit check passed"); + return true; + } + + @Override + public Transaction currentTransactionRequest() { + return transactionSource.currentTransactionRequest(); + } + + @Override + public void rollbackTransaction() { + transactionSource.rollbackTransaction(); + } + + @Override + public void commitTransaction(){ + transactionSource.commitTransaction(); + } + +} +{% endhighlight %} + +Notice the two CreditCheck methods are annotated with ```@NoPropagateFunction```, telling Fluxtion that no event +propagation will occur when either of these methods is invoked. The credit black list is a map and these methods should +only change the state of the internal map and not cause further processing to occur in the object graph. + +## Locating a service + +The steps required to locate a service and invoke methods on it are: + +- Build the DI container using one of the Fluxtion build methods +- To correctly intitialise the container call ```eventProcessor.init()``` on the DI instance +- To access the service call ```T service = eventProcessor.getExportedService()``` with the desired service type T + +### Accessing CreditCheck service + +The code below uses an enum to allow the user to select the DI generation strategy, in this example we are using the +AOT strategy. After eventprocessor generation the exported service are located and assigned to member variables in +the BankingApp class. + +```java +public class BankingApp { + + private final EventProcessor eventProcessor; + private final Account bankAccount; + private final CreditCheck creditCheck; + private final BankingOperations bankingOperations; + private final Consumer eventConsumer; + + @SneakyThrows + public BankingApp(GenerationStrategy generationStrategy) { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("com/fluxtion/example/cookbook/spring/spring-account.xml"); + eventProcessor = switch (generationStrategy) { + case USE_AOT -> new SpringBankEventProcessor(); + case INTERPRET -> FluxtionSpring.interpret(context); + case COMPILE -> FluxtionSpring.compile(context); + case GENERATE_AOT -> FluxtionSpring.compileAot(context, c -> { + c.setPackageName("com.fluxtion.example.cookbook.spring.generated"); + c.setClassName("SpringBankEventProcessor"); + }); + }; + eventProcessor.init(); + bankAccount = eventProcessor.getExportedService(); + creditCheck = eventProcessor.getExportedService(); + bankingOperations = eventProcessor.getExportedService(); + eventConsumer = eventProcessor::onEvent; + } + + public CreditCheck getCreditCheck() { + return creditCheck; + } +} +``` + +**line 22-24** acquire the exported services from the container by interface type + diff --git a/docs/sections/getting-started.md b/docs/sections/getting-started.md new file mode 100644 index 000000000..30ccb8936 --- /dev/null +++ b/docs/sections/getting-started.md @@ -0,0 +1,10 @@ +--- +title: Getting started +has_children: true +nav_order: 2 +published: true +--- + +# Introduction + +Start here \ No newline at end of file diff --git a/docs/sections/gettingstarted/first_tutorial.md b/docs/sections/gettingstarted/first_tutorial.md new file mode 100644 index 000000000..db54e3ee9 --- /dev/null +++ b/docs/sections/gettingstarted/first_tutorial.md @@ -0,0 +1,485 @@ +--- +title: First tutorial +parent: Getting started +has_children: false +nav_order: 1 +published: true +--- + +
    + + Table of contents + + {: .text-delta } +1. TOC +{:toc} +
    + + +# Introduction + +This tutorial is an introduction to writing event driven application logic using Fluxtion. The reader should be +proficient in Java, maven, git and possess a basic knowledge of Spring dependency injection. The project source can be +found [here.]({{site.cookbook_src}}/lottery) + +Our goal is to build the logic for a simple lottery application that will be connected to request and response queues. +Serialising requests to a queue makes our application event driven and easier to scale in the future, the response queue +stores the output from the application. This example is focused on building event driven processing by wiring together +software components using Fluxtion and not the connection to real queues. + +At the end of this tutorial you should understand how Fluxtion: + +- Exposes service interfaces for managed components +- Calls lifecycle methods on managed components +- Triggers event logic between dependent components +- Wires components together + +# The Lottery game + +A lottery game sells tickets to customers from a ticket shop, the shop is either opened or closed. A customer receives a +receipt for a purchased ticket or a message that no ticket was purchased. Tickets must have six numbers and cannot be +bought when the shop is closed. A lottery machine picks the winning ticket number from the tickets purchased and +publishes the lucky number to a queue. + +# Designing the components + +Our application will be event driven through a service interface api for the outside world to code against. We must first +think about the design of our services and then the concrete implementations. Once this design is complete we will use +Fluxtion to wire up the components. Fluxtion is low touch allowing engineers and architects to concentrate on design and +components with no distraction. + +## Service api + +From our business problem we have identified a concrete data type Ticket and two public services TicketStore and +LotteryMachine. Now we have identified the top level concepts we can create a service api that client code will use to +drive the system. + +{% highlight java %} +public record Ticket(int number, UUID id) { + public Ticket(int number){ + this(number, UUID.randomUUID()); + } +} + +public interface TicketStore { + boolean buyTicket(Ticket ticket); + void openStore(); + void closeStore(); + void setTicketSalesPublisher(Consumer ticketSalesPublisher); +} + +public interface LotteryMachine { + void selectWinningTicket(); + void setResultPublisher(Consumer resultPublisher); +} +{% endhighlight %} + +Our interfaces separate concerns logically making the api simple to work with. The methods +setTicketSalesPublisher and setResultPublisher connect the results of processing to output queues or a unit test. One +of our goals is to make the logic easy to test with the minimum of infrastructure. + +## Implementing logic + +We implement our two interfaces with concrete classes TicketStoreNode and LotteryGameNode using some lombok annotations +to remove boilerplate code. + +### TicketStoreNode + +The TicketStoreNode implements TicketSore and supports logic to buy and sell tickets depending on the state of the store +. A lifecycle method start is created that checks the ticketSalesPublisher has been set before progressing any further. +TicketStoreNode also implements Supplier<Ticket> which allows any child component to access the last sold ticket without +accessing the concrete type. Making components reference each other through interfaces is good practice. + +{% highlight java %} +@Slf4j +public class TicketStoreNode implements Supplier, TicketStore { + + private boolean storeOpen; + private Consumer ticketSalesPublisher; + private Ticket ticket; + + @Override + public void setTicketSalesPublisher(Consumer ticketSalesPublisher) { + this.ticketSalesPublisher = ticketSalesPublisher; + } + + public void start() { + Objects.requireNonNull(ticketSalesPublisher, "must have a ticketSalesPublisher set"); + storeOpen = false; + } + + @Override + public boolean buyTicket(Ticket ticket) { + if (ticket.number() < 9_99_99 | ticket.number() > 99_99_99) { + ticketSalesPublisher.accept("invalid numbers " + ticket); + this.ticket = null; + } else if (storeOpen) { + ticketSalesPublisher.accept("good luck with " + ticket); + this.ticket = ticket; + } else { + ticketSalesPublisher.accept("store shut - no tickets can be bought"); + this.ticket = null; + } + return this.ticket != null; + } + + @Override + public Ticket get() { + return ticket; + } + + @Override + public void openStore() { + log.info("store opened"); + storeOpen = true; + } + + @Override + public void closeStore() { + log.info("store closed"); + storeOpen = false; + } +} +{% endhighlight %} + +### LotteryGameNode + +The LotteryMachineNode implements LotteryMachine and supports logic to run the lottery. LotteryMachineNode holds a reference to +an instance of Supplier<Ticket> and whenever processNewTicketSale is called, acquires a purchased ticket and adds it +to the internal cache. A lifecycle method start is created that checks the resultPublisher has been set before +progressing any further. + +{% highlight java %} +@Slf4j +@RequiredArgsConstructor +public class LotteryMachineNode implements LotteryMachine { + + private final Supplier ticketSupplier; + private final transient List ticketsBought = new ArrayList<>(); + private Consumer resultPublisher; + + @Override + public void setResultPublisher(Consumer resultPublisher) { + this.resultPublisher = resultPublisher; + } + + public void start(){ + Objects.requireNonNull(resultPublisher, "must set a results publisher before starting the lottery game"); + log.info("started"); + } + + public boolean processNewTicketSale() { + ticketsBought.add(ticketSupplier.get()); + log.info("tickets sold:{}", ticketsBought.size()); + return false; + } + + @Override + public void selectWinningTicket() { + if(ticketsBought.isEmpty()){ + log.info("no tickets bought - no winning ticket"); + }else { + Collections.shuffle(ticketsBought); + log.info("WINNING ticket {}", ticketsBought.get(0)); + } + ticketsBought.clear(); + } +} +{% endhighlight %} + +The lifecycle methods and how clients access the TicketStore and LotteryMachine services are described below. + +# Building the application +Now we have our service interfaces designed and implemented we need to connect components together and make sure they provide +the functionality required in the expected manner. There are several problems to solve to deliver correct event driven +functionality: + +- How do clients access the components via service interfaces +- How are the lifecycle methods called +- How is LotteryGameNode#processNewTicketSale called only when a ticket is successfully purchased +- How are the components wired together + +Fluxtion solves these four problems for any event driven application. + +## Exporting services +We want clients to access components via service interface, this is simple to achieve by adding an **@ExportService** +annotation to the interface definitions on the concrete classes, as shown below. + +{% highlight java %} +import com.fluxtion.runtime.annotations.ExportService; +public class LotteryMachineNode implements @ExportService LotteryMachine { + //removed for clarity +} + +import com.fluxtion.runtime.annotations.ExportService; +public class TicketStoreNode implements Supplier, @ExportService TicketStore { + //removed for clarity +} +{% endhighlight %} + +Fluxtion will only export annotated interfaces at the container level, in this case Fluxtion will not export the +Supplier<Ticket> interface that TicketStoreNode implements. + +## Accessing exported services +Once the service interface has been marked for export client code can locate it through the EventProcessor instance that +holds the application components by calling EventProcessor#getExportedService. Client code invokes methods on the +interface and Fluxtion container will take care of all method routing. + +{% highlight java %} +public static void start(Consumer ticketReceiptHandler, Consumer resultsPublisher){ + EventProcessor lotteryEventProcessor = //removed for clarity + LotteryMachine lotteryMachine = lotteryEventProcessor.getExportedService(); + TicketStore ticketStore = lotteryEventProcessor.getExportedService(); + lotteryMachine.setResultPublisher(resultsPublisher); + ticketStore.setTicketSalesPublisher(ticketReceiptHandler); +} +{% endhighlight %} + + +## Event dispatch + +When a ticket has been successfully purchased the LotteryMachineNode instance method processNewTicketSale is invoked by +Fluxtion. The processNewTicketSale method grabs the last ticket sale from the Supplier<Ticket> reference and adds it to +the cache. Fluxtion knows to trigger a method if it is annotated with **@OnTrigger** and one of its dependencies has been +triggered from an incoming client service call. + + +{% highlight java %} +public class LotteryMachineNode implements LotteryMachine { + //code removed for clarity + + @OnTrigger + public boolean processNewTicketSale() { + ticketsBought.add(ticketSupplier.get()); + log.info("tickets sold:{}", ticketsBought.size()); + return false; + } +} +{% endhighlight %} + +How does Fluxtion know to invoke this method at the correct time? The container maps the dependency relationship between +TicketStoreNode and LotteryMachineNode, so when an exported service method is invoked on TicketStoreNode Fluxtion calls +the processNewTicketSale trigger method on LotteryMachineNode. This is great as it removes the need for the programmer +to manually call the event dispatch call graph. + +The next problem is we only want the processNewTicketSale method called when a ticket is successfully purchased. If we +try to add a ticket when the openStore is called a null pointer exception will be thrown at runtime. How can the +developer control the propagation of dependent trigger methods? + +Fluxtion manages exported service event propagation in two ways: + +- boolean return type from the service method, false indicates no event propagation, true propagates the notification +- annotate the method with **@NoPropagateFunction** annotation + +Both propagation controls are used in LotteryMachineNode ensuring the LotteryMachine is only triggered on successful +ticket purchases. The TicketStoreNode#buyTicket is the only method that will trigger an event notification to +LotteryMachineNode and only if the ticket passes basic validation and the store is open. + +{% highlight java %} +public class TicketStoreNode implements Supplier, @ExportService TicketStore { + //code removed for clarity + + @Override + @NoPropagateFunction + public void setTicketSalesPublisher(Consumer ticketSalesPublisher) {} + + public void start() {} + + //return true -> triggers event propagation + public boolean buyTicket(Ticket ticket) { + if (ticket.number() < 9_99_99 | ticket.number() > 99_99_99) { + ticketSalesPublisher.accept("invalid numbers " + ticket); + this.ticket = null; + } else if (storeOpen) { + ticketSalesPublisher.accept("good luck with " + ticket); + this.ticket = ticket; + } else { + ticketSalesPublisher.accept("store shut - no tickets can be bought"); + this.ticket = null; + } + return this.ticket != null; + } + + public Ticket get() {} + + @NoPropagateFunction + public void openStore() {} + + @NoPropagateFunction + public void closeStore() {} +} +{% endhighlight %} + + +## Lifecycle methods +Applications often benefit from lifecycle methods such as init, start and stop, allowing checks to be carried out before +executing the application. Fluxtion supports init, start and stop by annotating a method with an annotation **@Start @Stop** +or **@Initialise**. We use +the start method in our application to check output receivers ticketSalesPublisher and resultPublisher have been set +by the client code. + + +{% highlight java %} +public class TicketStoreNode implements Supplier, @ExportService TicketStore { + //code removed for clarity + @Start + public void start() { + Objects.requireNonNull(ticketSalesPublisher, "must have a ticketSalesPublisher set"); + storeOpen = false; + } +} + +public class LotteryMachineNode implements @ExportService LotteryMachine { + //code removed for clarity + @Start + public void start(){ + Objects.requireNonNull(resultPublisher, "must set a results publisher before starting the lottery game"); + log.info("started"); + } +} +{% endhighlight %} + +Client code invokes the lifecycle method on the container Fluxtion then calls all the lifecycle methods registered +by components in the right order. + +{% highlight java %} +public static void start(Consumer ticketReceiptHandler, Consumer resultsPublisher){ + EventProcessor lotteryEventProcessor = //removed for clarity + lotteryEventProcessor.init(); + LotteryMachine lotteryMachine = lotteryEventProcessor.getExportedService(); + TicketStore ticketStore = lotteryEventProcessor.getExportedService(); + lotteryMachine.setResultPublisher(resultsPublisher); + ticketStore.setTicketSalesPublisher(ticketReceiptHandler); + lotteryEventProcessor.start(); +} +{% endhighlight %} + +## Wiring the components together +The dependency injection container wires components depending upon the configuration supplied. As Fluxtion natively supports +spring ApplicationContext we use a spring configuration file in this example to wire the TicketStore to the LotteryMachine. + +{% highlight xml %} + + + + + + + + +{% endhighlight %} + +Fluxtion provides a spring extension for building a container using static helper methods. The built container is free +of any spring dependencies, Fluxtion just reads the spring file to drive its own configuration. To build the container +the tutorial loads the spring file from the classpath: + +{% highlight java %} +public static void start(Consumer ticketReceiptHandler, Consumer resultsPublisher){ + EventProcessor lotteryEventProcessor = FluxtionSpring.interpret( + new ClassPathXmlApplicationContext("com/fluxtion/example/cookbook/lottery/spring-lottery.xml")); + //removed for clarity +} +{% endhighlight %} + +# Running the application +Running the application requires the following from client code: + +- Building the container using the spring config file +- Call lifecycle methods on the container +- Lookup container exported service interfaces and store the references for use in client code + +The fact the components are managed by a container is completely hidden from the client code, this +makes integrating Fluxtion into an existing system extremely simple as no new programming models need to be adopted. + +In our example the main method only interacts with the business logic via the service interfaces, in a real application +the methods would be invoked by taking commands from an incoming request queue. + +{% highlight java %} +public class LotteryApp { + + private static LotteryMachine lotteryMachine; + private static TicketStore ticketStore; + + public static void main(String[] args) { + start(LotteryApp::ticketReceipt, LotteryApp::lotteryResult); + //try and buy a ticket - store is closed + ticketStore.buyTicket(new Ticket(12_65_56)); + + //open store and buy ticket + ticketStore.openStore(); + ticketStore.buyTicket(new Ticket(12_65_56)); + ticketStore.buyTicket(new Ticket(36_58_58)); + ticketStore.buyTicket(new Ticket(73_00_12)); + + //bad numbers + ticketStore.buyTicket(new Ticket(25)); + + //close the store and run the lottery + ticketStore.closeStore(); + + //try and buy a ticket - store is closed + ticketStore.buyTicket(new Ticket(12_65_56)); + + //run the lottery + lotteryMachine.selectWinningTicket(); + } + + public static void start(Consumer ticketReceiptHandler, Consumer resultsPublisher){ + var lotteryEventProcessor = FluxtionSpring.interpret( + new ClassPathXmlApplicationContext("com/fluxtion/example/cookbook/lottery/spring-lottery.xml")); + lotteryEventProcessor.init(); + lotteryMachine = lotteryEventProcessor.getExportedService(); + ticketStore = lotteryEventProcessor.getExportedService(); + lotteryMachine.setResultPublisher(resultsPublisher); + ticketStore.setTicketSalesPublisher(ticketReceiptHandler); + lotteryEventProcessor.start(); + } + + public static void ticketReceipt(String receipt){ + log.info(receipt); + } + + public static void lotteryResult(String receipt){ + log.info(receipt); + } +} +{% endhighlight %} + +Executing our application produces the following output: + +{% highlight console %} +[main] INFO LotteryMachineNode - started +[main] INFO LotteryApp - store shut - no tickets can be bought +[main] INFO TicketStoreNode - store opened +[main] INFO LotteryApp - good luck with Ticket[number=126556, id=77376783-3513-4f22-88be-5ace6cdf5839] +[main] INFO LotteryMachineNode - tickets sold:1 +[main] INFO LotteryApp - good luck with Ticket[number=365858, id=05e6f44e-5938-4b28-a183-047c6e75c532] +[main] INFO LotteryMachineNode - tickets sold:2 +[main] INFO LotteryApp - good luck with Ticket[number=730012, id=30af94d7-7aec-4e82-8159-2cade3b38b2b] +[main] INFO LotteryMachineNode - tickets sold:3 +[main] INFO LotteryApp - invalid numbers Ticket[number=25, id=62afdb45-25f8-4a80-bfba-37c22bfe8bf2] +[main] INFO TicketStoreNode - store closed +[main] INFO LotteryApp - store shut - no tickets can be bought +[main] INFO LotteryMachineNode - WINNING ticket Ticket[number=365858, id=05e6f44e-5938-4b28-a183-047c6e75c532] + +Process finished with exit code 0 +{% endhighlight %} + +# Conclusion +We have quite a lot of ground in a seemingly simple event driven application. Hopefully you can see the benefits of using +Fluxtion to write event driven business logic: +- Forcing client code to interact with components through interfaces +- Formal lifecycle phases that are easy to plug in to +- Dispatch of events to any bean managed instance exporting a service +- Automatic dispatch of dependent trigger methods +- Removal of state and conditional dispatch logic from business code +- Deterministic event handling that is well tested +- Control of event propagation with simple boolean returns or annotations +- More time spent writing logic and less time writing infrastructure +- Simple programming model that leverages Spring for quick adoption + +As applications grow and become more complex programming event driven logic becomes ever more expensive. The benefits +of a tool like Fluxtion really shine during the growth and maintenance phase. + +I hope you have enjoyed reading this tutorial, and it has given you an understanding of Fluxtion and a desire to use it +in your applications. Please send me in any comments or suggestions to improve this tutorial \ No newline at end of file diff --git a/docs/sections/gettingstarted/helloworld_imperative.md b/docs/sections/gettingstarted/helloworld_imperative.md new file mode 100644 index 000000000..7f17fa3fe --- /dev/null +++ b/docs/sections/gettingstarted/helloworld_imperative.md @@ -0,0 +1,273 @@ +--- +title: Hello fluxtion world +parent: Getting started +has_children: false +nav_order: 2 +published: false +example_src: https://github.com/v12technology/fluxtion-examples/tree/main/imperative-helloworld/src/main/java/com/fluxtion/example/imperative/helloworld +--- + +# 5 minute hello world + +Fluxtion hello world stream example. Add two numbers from different event streams and log when the sum > 100. +The sum is the addition of the current value from each event stream. + +This example creates an event processor, initialises it and fires data events at the processor. If a breach occurs +a warning will be logged to console. + +Code is available as a [maven project]({{page.example_src}}) + +## Steps to build an EventProcessor + +All projects that build a Fluxtion [EventProcessor]({{site.EventProcessor_link}}) at runtime follow similar steps + +- Create a maven or gradle project adding the Fluxtion compiler dependency to the project runtime classpath +- Write pojo's that will be nodes in the graph, set references between the pojos as per normal java +- [Annotate]({{site.fluxtion_src_runtime}}/annotations/) a method to indicate it is an event handling callback or a trigger method +- Use one of the [Fluxtion]({{site.Fluxtion_link}}) compile/interpret methods passing in the list of nodes to the builder method +- An EventProcessor instance is returned ready to be used +- Call EventProcessor.init() to ensure the graph is ready to process events +- To publish events to the processor call EventProcessor.onEvent(object) + +## Processing graph + +Graphical representation of the processing graph that Fluxtion will generate. + +![](../../images/helloworld/helloworld_imperative.png) + +## Dependencies + +
    + + +
    +
    +
    +{% highlight xml %} + + + com.fluxtion + compiler + {{site.fluxtion_version}} + + +{% endhighlight %} +
    +
    +
    +
    +{% highlight groovy %} +implementation 'com.fluxtion:compiler:{{site.fluxtion_version}}' +{% endhighlight %} +
    +
    + +## Maven pom + +{% highlight java %} + + + + com.fluxtion.example + 1.0.0-SNAPSHOT + 4.0.0 + functional-helloworld + + + 17 + 17 + + + + + com.fluxtion + compiler + {{site.fluxtion_version}} + + + +{% endhighlight %} + +## Java code + +All the elements are joined together using an imperative style in the Fluxtion builder. There are two style of class in +the example, pojo nodes that hold processing logic and events that notify the EventProcessor of a change. + +The example [Main method]({{page.example_src}}/Main.java) constructs an EvenProcessor, initialises it and fires events +to the processor for processing + +### Pojo classes + +| Name | Event handler | Trigger handler | Description | +|-------------------|---------------|-----------------|------------------------------------------------------------------| +| Data1Handler | yes | no | Handles incoming events of type InputDataEvent_1 | +| Data2Handler | yes | no | Handles incoming events of type InputDataEvent_2 | +| DataSumCalculator | no | yes | References DataHandler nodes and calculates the current sum | +| BreachNotifier | no | yes | References the DataSumCalculator and logs a warning if sum > 100 | + +The event handler method is called when a matching event type is published to the container, the trigger handler is +called when a parent dependency haa been trigger or a parent event handler method has been called. + +#### [Data1Handler]({{page.example_src}}/Data1handler.java) + +An entry point for processing events of type InputDataEvent_1 and stores the latest value as a member variable. +Annotate the event handler method as follows: + +{% highlight java %} +public class Data1handler { + private double value; + + @OnEventHandler + public boolean data1Update(InputDataEvent_1 data1) { + value = data1.value(); + return true; + } + + public double getValue() { + return value; + } +} +{% endhighlight %} + +#### [Data2Handler]({{page.example_src}}/Data2handler.java) + +An entry point for processing events of type InputDataEvent_2 and stores the latest value as a member variable. +Annotate the event handler method as follows: + +{% highlight java %} +public class Data2handler { + private double value; + + @OnEventHandler + public boolean data1Update(InputDataEvent_2 data2) { + value = data2.value(); + return true; + } + + public double getValue() { + return value; + } +} +{% endhighlight %} + +#### [DataSumCalculator]({{page.example_src}}/DataSumCalculator.java) + +Calculates the current sum adding the values of Data1Handler and Data2Handler. Will be triggered when either handler +has its updated method invoked. Annotate the trigger method as follows: + +{% highlight java %} +public class DataSumCalculator { + private final Data1handler data1handler; + private final Data2handler data2handler; + private double sum; + + public DataSumCalculator(Data1handler data1handler, Data2handler data2handler) { + this.data1handler = data1handler; + this.data2handler = data2handler; + } + + public DataSumCalculator() { + this(new Data1handler(), new Data2handler()); + } + + /** + * The {@link OnTrigger} annotation marks this method to be called if any parents have changed + * + * @return flag indicating a change and a propagation of the event wave to child dependencies if the sum > 100 + */ + @OnTrigger + public boolean calculate() { + sum = data1handler.getValue() + data2handler.getValue(); + System.out.println("sum:" + sum); + return sum > 100; + } + + public double getSum() { + return sum; + } +} +{% endhighlight %} + +The return flag indicates that the event notification should be propagated and any child nodes trigger methods +should be invoked. + +#### [BreachNotifier]({{page.example_src}}/BreachNotifier.java) + +Logs to console when the sum breaches a value, BreachNotifier holds a reference to the DataSumCalculator instance. +The trigger method is only invoked if the DataSumCalculator propagates the notification, by returning true from its +trigger +method. Annotate the trigger method as follows: + +{% highlight java %} +public class BreachNotifier { + private final DataSumCalculator dataAddition; + + public BreachNotifier(DataSumCalculator dataAddition) { + this.dataAddition = dataAddition; + } + + public BreachNotifier() { + this(new DataSumCalculator()); + } + + @OnTrigger + public boolean printWarning() { + System.out.println("WARNING DataSumCalculator value is greater than 100 sum = " + dataAddition.getSum()); + return true; + } +} +{% endhighlight %} + +### Event classes + +Java records as used as events. + +{% highlight java %} +public record InputDataEvent_1(double value) {} +public record InputDataEvent_2(double value) {} +{% endhighlight %} + +## Building the EventProcessor and processing events + +See [Main]({{page.example_src}}/Main.java) + +Building the EventProcessor is simply giving the root node instance, BreachNotifier to +a [Fluxtion]({{site.Fluxtion_link}}) builder method. The Fluxtion.interpret() +method accepts a list of nodes to add to the container. Fluxtion inspects all the references from the root node(s) and +constructs the EventProcessor with instances of Data1Handler, +Data2Handler and DataSumCalculator all included. + +Publishing events to the container is simply a case of calling init on the EventProcessor and then call onEvent() with instances of +InputDataEvent_1 +or InputDataEvent_2. The code for building and sending events follows: +root nodes to as follows: + +{% highlight java %} +public class Main { + public static void main(String[] args) { + var eventProcessor = Fluxtion.interpret(new BreachNotifier()); + eventProcessor.init(); + eventProcessor.onEvent(new InputDataEvent_1(34.4)); + eventProcessor.onEvent(new InputDataEvent_2(52.1)); + eventProcessor.onEvent(new InputDataEvent_1(105));//should create a breach warning + eventProcessor.onEvent(new InputDataEvent_1(12.4)); + } +} +{% endhighlight %} + +### Example execution output + +{% highlight console %} +sum:34.4 +sum:86.5 +sum:157.1 +WARNING DataSumCalculator value is greater than 100 sum = 157.1 +sum:64.5 +{% endhighlight %} + + + \ No newline at end of file diff --git a/docs/cookbook-functional/cookbook.md b/docs/sections/old-stuff/cookbook-functional.md similarity index 92% rename from docs/cookbook-functional/cookbook.md rename to docs/sections/old-stuff/cookbook-functional.md index faae31f0b..c5181c253 100644 --- a/docs/cookbook-functional/cookbook.md +++ b/docs/sections/old-stuff/cookbook-functional.md @@ -1,5 +1,6 @@ --- title: Cookbook functional +parent: Old stuff has_children: true nav_order: 103 published: true diff --git a/docs/cookbook-functional/combining_imperative.md b/docs/sections/old-stuff/cookbook-functional/combining_imperative.md similarity index 99% rename from docs/cookbook-functional/combining_imperative.md rename to docs/sections/old-stuff/cookbook-functional/combining_imperative.md index 0cae0b222..8b1295026 100644 --- a/docs/cookbook-functional/combining_imperative.md +++ b/docs/sections/old-stuff/cookbook-functional/combining_imperative.md @@ -1,6 +1,7 @@ --- title: Combining imperative parent: Cookbook functional +grand_parent: Old stuff has_children: false nav_order: 3 published: true diff --git a/docs/cookbook-functional/dynamic_filtering.md b/docs/sections/old-stuff/cookbook-functional/dynamic_filtering.md similarity index 99% rename from docs/cookbook-functional/dynamic_filtering.md rename to docs/sections/old-stuff/cookbook-functional/dynamic_filtering.md index 126247b1b..b81c02560 100644 --- a/docs/cookbook-functional/dynamic_filtering.md +++ b/docs/sections/old-stuff/cookbook-functional/dynamic_filtering.md @@ -1,6 +1,7 @@ --- title: Dynamic filtering parent: Cookbook functional +grand_parent: Old stuff has_children: false nav_order: 2 published: true diff --git a/docs/overview/detailpages/helloworld_functional.md b/docs/sections/old-stuff/cookbook-functional/helloworld_functional.md similarity index 97% rename from docs/overview/detailpages/helloworld_functional.md rename to docs/sections/old-stuff/cookbook-functional/helloworld_functional.md index b62fae244..427b06fca 100644 --- a/docs/overview/detailpages/helloworld_functional.md +++ b/docs/sections/old-stuff/cookbook-functional/helloworld_functional.md @@ -1,8 +1,9 @@ --- title: Helloworld functional -parent: Overview +parent: Cookbook functional +grand_parent: Old stuff has_children: false -nav_order: 2 +nav_order: 1 published: true example_src: https://github.com/v12technology/fluxtion-examples/tree/main/functional-helloworld/src/main/java/com/fluxtion/example/functional/helloworld --- @@ -23,7 +24,7 @@ Code is available as a [maven project]({{page.example_src}}) Graphical representation of the processing graph that Fluxtion will generate. -![](../../images/helloworld_eventstream.png) +![](../../../images/helloworld_eventstream.png) ## Dependencies diff --git a/docs/cookbook-functional/subscriptiption_functional.md b/docs/sections/old-stuff/cookbook-functional/subscriptiption_functional.md similarity index 99% rename from docs/cookbook-functional/subscriptiption_functional.md rename to docs/sections/old-stuff/cookbook-functional/subscriptiption_functional.md index 2c3acd4a0..17ce2ef7d 100644 --- a/docs/cookbook-functional/subscriptiption_functional.md +++ b/docs/sections/old-stuff/cookbook-functional/subscriptiption_functional.md @@ -1,8 +1,9 @@ --- title: EventFeed integration parent: Cookbook functional +grand_parent: Old stuff has_children: false -nav_order: 1 +nav_order: 4 published: true example_src: https://github.com/v12technology/fluxtion-examples/tree/cook_subscription_example/cookbook/src/main/java/com/fluxtion/example/cookbook/subscription --- diff --git a/docs/cookbook/cookbook.md b/docs/sections/old-stuff/cookbook.md similarity index 90% rename from docs/cookbook/cookbook.md rename to docs/sections/old-stuff/cookbook.md index 203b8ceda..58466feeb 100644 --- a/docs/cookbook/cookbook.md +++ b/docs/sections/old-stuff/cookbook.md @@ -1,5 +1,6 @@ --- title: Cookbook +parent: Old stuff has_children: true nav_order: 102 published: true diff --git a/docs/cookbook/audit_log.md b/docs/sections/old-stuff/cookbook/audit_log.md similarity index 99% rename from docs/cookbook/audit_log.md rename to docs/sections/old-stuff/cookbook/audit_log.md index 0e2c22f54..8b01637e2 100644 --- a/docs/cookbook/audit_log.md +++ b/docs/sections/old-stuff/cookbook/audit_log.md @@ -2,6 +2,7 @@ title: Audit logging parent: Cookbook has_children: false +grand_parent: Old stuff nav_order: 4 published: true example_src: https://github.com/v12technology/fluxtion-examples/tree/main/cookbook/src/main/java/com/fluxtion/example/cookbook/auditlog @@ -275,7 +276,7 @@ Process finished with exit code 0 ### EventProcessor graph for the example -![](../images/audit/Processor.png) +![](../../../images/audit/Processor.png) diff --git a/docs/cookbook/injecting_context.md b/docs/sections/old-stuff/cookbook/injecting_context.md similarity index 98% rename from docs/cookbook/injecting_context.md rename to docs/sections/old-stuff/cookbook/injecting_context.md index deee04aaa..699d01e33 100644 --- a/docs/cookbook/injecting_context.md +++ b/docs/sections/old-stuff/cookbook/injecting_context.md @@ -1,8 +1,9 @@ --- title: Injecting user context parent: Cookbook +grand_parent: Old stuff has_children: false -nav_order: 1 +nav_order: 9 published: true --- diff --git a/docs/cookbook/injecting_runtime_instances.md b/docs/sections/old-stuff/cookbook/injecting_runtime_instances.md similarity index 99% rename from docs/cookbook/injecting_runtime_instances.md rename to docs/sections/old-stuff/cookbook/injecting_runtime_instances.md index a037dd453..a3c8ef9dd 100644 --- a/docs/cookbook/injecting_runtime_instances.md +++ b/docs/sections/old-stuff/cookbook/injecting_runtime_instances.md @@ -1,6 +1,7 @@ --- title: Injecting at runtime - InstanceSupplier parent: Cookbook +grand_parent: Old stuff has_children: false nav_order: 6 published: true diff --git a/docs/cookbook/injecting_runtime_instances_lookup.md b/docs/sections/old-stuff/cookbook/injecting_runtime_instances_lookup.md similarity index 99% rename from docs/cookbook/injecting_runtime_instances_lookup.md rename to docs/sections/old-stuff/cookbook/injecting_runtime_instances_lookup.md index 1e41af593..f5a76550f 100644 --- a/docs/cookbook/injecting_runtime_instances_lookup.md +++ b/docs/sections/old-stuff/cookbook/injecting_runtime_instances_lookup.md @@ -1,6 +1,7 @@ --- title: Injecting at runtime - instance Lookup parent: Cookbook +grand_parent: Old stuff has_children: false nav_order: 5 published: true diff --git a/docs/cookbook/integrate_lombok.md b/docs/sections/old-stuff/cookbook/integrate_lombok.md similarity index 98% rename from docs/cookbook/integrate_lombok.md rename to docs/sections/old-stuff/cookbook/integrate_lombok.md index e787513ff..72fd99314 100644 --- a/docs/cookbook/integrate_lombok.md +++ b/docs/sections/old-stuff/cookbook/integrate_lombok.md @@ -1,6 +1,7 @@ --- title: Integrating lombok parent: Cookbook +grand_parent: Old stuff has_children: false nav_order: 7 published: true diff --git a/docs/sections/old-stuff/cookbook/node_callbacks.md b/docs/sections/old-stuff/cookbook/node_callbacks.md new file mode 100644 index 000000000..4c99f2347 --- /dev/null +++ b/docs/sections/old-stuff/cookbook/node_callbacks.md @@ -0,0 +1,211 @@ +--- +title: Node callback pattern +parent: Cookbook +grand_parent: Old stuff +has_children: false +nav_order: 3 +published: true +example_src: https://github.com/v12technology/fluxtion-examples/tree/main/cookbook/src/main/java/com/fluxtion/example/cookbook/nodecallback +--- + +## Introduction + +This example demonstrates invoking instances in a graph by direct function calls and the instance method triggering a graph processing +cycle. Dependencies of the callback node are triggered as if the method was annotated with an EventHandler annotation. +The callback pattern allows targeting of a single instance in a graph. + + +[See the example here]({{page.example_src}}/Main.java) + + +Sometimes a user will want to target a graph node function directly to trigger a graph cycle. Using EventHandler +annotation on a method is analogous to listening to a topic in a message system, while the node call back pattern is +like having a queue with a single listener. + +## Steps to register a call back +In order for a node to available as a uniquely addressable call back node the following steps are required + +1. The node must inherit from [CallBackNode]({{site.fluxtion_src_runtime}}/callback/CallBackNode.java) +2. The node must call the super constructor with a unique name, this allows the node to found via its name +3. To trigger a graph cycle from a method call [CallBackNode#triggerGraphCycle]({{site.fluxtion_src_runtime}}/callback/CallBackNode.java#52) +4. Add the node as normal to Fluxtion, build the graph and call init() on the event processor + +## Accessing the node +Once the graph is built and init() is called the callback node can be invoked to trigger graph cycles. But first the node must +be discovered and referenced from outside the graph. There are a number of strategies to do this: + +1. Call ```EventProcessor.getNodeById('node_name');``` using the name supplied in the node constructor +2. Pass a mutable event into the graph the node is listening for. The node adds its reference to the event +3. Inject an instance to the node and the node registers itself with the injected instance at startup +4. Create an interface with default methods implementations using (1) above, make the EventProcessor implement the interface + +The example uses the first of these approaches, ```EventProcessor.getNodeById('node_name');``` + +## Example + +[See the example here]({{page.example_src}}/Main.java) + +The example tracks voting candidates and gathers the results into a collector for printing. The classes in the example are: +- [Main]({{page.example_src}}/Main.java) method, builds the graph, finds the callback nodes by name and invokes functions on the nodes directly +- [CandidateVoteHandler]({{page.example_src}}/CandidateVoteHandler.java) is a call back node that can trigger graph cycles +- [ElectionTracker]({{page.example_src}}/ElectionTracker.java) Aggregates CandidateVoteHandlers, annotated methods receive callbacks during a graph cycle and prints results. +- [VoteData]({{page.example_src}}/VoteData.java) Generic data record that holds voting information updates. + +Although each CandidateVoteHandler has the same method signature only the targeted callback instance is invoked during +execution. + +### CandidateVoteHandler +Extends CallBackNode and uses base methods to trigger graph cycles. + +{% highlight java %} +public class CandidateVoteHandler extends CallBackNode { + + private int updateId; + private String lastNewsStory = ""; + private int totalVotes; + + public CandidateVoteHandler(String name) { + super(name); + } + + public void newStory(VoteData argument, int updateId) { + this.updateId = updateId; + this.lastNewsStory = argument.value(); + super.triggerGraphCycle(); + } + + public void voteCountUpdate(VoteData argument, int updateId) { + this.updateId = updateId; + this.totalVotes += argument.value(); + super.triggerGraphCycle(); + } + + @Override + public String toString() { + return getName() + + ", totalVotes=" + totalVotes + + ", updateId=" + updateId + + ", lastNewsStory='" + lastNewsStory + '\'' + ; + } +} +{% endhighlight %} + + +### Main +Builds the graph, finds CandidateVoteHandler nodes by name and invokes methods on the instances directly to trigger +graph process cycles. + +{% highlight java %} +public class Main { + + public static void main(String[] args) throws NoSuchFieldException { + var voteProcessor = Fluxtion.interpret(c -> c.addNode(new ElectionTracker(List.of( + new CandidateVoteHandler("Red_party"), + new CandidateVoteHandler("Blue_party"), + new CandidateVoteHandler("Green_party") + )))); + voteProcessor.init(); + + //get the nodes by name from the graph + CandidateVoteHandler redPartyNode = voteProcessor.getNodeById("Red_party"); + CandidateVoteHandler bluePartyNode = voteProcessor.getNodeById("Blue_party"); + CandidateVoteHandler greenPartyNode = voteProcessor.getNodeById("Green_party"); + + //invoke functions directly on nodes - creates a + redPartyNode.voteCountUpdate(new VoteData<>(25), 1); + bluePartyNode.voteCountUpdate(new VoteData<>(12), 2); + bluePartyNode.voteCountUpdate(new VoteData<>(19), 3); + bluePartyNode.voteCountUpdate(new VoteData<>(50), 4); + redPartyNode.newStory(new VoteData<>("red alert!!"), 5); + greenPartyNode.newStory(new VoteData<>("green and gone :("), 6); + greenPartyNode.voteCountUpdate(new VoteData<>(2), 1); + } +} +{% endhighlight %} + + +### ElectionTracker +Aggregated CandidateVoteHandlers and annotated methods receive callbacks during a graph cycle + +{% highlight java %} +public record ElectionTracker(List candidateVoteHandlers) { + + @OnParentUpdate + public void updatedCandidateStatus(CandidateVoteHandler candidateVoteHandler) { + System.out.println("update for:" + candidateVoteHandler.getName()); + } + + @OnTrigger + public boolean printLatestResults() { + String result = candidateVoteHandlers.stream() + .map(Objects::toString) + .collect(Collectors.joining("\n\t", "\t", "\n\n")); + System.out.println(result); + return true; + } +} +{% endhighlight %} + + +### VoteData +Generic data holder used as function arguments + +{% highlight java %} +public record VoteData(T value) { } +{% endhighlight %} + + +## Execution output + + +Running the sample produces this output: + +{% highlight console %} +update for:Red_party +Red_party, totalVotes=25, updateId=1, lastNewsStory='' +Blue_party, totalVotes=0, updateId=0, lastNewsStory='' +Green_party, totalVotes=0, updateId=0, lastNewsStory='' + + +update for:Blue_party +Red_party, totalVotes=25, updateId=1, lastNewsStory='' +Blue_party, totalVotes=12, updateId=2, lastNewsStory='' +Green_party, totalVotes=0, updateId=0, lastNewsStory='' + + +update for:Blue_party +Red_party, totalVotes=25, updateId=1, lastNewsStory='' +Blue_party, totalVotes=31, updateId=3, lastNewsStory='' +Green_party, totalVotes=0, updateId=0, lastNewsStory='' + + +update for:Blue_party +Red_party, totalVotes=25, updateId=1, lastNewsStory='' +Blue_party, totalVotes=81, updateId=4, lastNewsStory='' +Green_party, totalVotes=0, updateId=0, lastNewsStory='' + + +update for:Red_party +Red_party, totalVotes=25, updateId=5, lastNewsStory='red alert!!' +Blue_party, totalVotes=81, updateId=4, lastNewsStory='' +Green_party, totalVotes=0, updateId=0, lastNewsStory='' + + +update for:Green_party +Red_party, totalVotes=25, updateId=5, lastNewsStory='red alert!!' +Blue_party, totalVotes=81, updateId=4, lastNewsStory='' +Green_party, totalVotes=0, updateId=6, lastNewsStory='green and gone :(' + + +update for:Green_party +Red_party, totalVotes=25, updateId=5, lastNewsStory='red alert!!' +Blue_party, totalVotes=81, updateId=4, lastNewsStory='' +Green_party, totalVotes=2, updateId=1, lastNewsStory='green and gone :(' +{% endhighlight %} + + + + + + diff --git a/docs/cookbook/parallel_processing.md b/docs/sections/old-stuff/cookbook/parallel_processing.md similarity index 99% rename from docs/cookbook/parallel_processing.md rename to docs/sections/old-stuff/cookbook/parallel_processing.md index aeb6e0dae..161b57c89 100644 --- a/docs/cookbook/parallel_processing.md +++ b/docs/sections/old-stuff/cookbook/parallel_processing.md @@ -1,8 +1,9 @@ --- title: Parallel processing parent: Cookbook +grand_parent: Old stuff has_children: false -nav_order: 7 +nav_order: 8 published: true example_src: https://github.com/v12technology/fluxtion-examples/tree/main/cookbook/src/main/java/com/fluxtion/example/cookbook/parallel --- @@ -127,7 +128,7 @@ public class Main { The execution graph image shows a set of tasks are triggered from the RequestHandler instance and their outputs are gathered in the TaskCollector instance. -![](../images/parallel/parallel.png) +![](../../../images/parallel/parallel.png) ## Running the example diff --git a/docs/sections/old-stuff/cookbook/spring_integration.md b/docs/sections/old-stuff/cookbook/spring_integration.md new file mode 100644 index 000000000..9c624cdee --- /dev/null +++ b/docs/sections/old-stuff/cookbook/spring_integration.md @@ -0,0 +1,363 @@ +--- +title: Spring integration +parent: Cookbook +grand_parent: Old stuff +has_children: false +nav_order: 1 +published: true +example_src: https://github.com/v12technology/fluxtion-examples/tree/main/cookbook/src/main/java/com/fluxtion/example/cookbook/spring +resources_src: https://github.com/v12technology/fluxtion-examples/tree/main/cookbook/src/main/resources/com/fluxtion/example/cookbook/spring +--- + +## Introduction + +Fluxtion is a dependency injection container specialised for event driven application deployments. The container +exposes event consumer end-points, routing events as methods calls to beans within the running container. A bean +registers a method as an event-handler by using Fluxtion annotations. Any beans referencing an event-handler bean will +be triggered by the container as the internal dispatcher propagates an event notification through the object graph. + +All methods on an interface can be exported by annotating the interface in an implementing bean, the container exports +the interface methods as a single service. A client can look up an exported service by interface type using the +container apis. All method calls on the service proxy are routed through the container's internal dispatcher. + +Spring is a popular DI container in the java world, this tutorial demonstrates how the construction logic of spring can +be combined with the dispatching logic of Fluxtion to simplify building event driven applications. The goal is to allow +the developer to concentrate on developing application logic while the container automatically builds the object graph +and constructs event dispatch logic. + +This example builds a small banking application, that supports credit, debit, account query, credit checking, +opening hours and persistence functions. The methods are grouped into service interfaces that are exposed by the +container. + +The steps to combine spring and fluxtion: + +- Create service interfaces that define the api of the banking app +- Create implementing classes for the service interfaces +- Create a spring config file declaring instances the DI container will manage +- Use Fluxtion annotations to export services and define event notification methods +- Pass the spring config file to the Fluxtion compiler and generate the DI container AOT +- Create an instance of the DI container and locate the service interfaces +- Use the service interfaces in the sample application + +## Application structure + +[See the example on GitHUb]({{page.example_src}}), top level package is `com.fluxtion.example.cookbook.spring.service`. + +Package structure: + +- **top level**: Banking app and a sample main +- **service**: interfaces the sample main and banking app invoke +- **node**: implementations of the service interfaces +- **data**: data types used by services +- **generation**: location of the Fluxtion ahead of time generated DI container + +## Spring beans +Fluxtion provides support for building the DI container using spring configuration. The example uses [a spring +configuration]({{page.resources_src}}/spring-account.xml) file to declare the beans that will be managed by the Fluxtion DI container: + +{% highlight xml %} + + + + + + + + + + + + + + + + + + + + +{% endhighlight %} + +Once the file is created the file location can be passed to Fluxtion to build the container: + +{% highlight java %} +ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("com/fluxtion/example/cookbook/spring/spring-account.xml"); +eventProcessor = FluxtionSpring.interpret(context); +{% endhighlight %} + +## Invoking a service + +The BankingApp instance creates an instance of the AOT generated Fluxtion DI container and provides access to container +exported services. The service reference the client code receives is a proxy the DI container creates, the +proxy handler routes method calls to instances managed by the container. + +Services the DI container exposes are event driven, they are designed to be invoked asynchronously and do not return +application values to client code. A service method can optionally return a boolean value that is used by the container +as an event propagation flag. If the flag is true then child references are notified the parent has changed due to an +external event. Child instances are notified of event propagation by the container calling a trigger method. A trigger +method is any zero argument method marked with an `OnTrigger` annotation. `OnTrigger` methods return an event +propagation flag to control event notification dispatch in the same was as exported service methods. + +The fluxtion DI container manages all the proxy creation, event dispatch to services, monitoring dirty flags and +propagating event notifications to child references. To access an exported service client code calls: + +{% highlight java %} +T exportedService = eventProcessor.getExportedService(); +{% endhighlight %} + +### Main method execution + +The main method creates an instance of the BankingApp, rerieves service interfaces and invokes application methods on +the interfaces. It is expected the BankingApp would be instantiated and used within a larger application that marshalls +client requests from the network and then invokes the BankingApp appropiately. + +{% highlight java %} +public class Main { + + public static void main(String[] args) { + BankingApp bankingApp = new BankingApp(GenerationStrategy.USE_AOT); + //get services + Account accountService = bankingApp.getBankAccount(); + BankingOperations bankControllerService = bankingApp.getBankingOperations(); + CreditCheck creditCheckService = bankingApp.getCreditCheck(); + //persistence + FileDataStore fileDataStore = new FileDataStore(Paths.get("data/spring/bank")); + bankControllerService.setDataStore(fileDataStore); + //replay state + fileDataStore.replay(bankingApp.getEventConsumer()); + + bankingApp.start(); + //should reject unknown account + accountService.deposit(999, 250.12); + + //get opening balance for acc 100 + accountService.publishBalance(100); + + //should reject bank closed + accountService.openAccount(100); + accountService.deposit(100, 250.12); + + //open bank + bankControllerService.openForBusiness(); + accountService.deposit(100, 250.12); + + //blacklist an account + creditCheckService.blackListAccount(100); + accountService.deposit(100, 46.90); + + //remove account from blacklist + creditCheckService.whiteListAccount(100); + accountService.deposit(100, 46.90); + + //close bank + bankControllerService.closedForBusiness(); + accountService.deposit(100, 13); + } + +} +{% endhighlight %} + +running the main method prints the following to the console: + +{% highlight text %} +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - deposit request:Transaction[accountNumber=999, amount=250.12, debit=false] +[INFO] AccountNode - reject unknown account:999 +[INFO] ResponsePublisher - response reject:Transaction[accountNumber=999, amount=250.12, debit=false] +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[INFO] AccountNode - ------------------------------------------------------ +[INFO] ResponsePublisher - account:100, balance:3267.22 +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - opened account:100 +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - deposit request:Transaction[accountNumber=100, amount=250.12, debit=false] +[INFO] CreditCheckNode - credit check passed +[WARN] CentralTransactionProcessor - reject bank closed +[INFO] ResponsePublisher - response reject:Transaction[accountNumber=100, amount=250.12, debit=false] +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[INFO] CentralTransactionProcessor - open accepting transactions +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - deposit request:Transaction[accountNumber=100, amount=250.12, debit=false] +[INFO] CreditCheckNode - credit check passed +[INFO] CentralTransactionProcessor - accept bank open +[INFO] AccountNode - updated balance:3517.3399999999997 account:100 +[INFO] ResponsePublisher - response accept:Transaction[accountNumber=100, amount=250.12, debit=false] +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[INFO] CreditCheckNode - credit check blacklisted:100 +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - deposit request:Transaction[accountNumber=100, amount=46.9, debit=false] +[WARN] CreditCheckNode - credit check failed +[INFO] ResponsePublisher - response reject:Transaction[accountNumber=100, amount=46.9, debit=false] +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[INFO] CreditCheckNode - credit check whitelisted:100 +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - deposit request:Transaction[accountNumber=100, amount=46.9, debit=false] +[INFO] CreditCheckNode - credit check passed +[INFO] CentralTransactionProcessor - accept bank open +[INFO] AccountNode - updated balance:3564.24 account:100 +[INFO] ResponsePublisher - response accept:Transaction[accountNumber=100, amount=46.9, debit=false] +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + +[WARN] CentralTransactionProcessor - closed rejecting all transactions +[INFO] AccountNode - ------------------------------------------------------ +[INFO] AccountNode - deposit request:Transaction[accountNumber=100, amount=13.0, debit=false] +[INFO] CreditCheckNode - credit check passed +[WARN] CentralTransactionProcessor - reject bank closed +[INFO] ResponsePublisher - response reject:Transaction[accountNumber=100, amount=13.0, debit=false] +[INFO] AccountNode - request complete +[INFO] AccountNode - ------------------------------------------------------ + + +Process finished with exit code 0 +{% endhighlight %} + +## Exporting a service + +To export a service the following steps are required: + +- Create an interface and then implement the interface with a concrete class +- The implementation class must extend ```ExportFunctionNode``` +- Mark the interface to export with ```@ExportService``` annotation + +For example to export the CreditCheck service: + +### CreditCheck interface + +{% highlight java %} +public interface CreditCheck { + void blackListAccount(int accountNumber); + void whiteListAccount(int accountNumber); +} + +{% endhighlight %} + +### CreditCheckNode concrete class + +The CreditCheckNode implements two interfaces CreditCheck and TransactionProcessor. Only the CreditCheck interface +methods are exported as this is only interface marked with ```@ExportService``` + +{% highlight java %} +public class CreditCheckNode extends ExportFunctionNode implements @ExportService CreditCheck, TransactionProcessor { + + private transient Set blackListedAccounts = new HashSet<>(); + private TransactionProcessor transactionSource; + private ResponsePublisher responsePublisher; + + @Override + @NoPropagateFunction + public void blackListAccount(int accountNumber) { + log.[INFO]("credit check blacklisted:{}", accountNumber); + blackListedAccounts.add(accountNumber); + } + + @Override + @NoPropagateFunction + public void whiteListAccount(int accountNumber) { + log.[INFO]("credit check whitelisted:{}", accountNumber); + blackListedAccounts.remove(accountNumber); + } + + public boolean propagateParentNotification(){ + Transaction transaction = transactionSource.currentTransactionRequest(); + int accountNumber = transaction.accountNumber(); + if(blackListedAccounts.contains(accountNumber)){ + log.[WARN]("credit check failed"); + transactionSource.rollbackTransaction(); + responsePublisher.rejectTransaction(transaction); + return false; + } + log.[INFO]("credit check passed"); + return true; + } + + @Override + public Transaction currentTransactionRequest() { + return transactionSource.currentTransactionRequest(); + } + + @Override + public void rollbackTransaction() { + transactionSource.rollbackTransaction(); + } + + @Override + public void commitTransaction(){ + transactionSource.commitTransaction(); + } + +} +{% endhighlight %} + +Notice the two CreditCheck methods are annotated with ```@NoPropagateFunction```, telling Fluxtion that no event +propagation will occur when either of these methods is invoked. The credit black list is a map and these methods should +only change the state of the internal map and not cause further processing to occur in the object graph. + +## Locating a service + +The steps required to locate a service and invoke methods on it are: + +- Build the DI container using one of the Fluxtion build methods +- To correctly intitialise the container call ```eventProcessor.init()``` on the DI instance +- To access the service call ```T service = eventProcessor.getExportedService()``` with the desired service type T + +### Accessing CreditCheck service + +The code below uses an enum to allow the user to select the DI generation strategy, in this example we are using the +AOT strategy. After eventprocessor generation the exported service are located and assigned to member variables in +the BankingApp class. + +```java +public class BankingApp { + + private final EventProcessor eventProcessor; + private final Account bankAccount; + private final CreditCheck creditCheck; + private final BankingOperations bankingOperations; + private final Consumer eventConsumer; + + @SneakyThrows + public BankingApp(GenerationStrategy generationStrategy) { + ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("com/fluxtion/example/cookbook/spring/spring-account.xml"); + eventProcessor = switch (generationStrategy) { + case USE_AOT -> new SpringBankEventProcessor(); + case INTERPRET -> FluxtionSpring.interpret(context); + case COMPILE -> FluxtionSpring.compile(context); + case GENERATE_AOT -> FluxtionSpring.compileAot(context, c -> { + c.setPackageName("com.fluxtion.example.cookbook.spring.generated"); + c.setClassName("SpringBankEventProcessor"); + }); + }; + eventProcessor.init(); + bankAccount = eventProcessor.getExportedService(); + creditCheck = eventProcessor.getExportedService(); + bankingOperations = eventProcessor.getExportedService(); + eventConsumer = eventProcessor::onEvent; + } + + public CreditCheck getCreditCheck() { + return creditCheck; + } +} +``` + +**line 22-24** acquire the exported services from the container by interface type + diff --git a/docs/cookbook/subscription_imperative.md b/docs/sections/old-stuff/cookbook/subscription_imperative.md similarity index 97% rename from docs/cookbook/subscription_imperative.md rename to docs/sections/old-stuff/cookbook/subscription_imperative.md index 892e83a13..5e02ade2e 100644 --- a/docs/cookbook/subscription_imperative.md +++ b/docs/sections/old-stuff/cookbook/subscription_imperative.md @@ -1,6 +1,7 @@ --- title: EventFeed integration parent: Cookbook +grand_parent: Old stuff has_children: false nav_order: 2 published: true @@ -32,18 +33,18 @@ Diagrams below demonstrate the interaction between key subscription components f ### Register an EventFeed with EventProcessor
    -![](../images/subscription/subscription_register.png) +![](../../../images/subscription/subscription_register.png) ### SharePriceNode subscribes to symbols
    -![](../images/subscription/subscibe_to_symbol.png) +![](../../../images/subscription/subscibe_to_symbol.png) ### Publish data from legacy app
    -![](../images/subscription/publish_data.png) +![](../../../images/subscription/publish_data.png) ## Example diff --git a/docs/sections/old-stuff/development.md b/docs/sections/old-stuff/development.md new file mode 100644 index 000000000..a192dd1d6 --- /dev/null +++ b/docs/sections/old-stuff/development.md @@ -0,0 +1,10 @@ +--- +title: Development +parent: Old stuff +has_children: true +nav_order: 12 +published: true +--- + + +## Guide for developing with Fluxtion diff --git a/docs/overview/detailpages/build_eventprocessor.md b/docs/sections/old-stuff/development/build_eventprocessor.md similarity index 95% rename from docs/overview/detailpages/build_eventprocessor.md rename to docs/sections/old-stuff/development/build_eventprocessor.md index 5f2f275aa..06f51b602 100644 --- a/docs/overview/detailpages/build_eventprocessor.md +++ b/docs/sections/old-stuff/development/build_eventprocessor.md @@ -1,9 +1,10 @@ --- title: Build an EventProcessor -parent: Overview +parent: Development +grand_parent: Old stuff has_children: false -nav_order: 3 -published: true +nav_order: 1 +published: false --- # Build an EventProcessor diff --git a/docs/overview/detailpages/describe_processing.md b/docs/sections/old-stuff/development/describe_processing.md similarity index 92% rename from docs/overview/detailpages/describe_processing.md rename to docs/sections/old-stuff/development/describe_processing.md index ba5c1d677..c542b35b2 100644 --- a/docs/overview/detailpages/describe_processing.md +++ b/docs/sections/old-stuff/development/describe_processing.md @@ -1,9 +1,10 @@ --- title: Describe processing logic -parent: Overview +parent: Development +grand_parent: Old stuff has_children: false -nav_order: 4 -published: true +nav_order: 2 +published: false --- # Building the processing graph diff --git a/docs/graphbuilding/streaming-building.md b/docs/sections/old-stuff/development/streaming-building.md similarity index 90% rename from docs/graphbuilding/streaming-building.md rename to docs/sections/old-stuff/development/streaming-building.md index 57e534cc1..242f7c972 100644 --- a/docs/graphbuilding/streaming-building.md +++ b/docs/sections/old-stuff/development/streaming-building.md @@ -1,9 +1,10 @@ --- -title: Streaming api -parent: Describe processing logic +title: Functional support +parent: Development +grand_parent: Old stuff has_children: false -nav_order: 1 -published: true +nav_order: 4 +published: false --- # Streaming api diff --git a/docs/sections/old-stuff/index.md b/docs/sections/old-stuff/index.md new file mode 100644 index 000000000..c2c80eab8 --- /dev/null +++ b/docs/sections/old-stuff/index.md @@ -0,0 +1,65 @@ +--- +title: Overview +has_children: false +nav_order: 1 +published: false +--- + +# Introduction + +Fluxtion is a java utility that builds embeddable dependency injection containers for use as a low latency event +processor within streaming applications. Developers concentrate on developing and extending business logic, dependency +injection and event dispatch is handled by the container. + +{: .info } +Fluxtion = Dependency injection + Event dispatch +{: .fs-8 } + +The Fluxtion container combines construction, instance lifecycle and event dispatch, supporting: + +
    +
    +
    +
      +
    • Streaming event processing
    • +
    • AOT compilation for fast start
    • +
    • Spring integration
    • +
    +
    +
    +
    +
    +
      +
    • Low latency microsecond response
    • +
    • Event sourcing compatible
    • +
    • Functional and imperative construction
    • +
    +
    +
    +
    + + +## Dependency injection container +Fluxtion builds a dependency injection container from configuration information given by the programmer. Functions +supported by the container include: creating instances, injecting references between beans, setting properties, calling +lifecycle methods, factory methods, singleton injection, named references, constructor and setter injection. +Configuration data can be programmatic, spring xml config, yaml or custom data format. + +There are three options for building a container: +- Interpreted - built and run in process, uses dynamic dispatch can handle millions of nodes +- Compiled - static analysis, code generated and compiled in process. handles thousands of nodes +- Compiled AOT - code generated at build time, zero cost start time when deployed + +Fluxtion DI containers are very lightweight and designed to be run within an application. Multiple containers can be +used within a single application each container providing specialised processing. + +## Automatic event dispatch + +The container exposes event consumer end-points, routing events as methods calls to beans within the container +via an internal dispatcher. The internal dispatcher propagates event notification through the object graph. + +Fluxtion leverages the familiar dependency injection workflow for constructing the object graph. Annotated +event handler and trigger methods are dispatch targets. When building a container Fluxtion uses the annotations to +calculate the dispatch call trees for the internal dispatcher. A bean can export multiple service interfaces or just a +single method. For exported interfaces the container generates proxies that routes calls from the proxy handler methods +to the container's dispatcher. diff --git a/docs/overview/overview.md b/docs/sections/old-stuff/overview.md similarity index 96% rename from docs/overview/overview.md rename to docs/sections/old-stuff/overview.md index 01e756c89..568a6fd15 100644 --- a/docs/overview/overview.md +++ b/docs/sections/old-stuff/overview.md @@ -1,5 +1,6 @@ --- -title: Concepts +title: Concepts 2 +parent: Overview has_children: false nav_order: 13 published: false @@ -23,7 +24,7 @@ When building a stream processing application the developer has to answer three Fluxtion is the automatic generation of the **WHEN** logic in a streaming application. Looking at the example diagram below Fluxtion acts as the arrows connecting components: -![](../images/flow-processing-example.png) +![](../../images/flow-processing-example.png) When a specific event is received only connected components are executed and always in the [correct predictable order](https://www.geeksforgeeks.org/topological-sorting/). diff --git a/docs/sections/old-stuff/running.md b/docs/sections/old-stuff/running.md new file mode 100644 index 000000000..ded61d95e --- /dev/null +++ b/docs/sections/old-stuff/running.md @@ -0,0 +1,10 @@ +--- +title: Event stream processing +parent: Old stuff +has_children: true +nav_order: 13 +published: true +--- + + +## Guide for executing event stream processing with Fluxtion diff --git a/docs/overview/detailpages/minimal_runtime.md b/docs/sections/old-stuff/running/minimal_runtime.md similarity index 96% rename from docs/overview/detailpages/minimal_runtime.md rename to docs/sections/old-stuff/running/minimal_runtime.md index 1db7b209a..b61230d93 100644 --- a/docs/overview/detailpages/minimal_runtime.md +++ b/docs/sections/old-stuff/running/minimal_runtime.md @@ -1,6 +1,7 @@ --- title: Zero dependency processor -parent: Overview +parent: Event stream processing +grand_parent: Old stuff has_children: false nav_order: 6 published: true @@ -24,7 +25,7 @@ To generate an event processor with no builder dependencies at runtime four step 1. Remove any calls to dynamically build a processor at runtime and use the fqn above to instantiate a statically generated processor, including test cases. 1. Update the pom file to exclude builder libraries as transitive dependencies. -The first three steps are covered in the previous [buildtime generation example](../../old-stuff/starting/aot_compilation.md). +The first three steps are covered in the previous [buildtime generation example](../starting/aot_compilation.md). ### Build zero dependency artifact The builder libraries are compile time only and marked @@ -99,7 +100,7 @@ the relevant section are shown below. ## Artifact analysis As part of the build the maven shade plugin generates an uber jar that contains all the dependencies to run the application. The uber jar contains Fluxtion runtime libraries, application classes and slf4j interfaces only. -The table below shows a comparinson with the [prevous example](../../old-stuff/starting/aot_compilation.md) where the +The table below shows a comparinson with the [prevous example](../starting/aot_compilation.md) where the final artifact contains all the Fluxtion builders and their transitive dependencies. | Fluxtion libraries | Size | External library count | diff --git a/docs/overview/detailpages/processing_events.md b/docs/sections/old-stuff/running/processing_events.md similarity index 98% rename from docs/overview/detailpages/processing_events.md rename to docs/sections/old-stuff/running/processing_events.md index 34155a914..bd5c16ecb 100644 --- a/docs/overview/detailpages/processing_events.md +++ b/docs/sections/old-stuff/running/processing_events.md @@ -1,8 +1,9 @@ --- title: Processing event streams -parent: Overview +parent: Event stream processing +grand_parent: Old stuff has_children: false -nav_order: 5 +nav_order: 1 published: true --- diff --git a/docs/old-stuff/starting/aot_compilation.md b/docs/sections/old-stuff/starting/aot_compilation.md similarity index 100% rename from docs/old-stuff/starting/aot_compilation.md rename to docs/sections/old-stuff/starting/aot_compilation.md diff --git a/docs/old-stuff/starting/get_started.md b/docs/sections/old-stuff/starting/get_started.md similarity index 100% rename from docs/old-stuff/starting/get_started.md rename to docs/sections/old-stuff/starting/get_started.md diff --git a/docs/sections/oldstuff.md b/docs/sections/oldstuff.md new file mode 100644 index 000000000..6e09c8634 --- /dev/null +++ b/docs/sections/oldstuff.md @@ -0,0 +1,10 @@ +--- +title: Old stuff +has_children: true +nav_order: 6 +published: false +--- + +# Introduction + +Hello \ No newline at end of file diff --git a/docs/sections/quick-reference.md b/docs/sections/quick-reference.md new file mode 100644 index 000000000..9fa7c474f --- /dev/null +++ b/docs/sections/quick-reference.md @@ -0,0 +1,10 @@ +--- +title: Quick reference +has_children: true +nav_order: 5 +published: true +--- + +# Introduction + +Hello \ No newline at end of file diff --git a/docs/sections/technical-deep-dive/event-processing.md b/docs/sections/technical-deep-dive/event-processing.md new file mode 100644 index 000000000..0409cd88e --- /dev/null +++ b/docs/sections/technical-deep-dive/event-processing.md @@ -0,0 +1,9 @@ +--- +title: Event processing +parent: Technical deep dive +has_children: false +nav_order: 1 +published: true +--- + +# Introduction \ No newline at end of file diff --git a/parent-root/pom.xml b/parent-root/pom.xml index c0720c1e1..0c1c2e13f 100644 --- a/parent-root/pom.xml +++ b/parent-root/pom.xml @@ -21,7 +21,7 @@ 4.0.0 com.fluxtion root-parent-pom - 9.0.1 + 9.1.13-SNAPSHOT pom fluxtion :: poms :: parent root @@ -92,7 +92,8 @@ org.sonatype.plugins nexus-staging-maven-plugin - 1.6.7 + 1.6.13 + true ossrh @@ -417,6 +418,11 @@ commons-lang3 3.12.0 + + org.apache.commons + commons-text + 1.10.0 + commons-cli commons-cli diff --git a/pom.xml b/pom.xml index abee28910..9aaa76c78 100644 --- a/pom.xml +++ b/pom.xml @@ -19,7 +19,7 @@ along with this program. If not, see 4.0.0 com.fluxtion fluxtion.master - 9.0.1 + 9.1.13-SNAPSHOT pom fluxtion diff --git a/runtime/pom.xml b/runtime/pom.xml index c05ff6b3d..8e5cc5d18 100644 --- a/runtime/pom.xml +++ b/runtime/pom.xml @@ -20,7 +20,7 @@ Copyright (C) 2018 V12 Technology Ltd. com.fluxtion root-parent-pom - 9.0.1 + 9.1.13-SNAPSHOT ../parent-root/pom.xml diff --git a/runtime/src/main/java/com/fluxtion/runtime/NodeDiscovery.java b/runtime/src/main/java/com/fluxtion/runtime/NodeDiscovery.java new file mode 100644 index 000000000..a931474db --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/NodeDiscovery.java @@ -0,0 +1,6 @@ +package com.fluxtion.runtime; + +public interface NodeDiscovery { + + T getNodeById(String id) throws NoSuchFieldException; +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/StaticEventProcessor.java b/runtime/src/main/java/com/fluxtion/runtime/StaticEventProcessor.java index 7b3306f4d..dddf466ac 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/StaticEventProcessor.java +++ b/runtime/src/main/java/com/fluxtion/runtime/StaticEventProcessor.java @@ -17,11 +17,8 @@ import com.fluxtion.runtime.annotations.OnEventHandler; import com.fluxtion.runtime.annotations.builder.Inject; -import com.fluxtion.runtime.audit.Auditor; -import com.fluxtion.runtime.audit.EventLogControlEvent; +import com.fluxtion.runtime.audit.*; import com.fluxtion.runtime.audit.EventLogControlEvent.LogLevel; -import com.fluxtion.runtime.audit.EventLogManager; -import com.fluxtion.runtime.audit.LogRecordListener; import com.fluxtion.runtime.dataflow.FlowFunction; import com.fluxtion.runtime.event.Signal; import com.fluxtion.runtime.input.EventFeed; @@ -33,11 +30,7 @@ import com.fluxtion.runtime.time.ClockStrategy; import java.util.Map; -import java.util.function.BooleanSupplier; -import java.util.function.Consumer; -import java.util.function.DoubleConsumer; -import java.util.function.IntConsumer; -import java.util.function.LongConsumer; +import java.util.function.*; /** * Processes events of any type and dispatches to registered {@link EventHandlerNode} @@ -64,7 +57,7 @@ * * @author Greg Higgins */ -public interface StaticEventProcessor { +public interface StaticEventProcessor extends NodeDiscovery { StaticEventProcessor NULL_EVENTHANDLER = e -> { }; @@ -349,6 +342,10 @@ default void setAuditLogProcessor(LogRecordListener logProcessor) { onEvent(new EventLogControlEvent(logProcessor)); } + default void setAuditLogRecordEncoder(LogRecord logRecord) { + onEvent(new EventLogControlEvent(logRecord)); + } + /** * Attempts to get the last {@link com.fluxtion.runtime.audit.LogRecord} as a String if one is available. Useful * for error handling if there is a filure in the graph; @@ -366,4 +363,71 @@ default String getLastAuditLogRecord() { default void setClockStrategy(ClockStrategy clockStrategy) { onEvent(ClockStrategy.registerClockEvent(clockStrategy)); } + + /** + * Returns an instance of the event processor cast to an interface type. The implemented interfaces of an event processor + * are specified using the com.fluxtion.compiler.EventProcessorConfig#addInterfaceImplementation during the + * building phase of the processor or using the @ExportService annotation. + * + * @param the interface type to cast to + * @return The {@link StaticEventProcessor} cast to an interface + */ + @SuppressWarnings("unchecked") + default T getExportedService() { + return (T) this; + } + + /** + * Determines if the event processor exports the service interface. The implemented interfaces of an event processor + * are specified using the com.fluxtion.compiler.EventProcessorConfig#addInterfaceImplementation during the + * building phase of the processor or using the @ExportService annotation. + * + * @param exportedServiceClass the type of service to search for + * @param the interface type to cast to + * @return flag indicating the event processor exports the interface + */ + default boolean exportsService(Class exportedServiceClass) { + T svcExport = getExportedService(); + return exportedServiceClass.isInstance(svcExport); + } + + /** + * Returns an instance of the event processor cast to an interface type. The implemented interfaces of an event processor + * are specified using the com.fluxtion.compiler.EventProcessorConfig#addInterfaceImplementation during the + * building phase of the processor or using the @ExportService annotation. + * + * @param exportedServiceClass the type of service to search for + * @param the interface type to cast to + * @return The {@link StaticEventProcessor} cast to an interface + */ + default T getExportedService(Class exportedServiceClass) { + return exportsService(exportedServiceClass) ? getExportedService() : null; + } + + /** + * Returns an instance of the event processor cast to an interface type, returning a default value if one cannot + * be found + * + * @param exportedServiceClass the type of service to search for + * @param defaultValue default service instance to return if no service is exported + * @param the interface type to cast to + * @return The {@link StaticEventProcessor} cast to an interface + */ + default T getExportedService(Class exportedServiceClass, T defaultValue) { + return exportsService(exportedServiceClass) ? getExportedService() : defaultValue; + } + + /** + * Passes the event processor cast to an interface for a consumer to process if the event processor exports service + * + * @param exportedServiceClass the type of service to search for + * @param serviceConsumer service consumer callback, invoked if the service is exported + * @param the interface type to cast to + */ + default void consumeServiceIfExported(Class exportedServiceClass, Consumer serviceConsumer) { + T exportedService = getExportedService(exportedServiceClass); + if (exportedService != null) { + serviceConsumer.accept(exportedService); + } + } } diff --git a/runtime/src/main/java/com/fluxtion/runtime/annotations/ExportService.java b/runtime/src/main/java/com/fluxtion/runtime/annotations/ExportService.java new file mode 100644 index 000000000..44f56a67c --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/annotations/ExportService.java @@ -0,0 +1,11 @@ +package com.fluxtion.runtime.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +@Retention(RetentionPolicy.RUNTIME) +@Target({ElementType.TYPE_USE}) +public @interface ExportService { +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/annotations/NoPropagateFunction.java b/runtime/src/main/java/com/fluxtion/runtime/annotations/NoPropagateFunction.java new file mode 100644 index 000000000..94df5d3f5 --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/annotations/NoPropagateFunction.java @@ -0,0 +1,17 @@ +package com.fluxtion.runtime.annotations; + +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * Marks an exported function as non propagating. + * + * @author Greg Higgins + * @see ExportService + */ +@Retention(RetentionPolicy.RUNTIME) +@Target(ElementType.METHOD) +public @interface NoPropagateFunction { +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/audit/EventLogControlEvent.java b/runtime/src/main/java/com/fluxtion/runtime/audit/EventLogControlEvent.java index 5e55e0140..e2919ff58 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/audit/EventLogControlEvent.java +++ b/runtime/src/main/java/com/fluxtion/runtime/audit/EventLogControlEvent.java @@ -43,7 +43,14 @@ public class EventLogControlEvent implements Event { * default, is no filtering and configuration will be applied to all SEP's. */ private String groupId; + /** + * user configured {@link LogRecordListener} + */ private LogRecordListener logRecordProcessor; + /** + * User configured {@link LogRecord} + */ + private LogRecord logRecord; public EventLogControlEvent() { this(LogLevel.INFO); @@ -58,6 +65,11 @@ public EventLogControlEvent(LogRecordListener logRecordProcessor) { this.logRecordProcessor = logRecordProcessor; } + public EventLogControlEvent(LogRecord logRecord) { + this(null, null, null); + this.logRecord = logRecord; + } + public EventLogControlEvent(String sourceId, String groupId, LogLevel level) { this.sourceId = sourceId; this.groupId = groupId; @@ -87,6 +99,10 @@ public LogRecordListener getLogRecordProcessor() { return logRecordProcessor; } + public LogRecord getLogRecord() { + return logRecord; + } + public enum LogLevel { NONE(0), ERROR(1), WARN(2), INFO(3), DEBUG(4), TRACE(5); diff --git a/runtime/src/main/java/com/fluxtion/runtime/audit/EventLogManager.java b/runtime/src/main/java/com/fluxtion/runtime/audit/EventLogManager.java index 5b293b521..fd786d23c 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/audit/EventLogManager.java +++ b/runtime/src/main/java/com/fluxtion/runtime/audit/EventLogManager.java @@ -53,6 +53,7 @@ public class EventLogManager implements Auditor { private LogRecordListener sink; private LogRecord logRecord; private Map node2Logger; + private Map name2LogSourceMap; private boolean clearAfterPublish; private static final Logger LOGGER = Logger.getLogger(EventLogManager.class.getName()); public boolean trace = false; @@ -105,11 +106,23 @@ public void nodeRegistered(Object node, String nodeName) { if (node instanceof EventLogSource) { EventLogSource calcSource = (EventLogSource) node; calcSource.setLogger(logger); + name2LogSourceMap.put(nodeName, calcSource); } node2Logger.put(nodeName, logger); canTrace = trace && node2Logger.values().stream().filter(e -> e.canLog(traceLevel)).findAny().isPresent(); } + private void updateLogRecord() { + for (Map.Entry stringEventLogSourceEntry : name2LogSourceMap.entrySet()) { + String nodeName = stringEventLogSourceEntry.getKey(); + EventLogSource calcSource = stringEventLogSourceEntry.getValue(); + EventLogger logger = new EventLogger(logRecord, nodeName); + calcSource.setLogger(logger); + name2LogSourceMap.put(nodeName, calcSource); + node2Logger.put(nodeName, logger); + } + } + @Override public boolean auditInvocations() { return trace; @@ -135,6 +148,11 @@ public void calculationLogConfig(EventLogControlEvent newConfig) { if (newConfig.getLogRecordProcessor() != null) { this.sink = newConfig.getLogRecordProcessor(); } + if (newConfig.getLogRecord() != null) { + this.logRecord = newConfig.getLogRecord(); + this.logRecord.setClock(clock); + updateLogRecord(); + } final EventLogControlEvent.LogLevel level = newConfig.getLevel(); if (level != null && (logRecord.groupingId == null || logRecord.groupingId.equals(newConfig.getGroupId()))) { @@ -198,6 +216,7 @@ public void init() { logRecord.printEventToString(printEventToString); logRecord.setPrintThreadName(printThreadName); node2Logger = new HashMap<>(); + name2LogSourceMap = new HashMap<>(); clearAfterPublish = true; } diff --git a/runtime/src/main/java/com/fluxtion/runtime/audit/EventLogger.java b/runtime/src/main/java/com/fluxtion/runtime/audit/EventLogger.java index ed99f2255..67f52e253 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/audit/EventLogger.java +++ b/runtime/src/main/java/com/fluxtion/runtime/audit/EventLogger.java @@ -255,7 +255,7 @@ public EventLogger logNodeInvocation(LogLevel logLevel) { public EventLogger log(String key, Object value, LogLevel logLevel) { if (this.logLevel.level >= logLevel.level) { - log(key, value == null ? "NULL" : value.toString(), logLevel); + logrecord.addRecord(logSourceId, key, value); } return this; } diff --git a/runtime/src/main/java/com/fluxtion/runtime/audit/LogRecord.java b/runtime/src/main/java/com/fluxtion/runtime/audit/LogRecord.java index 4e0a0e36e..8e46cd248 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/audit/LogRecord.java +++ b/runtime/src/main/java/com/fluxtion/runtime/audit/LogRecord.java @@ -98,6 +98,11 @@ public void addRecord(String sourceId, String propertyKey, CharSequence value) { sb.append(value); } + public void addRecord(String sourceId, String propertyKey, Object value) { + addSourceId(sourceId, propertyKey); + sb.append(value == null ? "NULL" : value); + } + public void addRecord(String sourceId, String propertyKey, boolean value) { addSourceId(sourceId, propertyKey); sb.append(value); @@ -112,6 +117,10 @@ public void addTrace(String sourceId) { addSourceId(sourceId, null); } + public void setClock(Clock clock) { + this.clock = clock; + } + public void printEventToString(boolean printEventToString) { this.printEventToString = printEventToString; } @@ -156,10 +165,10 @@ public void triggerEvent(Event event) { sb.append("\n groupingId: ").append(groupingId); sb.append("\n event: ").append(aClass.getSimpleName()); if (printEventToString) { - sb.append("\n eventToString: {").append(event.toString()).append('}'); + sb.append("\n eventToString: ").append(event.toString()); } if (printThreadName) { - sb.append("\n thread: {").append(Thread.currentThread().getName()).append('}'); + sb.append("\n thread: ").append(Thread.currentThread().getName()); } if (event.filterString() != null && !event.filterString().isEmpty()) { sb.append("\n eventFilter: ").append(event.filterString()); @@ -178,7 +187,7 @@ public void triggerObject(Object event) { sb.append("\n groupingId: ").append(groupingId); sb.append("\n event: ").append(aClass.getSimpleName()); if (printEventToString) { - sb.append("\n eventToString: {").append(event.toString()).append('}'); + sb.append("\n eventToString: ").append(event.toString()); } sb.append("\n nodeLogs: "); } diff --git a/runtime/src/main/java/com/fluxtion/runtime/callback/CallBackNode.java b/runtime/src/main/java/com/fluxtion/runtime/callback/CallBackNode.java new file mode 100644 index 000000000..f7d6eee6b --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/callback/CallBackNode.java @@ -0,0 +1,57 @@ +package com.fluxtion.runtime.callback; + +import com.fluxtion.runtime.EventProcessorBuilderService; +import com.fluxtion.runtime.annotations.builder.Inject; +import com.fluxtion.runtime.node.EventHandlerNode; +import com.fluxtion.runtime.node.SingleNamedNode; +import lombok.Getter; +import lombok.Setter; +import lombok.SneakyThrows; + +/** + * Extend this node to expose instance callback outside the {@link com.fluxtion.runtime.EventProcessor}. + * Use the protected methods + */ +@Getter +@Setter +public abstract class CallBackNode extends SingleNamedNode implements EventHandlerNode { + + private Object event; + @Inject + private EventDispatcher dispatcher; + @Inject + private DirtyStateMonitor dirtyStateMonitor; + + @SneakyThrows + public CallBackNode(String name) { + super(name); + if (EventProcessorBuilderService.service().buildTime()) { + Class cbClass = InstanceCallbackEvent.cbClassList.remove(0); + event = cbClass.getDeclaredConstructor().newInstance(); + } + } + + @Override + public boolean onEvent(Object e) { + return true; + } + + + @Override + @SneakyThrows + public final Class eventClass() { + return event.getClass(); + } + + /** + * Trigger a callback calculation with this node as the root of the + */ + protected final void triggerGraphCycle() { + dispatcher.processAsNewEventCycle(event); + } + + protected final void markDirty() { + dirtyStateMonitor.markDirty(this); + } + +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/callback/ExportFunctionAuditEvent.java b/runtime/src/main/java/com/fluxtion/runtime/callback/ExportFunctionAuditEvent.java new file mode 100644 index 000000000..ab4158cf3 --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/callback/ExportFunctionAuditEvent.java @@ -0,0 +1,18 @@ +package com.fluxtion.runtime.callback; + +import com.fluxtion.runtime.event.Event; + +public class ExportFunctionAuditEvent implements Event { + private String functionDescription; + + + public ExportFunctionAuditEvent setFunctionDescription(String functionDescription) { + this.functionDescription = functionDescription; + return this; + } + + @Override + public String toString() { + return functionDescription; + } +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/callback/InstanceCallbackEvent.java b/runtime/src/main/java/com/fluxtion/runtime/callback/InstanceCallbackEvent.java new file mode 100644 index 000000000..9f77589b3 --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/callback/InstanceCallbackEvent.java @@ -0,0 +1,1056 @@ +package com.fluxtion.runtime.callback; + +import com.fluxtion.runtime.annotations.builder.SepNode; + +import java.util.ArrayList; +import java.util.List; + +public interface InstanceCallbackEvent { + + List> cbClassList = new ArrayList<>(); + + static void main(String[] args) { + for (int i = 0; i < 128; i++) { + System.out.printf("@SepNode%n" + + "class InstanceCallbackEvent_%1$d extends NamedNodeSimple {%n" + + " public InstanceCallbackEvent_%1$d(){%n" + + " super(\"callBackTriggerEvent_%1$d\");%n" + + " }%n" + + "}%n%n", i); + } + for (int i = 0; i < 128; i++) { + System.out.printf("cbClassList.add(InstanceCallbackEvent_%d.class);%n", i); + } + } + + static void reset() { +// ExportFunctionTriggerEvent.reset(); +// System.out.println("FunctionTriggerNode::reset"); + cbClassList.clear(); + cbClassList.add(InstanceCallbackEvent_0.class); + cbClassList.add(InstanceCallbackEvent_1.class); + cbClassList.add(InstanceCallbackEvent_2.class); + cbClassList.add(InstanceCallbackEvent_3.class); + cbClassList.add(InstanceCallbackEvent_4.class); + cbClassList.add(InstanceCallbackEvent_5.class); + cbClassList.add(InstanceCallbackEvent_6.class); + cbClassList.add(InstanceCallbackEvent_7.class); + cbClassList.add(InstanceCallbackEvent_8.class); + cbClassList.add(InstanceCallbackEvent_9.class); + cbClassList.add(InstanceCallbackEvent_10.class); + cbClassList.add(InstanceCallbackEvent_11.class); + cbClassList.add(InstanceCallbackEvent_12.class); + cbClassList.add(InstanceCallbackEvent_13.class); + cbClassList.add(InstanceCallbackEvent_14.class); + cbClassList.add(InstanceCallbackEvent_15.class); + cbClassList.add(InstanceCallbackEvent_16.class); + cbClassList.add(InstanceCallbackEvent_17.class); + cbClassList.add(InstanceCallbackEvent_18.class); + cbClassList.add(InstanceCallbackEvent_19.class); + cbClassList.add(InstanceCallbackEvent_20.class); + cbClassList.add(InstanceCallbackEvent_21.class); + cbClassList.add(InstanceCallbackEvent_22.class); + cbClassList.add(InstanceCallbackEvent_23.class); + cbClassList.add(InstanceCallbackEvent_24.class); + cbClassList.add(InstanceCallbackEvent_25.class); + cbClassList.add(InstanceCallbackEvent_26.class); + cbClassList.add(InstanceCallbackEvent_27.class); + cbClassList.add(InstanceCallbackEvent_28.class); + cbClassList.add(InstanceCallbackEvent_29.class); + cbClassList.add(InstanceCallbackEvent_30.class); + cbClassList.add(InstanceCallbackEvent_31.class); + cbClassList.add(InstanceCallbackEvent_32.class); + cbClassList.add(InstanceCallbackEvent_33.class); + cbClassList.add(InstanceCallbackEvent_34.class); + cbClassList.add(InstanceCallbackEvent_35.class); + cbClassList.add(InstanceCallbackEvent_36.class); + cbClassList.add(InstanceCallbackEvent_37.class); + cbClassList.add(InstanceCallbackEvent_38.class); + cbClassList.add(InstanceCallbackEvent_39.class); + cbClassList.add(InstanceCallbackEvent_40.class); + cbClassList.add(InstanceCallbackEvent_41.class); + cbClassList.add(InstanceCallbackEvent_42.class); + cbClassList.add(InstanceCallbackEvent_43.class); + cbClassList.add(InstanceCallbackEvent_44.class); + cbClassList.add(InstanceCallbackEvent_45.class); + cbClassList.add(InstanceCallbackEvent_46.class); + cbClassList.add(InstanceCallbackEvent_47.class); + cbClassList.add(InstanceCallbackEvent_48.class); + cbClassList.add(InstanceCallbackEvent_49.class); + cbClassList.add(InstanceCallbackEvent_50.class); + cbClassList.add(InstanceCallbackEvent_51.class); + cbClassList.add(InstanceCallbackEvent_52.class); + cbClassList.add(InstanceCallbackEvent_53.class); + cbClassList.add(InstanceCallbackEvent_54.class); + cbClassList.add(InstanceCallbackEvent_55.class); + cbClassList.add(InstanceCallbackEvent_56.class); + cbClassList.add(InstanceCallbackEvent_57.class); + cbClassList.add(InstanceCallbackEvent_58.class); + cbClassList.add(InstanceCallbackEvent_59.class); + cbClassList.add(InstanceCallbackEvent_60.class); + cbClassList.add(InstanceCallbackEvent_61.class); + cbClassList.add(InstanceCallbackEvent_62.class); + cbClassList.add(InstanceCallbackEvent_63.class); + cbClassList.add(InstanceCallbackEvent_64.class); + cbClassList.add(InstanceCallbackEvent_65.class); + cbClassList.add(InstanceCallbackEvent_66.class); + cbClassList.add(InstanceCallbackEvent_67.class); + cbClassList.add(InstanceCallbackEvent_68.class); + cbClassList.add(InstanceCallbackEvent_69.class); + cbClassList.add(InstanceCallbackEvent_70.class); + cbClassList.add(InstanceCallbackEvent_71.class); + cbClassList.add(InstanceCallbackEvent_72.class); + cbClassList.add(InstanceCallbackEvent_73.class); + cbClassList.add(InstanceCallbackEvent_74.class); + cbClassList.add(InstanceCallbackEvent_75.class); + cbClassList.add(InstanceCallbackEvent_76.class); + cbClassList.add(InstanceCallbackEvent_77.class); + cbClassList.add(InstanceCallbackEvent_78.class); + cbClassList.add(InstanceCallbackEvent_79.class); + cbClassList.add(InstanceCallbackEvent_80.class); + cbClassList.add(InstanceCallbackEvent_81.class); + cbClassList.add(InstanceCallbackEvent_82.class); + cbClassList.add(InstanceCallbackEvent_83.class); + cbClassList.add(InstanceCallbackEvent_84.class); + cbClassList.add(InstanceCallbackEvent_85.class); + cbClassList.add(InstanceCallbackEvent_86.class); + cbClassList.add(InstanceCallbackEvent_87.class); + cbClassList.add(InstanceCallbackEvent_88.class); + cbClassList.add(InstanceCallbackEvent_89.class); + cbClassList.add(InstanceCallbackEvent_90.class); + cbClassList.add(InstanceCallbackEvent_91.class); + cbClassList.add(InstanceCallbackEvent_92.class); + cbClassList.add(InstanceCallbackEvent_93.class); + cbClassList.add(InstanceCallbackEvent_94.class); + cbClassList.add(InstanceCallbackEvent_95.class); + cbClassList.add(InstanceCallbackEvent_96.class); + cbClassList.add(InstanceCallbackEvent_97.class); + cbClassList.add(InstanceCallbackEvent_98.class); + cbClassList.add(InstanceCallbackEvent_99.class); + cbClassList.add(InstanceCallbackEvent_100.class); + cbClassList.add(InstanceCallbackEvent_101.class); + cbClassList.add(InstanceCallbackEvent_102.class); + cbClassList.add(InstanceCallbackEvent_103.class); + cbClassList.add(InstanceCallbackEvent_104.class); + cbClassList.add(InstanceCallbackEvent_105.class); + cbClassList.add(InstanceCallbackEvent_106.class); + cbClassList.add(InstanceCallbackEvent_107.class); + cbClassList.add(InstanceCallbackEvent_108.class); + cbClassList.add(InstanceCallbackEvent_109.class); + cbClassList.add(InstanceCallbackEvent_110.class); + cbClassList.add(InstanceCallbackEvent_111.class); + cbClassList.add(InstanceCallbackEvent_112.class); + cbClassList.add(InstanceCallbackEvent_113.class); + cbClassList.add(InstanceCallbackEvent_114.class); + cbClassList.add(InstanceCallbackEvent_115.class); + cbClassList.add(InstanceCallbackEvent_116.class); + cbClassList.add(InstanceCallbackEvent_117.class); + cbClassList.add(InstanceCallbackEvent_118.class); + cbClassList.add(InstanceCallbackEvent_119.class); + cbClassList.add(InstanceCallbackEvent_120.class); + cbClassList.add(InstanceCallbackEvent_121.class); + cbClassList.add(InstanceCallbackEvent_122.class); + cbClassList.add(InstanceCallbackEvent_123.class); + cbClassList.add(InstanceCallbackEvent_124.class); + cbClassList.add(InstanceCallbackEvent_125.class); + cbClassList.add(InstanceCallbackEvent_126.class); + cbClassList.add(InstanceCallbackEvent_127.class); + } + + + @SepNode + class InstanceCallbackEvent_0 extends NamedNodeSimple { + public InstanceCallbackEvent_0() { + super("callBackTriggerEvent_0"); + } + } + + @SepNode + class InstanceCallbackEvent_1 extends NamedNodeSimple { + public InstanceCallbackEvent_1() { + super("callBackTriggerEvent_1"); + } + } + + @SepNode + class InstanceCallbackEvent_2 extends NamedNodeSimple { + public InstanceCallbackEvent_2() { + super("callBackTriggerEvent_2"); + } + } + + @SepNode + class InstanceCallbackEvent_3 extends NamedNodeSimple { + public InstanceCallbackEvent_3() { + super("callBackTriggerEvent_3"); + } + } + + @SepNode + class InstanceCallbackEvent_4 extends NamedNodeSimple { + public InstanceCallbackEvent_4() { + super("callBackTriggerEvent_4"); + } + } + + @SepNode + class InstanceCallbackEvent_5 extends NamedNodeSimple { + public InstanceCallbackEvent_5() { + super("callBackTriggerEvent_5"); + } + } + + @SepNode + class InstanceCallbackEvent_6 extends NamedNodeSimple { + public InstanceCallbackEvent_6() { + super("callBackTriggerEvent_6"); + } + } + + @SepNode + class InstanceCallbackEvent_7 extends NamedNodeSimple { + public InstanceCallbackEvent_7() { + super("callBackTriggerEvent_7"); + } + } + + @SepNode + class InstanceCallbackEvent_8 extends NamedNodeSimple { + public InstanceCallbackEvent_8() { + super("callBackTriggerEvent_8"); + } + } + + @SepNode + class InstanceCallbackEvent_9 extends NamedNodeSimple { + public InstanceCallbackEvent_9() { + super("callBackTriggerEvent_9"); + } + } + + @SepNode + class InstanceCallbackEvent_10 extends NamedNodeSimple { + public InstanceCallbackEvent_10() { + super("callBackTriggerEvent_10"); + } + } + + @SepNode + class InstanceCallbackEvent_11 extends NamedNodeSimple { + public InstanceCallbackEvent_11() { + super("callBackTriggerEvent_11"); + } + } + + @SepNode + class InstanceCallbackEvent_12 extends NamedNodeSimple { + public InstanceCallbackEvent_12() { + super("callBackTriggerEvent_12"); + } + } + + @SepNode + class InstanceCallbackEvent_13 extends NamedNodeSimple { + public InstanceCallbackEvent_13() { + super("callBackTriggerEvent_13"); + } + } + + @SepNode + class InstanceCallbackEvent_14 extends NamedNodeSimple { + public InstanceCallbackEvent_14() { + super("callBackTriggerEvent_14"); + } + } + + @SepNode + class InstanceCallbackEvent_15 extends NamedNodeSimple { + public InstanceCallbackEvent_15() { + super("callBackTriggerEvent_15"); + } + } + + @SepNode + class InstanceCallbackEvent_16 extends NamedNodeSimple { + public InstanceCallbackEvent_16() { + super("callBackTriggerEvent_16"); + } + } + + @SepNode + class InstanceCallbackEvent_17 extends NamedNodeSimple { + public InstanceCallbackEvent_17() { + super("callBackTriggerEvent_17"); + } + } + + @SepNode + class InstanceCallbackEvent_18 extends NamedNodeSimple { + public InstanceCallbackEvent_18() { + super("callBackTriggerEvent_18"); + } + } + + @SepNode + class InstanceCallbackEvent_19 extends NamedNodeSimple { + public InstanceCallbackEvent_19() { + super("callBackTriggerEvent_19"); + } + } + + @SepNode + class InstanceCallbackEvent_20 extends NamedNodeSimple { + public InstanceCallbackEvent_20() { + super("callBackTriggerEvent_20"); + } + } + + @SepNode + class InstanceCallbackEvent_21 extends NamedNodeSimple { + public InstanceCallbackEvent_21() { + super("callBackTriggerEvent_21"); + } + } + + @SepNode + class InstanceCallbackEvent_22 extends NamedNodeSimple { + public InstanceCallbackEvent_22() { + super("callBackTriggerEvent_22"); + } + } + + @SepNode + class InstanceCallbackEvent_23 extends NamedNodeSimple { + public InstanceCallbackEvent_23() { + super("callBackTriggerEvent_23"); + } + } + + @SepNode + class InstanceCallbackEvent_24 extends NamedNodeSimple { + public InstanceCallbackEvent_24() { + super("callBackTriggerEvent_24"); + } + } + + @SepNode + class InstanceCallbackEvent_25 extends NamedNodeSimple { + public InstanceCallbackEvent_25() { + super("callBackTriggerEvent_25"); + } + } + + @SepNode + class InstanceCallbackEvent_26 extends NamedNodeSimple { + public InstanceCallbackEvent_26() { + super("callBackTriggerEvent_26"); + } + } + + @SepNode + class InstanceCallbackEvent_27 extends NamedNodeSimple { + public InstanceCallbackEvent_27() { + super("callBackTriggerEvent_27"); + } + } + + @SepNode + class InstanceCallbackEvent_28 extends NamedNodeSimple { + public InstanceCallbackEvent_28() { + super("callBackTriggerEvent_28"); + } + } + + @SepNode + class InstanceCallbackEvent_29 extends NamedNodeSimple { + public InstanceCallbackEvent_29() { + super("callBackTriggerEvent_29"); + } + } + + @SepNode + class InstanceCallbackEvent_30 extends NamedNodeSimple { + public InstanceCallbackEvent_30() { + super("callBackTriggerEvent_30"); + } + } + + @SepNode + class InstanceCallbackEvent_31 extends NamedNodeSimple { + public InstanceCallbackEvent_31() { + super("callBackTriggerEvent_31"); + } + } + + @SepNode + class InstanceCallbackEvent_32 extends NamedNodeSimple { + public InstanceCallbackEvent_32() { + super("callBackTriggerEvent_32"); + } + } + + @SepNode + class InstanceCallbackEvent_33 extends NamedNodeSimple { + public InstanceCallbackEvent_33() { + super("callBackTriggerEvent_33"); + } + } + + @SepNode + class InstanceCallbackEvent_34 extends NamedNodeSimple { + public InstanceCallbackEvent_34() { + super("callBackTriggerEvent_34"); + } + } + + @SepNode + class InstanceCallbackEvent_35 extends NamedNodeSimple { + public InstanceCallbackEvent_35() { + super("callBackTriggerEvent_35"); + } + } + + @SepNode + class InstanceCallbackEvent_36 extends NamedNodeSimple { + public InstanceCallbackEvent_36() { + super("callBackTriggerEvent_36"); + } + } + + @SepNode + class InstanceCallbackEvent_37 extends NamedNodeSimple { + public InstanceCallbackEvent_37() { + super("callBackTriggerEvent_37"); + } + } + + @SepNode + class InstanceCallbackEvent_38 extends NamedNodeSimple { + public InstanceCallbackEvent_38() { + super("callBackTriggerEvent_38"); + } + } + + @SepNode + class InstanceCallbackEvent_39 extends NamedNodeSimple { + public InstanceCallbackEvent_39() { + super("callBackTriggerEvent_39"); + } + } + + @SepNode + class InstanceCallbackEvent_40 extends NamedNodeSimple { + public InstanceCallbackEvent_40() { + super("callBackTriggerEvent_40"); + } + } + + @SepNode + class InstanceCallbackEvent_41 extends NamedNodeSimple { + public InstanceCallbackEvent_41() { + super("callBackTriggerEvent_41"); + } + } + + @SepNode + class InstanceCallbackEvent_42 extends NamedNodeSimple { + public InstanceCallbackEvent_42() { + super("callBackTriggerEvent_42"); + } + } + + @SepNode + class InstanceCallbackEvent_43 extends NamedNodeSimple { + public InstanceCallbackEvent_43() { + super("callBackTriggerEvent_43"); + } + } + + @SepNode + class InstanceCallbackEvent_44 extends NamedNodeSimple { + public InstanceCallbackEvent_44() { + super("callBackTriggerEvent_44"); + } + } + + @SepNode + class InstanceCallbackEvent_45 extends NamedNodeSimple { + public InstanceCallbackEvent_45() { + super("callBackTriggerEvent_45"); + } + } + + @SepNode + class InstanceCallbackEvent_46 extends NamedNodeSimple { + public InstanceCallbackEvent_46() { + super("callBackTriggerEvent_46"); + } + } + + @SepNode + class InstanceCallbackEvent_47 extends NamedNodeSimple { + public InstanceCallbackEvent_47() { + super("callBackTriggerEvent_47"); + } + } + + @SepNode + class InstanceCallbackEvent_48 extends NamedNodeSimple { + public InstanceCallbackEvent_48() { + super("callBackTriggerEvent_48"); + } + } + + @SepNode + class InstanceCallbackEvent_49 extends NamedNodeSimple { + public InstanceCallbackEvent_49() { + super("callBackTriggerEvent_49"); + } + } + + @SepNode + class InstanceCallbackEvent_50 extends NamedNodeSimple { + public InstanceCallbackEvent_50() { + super("callBackTriggerEvent_50"); + } + } + + @SepNode + class InstanceCallbackEvent_51 extends NamedNodeSimple { + public InstanceCallbackEvent_51() { + super("callBackTriggerEvent_51"); + } + } + + @SepNode + class InstanceCallbackEvent_52 extends NamedNodeSimple { + public InstanceCallbackEvent_52() { + super("callBackTriggerEvent_52"); + } + } + + @SepNode + class InstanceCallbackEvent_53 extends NamedNodeSimple { + public InstanceCallbackEvent_53() { + super("callBackTriggerEvent_53"); + } + } + + @SepNode + class InstanceCallbackEvent_54 extends NamedNodeSimple { + public InstanceCallbackEvent_54() { + super("callBackTriggerEvent_54"); + } + } + + @SepNode + class InstanceCallbackEvent_55 extends NamedNodeSimple { + public InstanceCallbackEvent_55() { + super("callBackTriggerEvent_55"); + } + } + + @SepNode + class InstanceCallbackEvent_56 extends NamedNodeSimple { + public InstanceCallbackEvent_56() { + super("callBackTriggerEvent_56"); + } + } + + @SepNode + class InstanceCallbackEvent_57 extends NamedNodeSimple { + public InstanceCallbackEvent_57() { + super("callBackTriggerEvent_57"); + } + } + + @SepNode + class InstanceCallbackEvent_58 extends NamedNodeSimple { + public InstanceCallbackEvent_58() { + super("callBackTriggerEvent_58"); + } + } + + @SepNode + class InstanceCallbackEvent_59 extends NamedNodeSimple { + public InstanceCallbackEvent_59() { + super("callBackTriggerEvent_59"); + } + } + + @SepNode + class InstanceCallbackEvent_60 extends NamedNodeSimple { + public InstanceCallbackEvent_60() { + super("callBackTriggerEvent_60"); + } + } + + @SepNode + class InstanceCallbackEvent_61 extends NamedNodeSimple { + public InstanceCallbackEvent_61() { + super("callBackTriggerEvent_61"); + } + } + + @SepNode + class InstanceCallbackEvent_62 extends NamedNodeSimple { + public InstanceCallbackEvent_62() { + super("callBackTriggerEvent_62"); + } + } + + @SepNode + class InstanceCallbackEvent_63 extends NamedNodeSimple { + public InstanceCallbackEvent_63() { + super("callBackTriggerEvent_63"); + } + } + + @SepNode + class InstanceCallbackEvent_64 extends NamedNodeSimple { + public InstanceCallbackEvent_64() { + super("callBackTriggerEvent_64"); + } + } + + @SepNode + class InstanceCallbackEvent_65 extends NamedNodeSimple { + public InstanceCallbackEvent_65() { + super("callBackTriggerEvent_65"); + } + } + + @SepNode + class InstanceCallbackEvent_66 extends NamedNodeSimple { + public InstanceCallbackEvent_66() { + super("callBackTriggerEvent_66"); + } + } + + @SepNode + class InstanceCallbackEvent_67 extends NamedNodeSimple { + public InstanceCallbackEvent_67() { + super("callBackTriggerEvent_67"); + } + } + + @SepNode + class InstanceCallbackEvent_68 extends NamedNodeSimple { + public InstanceCallbackEvent_68() { + super("callBackTriggerEvent_68"); + } + } + + @SepNode + class InstanceCallbackEvent_69 extends NamedNodeSimple { + public InstanceCallbackEvent_69() { + super("callBackTriggerEvent_69"); + } + } + + @SepNode + class InstanceCallbackEvent_70 extends NamedNodeSimple { + public InstanceCallbackEvent_70() { + super("callBackTriggerEvent_70"); + } + } + + @SepNode + class InstanceCallbackEvent_71 extends NamedNodeSimple { + public InstanceCallbackEvent_71() { + super("callBackTriggerEvent_71"); + } + } + + @SepNode + class InstanceCallbackEvent_72 extends NamedNodeSimple { + public InstanceCallbackEvent_72() { + super("callBackTriggerEvent_72"); + } + } + + @SepNode + class InstanceCallbackEvent_73 extends NamedNodeSimple { + public InstanceCallbackEvent_73() { + super("callBackTriggerEvent_73"); + } + } + + @SepNode + class InstanceCallbackEvent_74 extends NamedNodeSimple { + public InstanceCallbackEvent_74() { + super("callBackTriggerEvent_74"); + } + } + + @SepNode + class InstanceCallbackEvent_75 extends NamedNodeSimple { + public InstanceCallbackEvent_75() { + super("callBackTriggerEvent_75"); + } + } + + @SepNode + class InstanceCallbackEvent_76 extends NamedNodeSimple { + public InstanceCallbackEvent_76() { + super("callBackTriggerEvent_76"); + } + } + + @SepNode + class InstanceCallbackEvent_77 extends NamedNodeSimple { + public InstanceCallbackEvent_77() { + super("callBackTriggerEvent_77"); + } + } + + @SepNode + class InstanceCallbackEvent_78 extends NamedNodeSimple { + public InstanceCallbackEvent_78() { + super("callBackTriggerEvent_78"); + } + } + + @SepNode + class InstanceCallbackEvent_79 extends NamedNodeSimple { + public InstanceCallbackEvent_79() { + super("callBackTriggerEvent_79"); + } + } + + @SepNode + class InstanceCallbackEvent_80 extends NamedNodeSimple { + public InstanceCallbackEvent_80() { + super("callBackTriggerEvent_80"); + } + } + + @SepNode + class InstanceCallbackEvent_81 extends NamedNodeSimple { + public InstanceCallbackEvent_81() { + super("callBackTriggerEvent_81"); + } + } + + @SepNode + class InstanceCallbackEvent_82 extends NamedNodeSimple { + public InstanceCallbackEvent_82() { + super("callBackTriggerEvent_82"); + } + } + + @SepNode + class InstanceCallbackEvent_83 extends NamedNodeSimple { + public InstanceCallbackEvent_83() { + super("callBackTriggerEvent_83"); + } + } + + @SepNode + class InstanceCallbackEvent_84 extends NamedNodeSimple { + public InstanceCallbackEvent_84() { + super("callBackTriggerEvent_84"); + } + } + + @SepNode + class InstanceCallbackEvent_85 extends NamedNodeSimple { + public InstanceCallbackEvent_85() { + super("callBackTriggerEvent_85"); + } + } + + @SepNode + class InstanceCallbackEvent_86 extends NamedNodeSimple { + public InstanceCallbackEvent_86() { + super("callBackTriggerEvent_86"); + } + } + + @SepNode + class InstanceCallbackEvent_87 extends NamedNodeSimple { + public InstanceCallbackEvent_87() { + super("callBackTriggerEvent_87"); + } + } + + @SepNode + class InstanceCallbackEvent_88 extends NamedNodeSimple { + public InstanceCallbackEvent_88() { + super("callBackTriggerEvent_88"); + } + } + + @SepNode + class InstanceCallbackEvent_89 extends NamedNodeSimple { + public InstanceCallbackEvent_89() { + super("callBackTriggerEvent_89"); + } + } + + @SepNode + class InstanceCallbackEvent_90 extends NamedNodeSimple { + public InstanceCallbackEvent_90() { + super("callBackTriggerEvent_90"); + } + } + + @SepNode + class InstanceCallbackEvent_91 extends NamedNodeSimple { + public InstanceCallbackEvent_91() { + super("callBackTriggerEvent_91"); + } + } + + @SepNode + class InstanceCallbackEvent_92 extends NamedNodeSimple { + public InstanceCallbackEvent_92() { + super("callBackTriggerEvent_92"); + } + } + + @SepNode + class InstanceCallbackEvent_93 extends NamedNodeSimple { + public InstanceCallbackEvent_93() { + super("callBackTriggerEvent_93"); + } + } + + @SepNode + class InstanceCallbackEvent_94 extends NamedNodeSimple { + public InstanceCallbackEvent_94() { + super("callBackTriggerEvent_94"); + } + } + + @SepNode + class InstanceCallbackEvent_95 extends NamedNodeSimple { + public InstanceCallbackEvent_95() { + super("callBackTriggerEvent_95"); + } + } + + @SepNode + class InstanceCallbackEvent_96 extends NamedNodeSimple { + public InstanceCallbackEvent_96() { + super("callBackTriggerEvent_96"); + } + } + + @SepNode + class InstanceCallbackEvent_97 extends NamedNodeSimple { + public InstanceCallbackEvent_97() { + super("callBackTriggerEvent_97"); + } + } + + @SepNode + class InstanceCallbackEvent_98 extends NamedNodeSimple { + public InstanceCallbackEvent_98() { + super("callBackTriggerEvent_98"); + } + } + + @SepNode + class InstanceCallbackEvent_99 extends NamedNodeSimple { + public InstanceCallbackEvent_99() { + super("callBackTriggerEvent_99"); + } + } + + @SepNode + class InstanceCallbackEvent_100 extends NamedNodeSimple { + public InstanceCallbackEvent_100() { + super("callBackTriggerEvent_100"); + } + } + + @SepNode + class InstanceCallbackEvent_101 extends NamedNodeSimple { + public InstanceCallbackEvent_101() { + super("callBackTriggerEvent_101"); + } + } + + @SepNode + class InstanceCallbackEvent_102 extends NamedNodeSimple { + public InstanceCallbackEvent_102() { + super("callBackTriggerEvent_102"); + } + } + + @SepNode + class InstanceCallbackEvent_103 extends NamedNodeSimple { + public InstanceCallbackEvent_103() { + super("callBackTriggerEvent_103"); + } + } + + @SepNode + class InstanceCallbackEvent_104 extends NamedNodeSimple { + public InstanceCallbackEvent_104() { + super("callBackTriggerEvent_104"); + } + } + + @SepNode + class InstanceCallbackEvent_105 extends NamedNodeSimple { + public InstanceCallbackEvent_105() { + super("callBackTriggerEvent_105"); + } + } + + @SepNode + class InstanceCallbackEvent_106 extends NamedNodeSimple { + public InstanceCallbackEvent_106() { + super("callBackTriggerEvent_106"); + } + } + + @SepNode + class InstanceCallbackEvent_107 extends NamedNodeSimple { + public InstanceCallbackEvent_107() { + super("callBackTriggerEvent_107"); + } + } + + @SepNode + class InstanceCallbackEvent_108 extends NamedNodeSimple { + public InstanceCallbackEvent_108() { + super("callBackTriggerEvent_108"); + } + } + + @SepNode + class InstanceCallbackEvent_109 extends NamedNodeSimple { + public InstanceCallbackEvent_109() { + super("callBackTriggerEvent_109"); + } + } + + @SepNode + class InstanceCallbackEvent_110 extends NamedNodeSimple { + public InstanceCallbackEvent_110() { + super("callBackTriggerEvent_110"); + } + } + + @SepNode + class InstanceCallbackEvent_111 extends NamedNodeSimple { + public InstanceCallbackEvent_111() { + super("callBackTriggerEvent_111"); + } + } + + @SepNode + class InstanceCallbackEvent_112 extends NamedNodeSimple { + public InstanceCallbackEvent_112() { + super("callBackTriggerEvent_112"); + } + } + + @SepNode + class InstanceCallbackEvent_113 extends NamedNodeSimple { + public InstanceCallbackEvent_113() { + super("callBackTriggerEvent_113"); + } + } + + @SepNode + class InstanceCallbackEvent_114 extends NamedNodeSimple { + public InstanceCallbackEvent_114() { + super("callBackTriggerEvent_114"); + } + } + + @SepNode + class InstanceCallbackEvent_115 extends NamedNodeSimple { + public InstanceCallbackEvent_115() { + super("callBackTriggerEvent_115"); + } + } + + @SepNode + class InstanceCallbackEvent_116 extends NamedNodeSimple { + public InstanceCallbackEvent_116() { + super("callBackTriggerEvent_116"); + } + } + + @SepNode + class InstanceCallbackEvent_117 extends NamedNodeSimple { + public InstanceCallbackEvent_117() { + super("callBackTriggerEvent_117"); + } + } + + @SepNode + class InstanceCallbackEvent_118 extends NamedNodeSimple { + public InstanceCallbackEvent_118() { + super("callBackTriggerEvent_118"); + } + } + + @SepNode + class InstanceCallbackEvent_119 extends NamedNodeSimple { + public InstanceCallbackEvent_119() { + super("callBackTriggerEvent_119"); + } + } + + @SepNode + class InstanceCallbackEvent_120 extends NamedNodeSimple { + public InstanceCallbackEvent_120() { + super("callBackTriggerEvent_120"); + } + } + + @SepNode + class InstanceCallbackEvent_121 extends NamedNodeSimple { + public InstanceCallbackEvent_121() { + super("callBackTriggerEvent_121"); + } + } + + @SepNode + class InstanceCallbackEvent_122 extends NamedNodeSimple { + public InstanceCallbackEvent_122() { + super("callBackTriggerEvent_122"); + } + } + + @SepNode + class InstanceCallbackEvent_123 extends NamedNodeSimple { + public InstanceCallbackEvent_123() { + super("callBackTriggerEvent_123"); + } + } + + @SepNode + class InstanceCallbackEvent_124 extends NamedNodeSimple { + public InstanceCallbackEvent_124() { + super("callBackTriggerEvent_124"); + } + } + + @SepNode + class InstanceCallbackEvent_125 extends NamedNodeSimple { + public InstanceCallbackEvent_125() { + super("callBackTriggerEvent_125"); + } + } + + @SepNode + class InstanceCallbackEvent_126 extends NamedNodeSimple { + public InstanceCallbackEvent_126() { + super("callBackTriggerEvent_126"); + } + } + + @SepNode + class InstanceCallbackEvent_127 extends NamedNodeSimple { + public InstanceCallbackEvent_127() { + super("callBackTriggerEvent_127"); + } + } +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/callback/NamedNodeSimple.java b/runtime/src/main/java/com/fluxtion/runtime/callback/NamedNodeSimple.java new file mode 100644 index 000000000..ab0ad6dc5 --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/callback/NamedNodeSimple.java @@ -0,0 +1,33 @@ +package com.fluxtion.runtime.callback; + +import com.fluxtion.runtime.audit.EventLogNode; +import com.fluxtion.runtime.node.NamedNode; + +import java.util.Objects; + +public abstract class NamedNodeSimple extends EventLogNode implements NamedNode { + + private String name; + + public NamedNodeSimple(String name) { + this.name = name; + } + + @Override + public String getName() { + return name; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NamedNodeSimple that = (NamedNodeSimple) o; + return Objects.equals(name, that.name); + } + + @Override + public int hashCode() { + return name.hashCode(); + } +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/dataflow/aggregate/function/AggregateToListFlowFunction.java b/runtime/src/main/java/com/fluxtion/runtime/dataflow/aggregate/function/AggregateToListFlowFunction.java index 0bd71d06a..8e723cac2 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/dataflow/aggregate/function/AggregateToListFlowFunction.java +++ b/runtime/src/main/java/com/fluxtion/runtime/dataflow/aggregate/function/AggregateToListFlowFunction.java @@ -7,7 +7,7 @@ public class AggregateToListFlowFunction implements AggregateFlowFunction, AggregateToListFlowFunction> { - private final List list = new ArrayList<>(); + private transient final List list = new ArrayList<>(); private final int maxElementCount; diff --git a/runtime/src/main/java/com/fluxtion/runtime/dataflow/aggregate/function/AggregateToSetFlowFunction.java b/runtime/src/main/java/com/fluxtion/runtime/dataflow/aggregate/function/AggregateToSetFlowFunction.java index 8b33fe38e..405a6c825 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/dataflow/aggregate/function/AggregateToSetFlowFunction.java +++ b/runtime/src/main/java/com/fluxtion/runtime/dataflow/aggregate/function/AggregateToSetFlowFunction.java @@ -7,7 +7,7 @@ public class AggregateToSetFlowFunction implements AggregateFlowFunction, AggregateToSetFlowFunction> { - private final Set list = new HashSet<>(); + private transient final Set list = new HashSet<>(); @Override public Set reset() { @@ -36,11 +36,4 @@ public Set aggregate(T input) { return list; } - - public static class AggregateToSetFactory { - - public AggregateToSetFlowFunction newList() { - return new AggregateToSetFlowFunction<>(); - } - } } diff --git a/runtime/src/main/java/com/fluxtion/runtime/dataflow/function/MergeFlowFunction.java b/runtime/src/main/java/com/fluxtion/runtime/dataflow/function/MergeFlowFunction.java index e53cac972..f82961c98 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/dataflow/function/MergeFlowFunction.java +++ b/runtime/src/main/java/com/fluxtion/runtime/dataflow/function/MergeFlowFunction.java @@ -8,28 +8,33 @@ import com.fluxtion.runtime.dataflow.FlowFunction; import com.fluxtion.runtime.dataflow.TriggeredFlowFunction; +import java.util.ArrayList; +import java.util.List; + public class MergeFlowFunction, R extends FlowFunction> extends EventLogNode implements TriggeredFlowFunction { - private final S inputEventStream1; - private final R inputEventStream2; + private final List> mergeList; + private T update; @Inject public DirtyStateMonitor dirtyStateMonitor; - public MergeFlowFunction(S inputEventStream1, R inputEventStream2) { - this.inputEventStream1 = inputEventStream1; - this.inputEventStream2 = inputEventStream2; + public MergeFlowFunction( + S inputEventStream1, + R inputEventStream2) { + mergeList = new ArrayList<>(); + mergeList.add(inputEventStream1); + mergeList.add(inputEventStream2); } - @OnParentUpdate("inputEventStream1") - public void inputStream1Updated(S inputEventStream1) { - update = inputEventStream1.get(); + public MergeFlowFunction(List> mergeList) { + this.mergeList = mergeList; } - @OnParentUpdate("inputEventStream2") - public void inputStream2Updated(R inputEventStream2) { - update = inputEventStream2.get(); + @OnParentUpdate("mergeList") + public void inputStreamUpdated(FlowFunction inputEventStream1) { + update = (T) inputEventStream1.get(); } @Override diff --git a/runtime/src/main/java/com/fluxtion/runtime/dataflow/groupby/GroupByKey.java b/runtime/src/main/java/com/fluxtion/runtime/dataflow/groupby/GroupByKey.java new file mode 100644 index 000000000..e2e307217 --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/dataflow/groupby/GroupByKey.java @@ -0,0 +1,109 @@ +package com.fluxtion.runtime.dataflow.groupby; + +import com.fluxtion.runtime.partition.LambdaReflection; +import lombok.Getter; +import lombok.ToString; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +/** + * Constructs a compound key for using on group by constructs in a data flow. The key is composed using method references + * of the type to be grouped by. + * + * @param The type of data flow to create a key for + */ +@ToString(of = {"key", "name"}) +public class GroupByKey { + public final List> accessors; + private final transient StringBuilder keyHolder = new StringBuilder(); + @Getter + private final transient Class valueClass; + @Getter + private transient String key; + private transient final String name; + + public GroupByKey(List> accessorsToAdd) { + this.accessors = new ArrayList<>(); + String tmpName = ""; + for (LambdaReflection.SerializableFunction element : accessorsToAdd) { + if (!accessors.contains(element)) { + accessors.add(element); + tmpName += "_" + element.method().getName(); + } + } + valueClass = (Class) accessors.get(0).method().getDeclaringClass(); + name = valueClass.getName() + tmpName; + } + + public GroupByKey(LambdaReflection.SerializableFunction accessor) { + this(Arrays.asList(accessor)); + } + + @SafeVarargs + public GroupByKey(LambdaReflection.SerializableFunction... accessorList) { + this(Arrays.asList(accessorList)); + } + + private GroupByKey(GroupByKey toClone) { + accessors = toClone.accessors; + valueClass = toClone.getValueClass(); + name = toClone.name; + } + + public static LambdaReflection.SerializableFunction> build(LambdaReflection.SerializableFunction accessor) { + return new GroupByKey<>(accessor)::toKey; + } + + @SafeVarargs + public static LambdaReflection.SerializableFunction> build( + LambdaReflection.SerializableFunction accessor, + LambdaReflection.SerializableFunction... accessorList) { + List> accessors = new ArrayList<>(); + accessors.add(accessor); + accessors.addAll(Arrays.asList(accessorList)); + GroupByKey accessorKey = new GroupByKey<>(accessors); + return accessorKey::toKey; + } + + + public boolean keyPresent(LambdaReflection.SerializableFunction keyToCheck) { + return accessors.contains(keyToCheck); + } + + public GroupByKey toKey(T input) { + //TODO add object pooling + GroupByKey cloned = new GroupByKey<>(this); + cloned.keyHolder.setLength(0); + for (int i = 0, accessorsSize = accessors.size(); i < accessorsSize; i++) { + LambdaReflection.SerializableFunction accessor = accessors.get(i); + cloned.keyHolder.append(accessor.apply(input).toString()); + cloned.keyHolder.append("_"); + } + cloned.key = cloned.keyHolder.toString(); + return cloned; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + + GroupByKey that = (GroupByKey) o; + + if (!valueClass.equals(that.valueClass)) return false; + if (!Objects.equals(key, that.key)) return false; + return name.equals(that.name); + } + + @Override + public int hashCode() { + int result = valueClass.hashCode(); + result = 31 * result + (key != null ? key.hashCode() : 0); + result = 31 * result + name.hashCode(); + return result; + } + +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/dataflow/groupby/MutableTuple.java b/runtime/src/main/java/com/fluxtion/runtime/dataflow/groupby/MutableTuple.java index 653eb4f80..0915477c5 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/dataflow/groupby/MutableTuple.java +++ b/runtime/src/main/java/com/fluxtion/runtime/dataflow/groupby/MutableTuple.java @@ -2,9 +2,11 @@ import com.fluxtion.runtime.dataflow.Tuple; import com.fluxtion.runtime.util.ObjectPool; +import lombok.EqualsAndHashCode; import lombok.ToString; @ToString +@EqualsAndHashCode public class MutableTuple implements Tuple { private F first; private S second; diff --git a/runtime/src/main/java/com/fluxtion/runtime/dataflow/helpers/Collectors.java b/runtime/src/main/java/com/fluxtion/runtime/dataflow/helpers/Collectors.java index fa2990506..bb8765007 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/dataflow/helpers/Collectors.java +++ b/runtime/src/main/java/com/fluxtion/runtime/dataflow/helpers/Collectors.java @@ -10,18 +10,31 @@ import com.fluxtion.runtime.partition.LambdaReflection.SerializableSupplier; import java.util.List; +import java.util.Set; public interface Collectors { - static SerializableSupplier> toList(int maximumElementCount) { + static SerializableFunction> toSet() { + return new AggregateToSetFlowFunction()::aggregate; + } + + static SerializableFunction> toList() { + return new AggregateToListFlowFunction()::aggregate; + } + + static SerializableFunction> toList(int maxElements) { + return new AggregateToListFlowFunction(maxElements)::aggregate; + } + + static SerializableSupplier> listFactory(int maximumElementCount) { return new AggregateToListFactory(maximumElementCount)::newList; } - static SerializableSupplier> toList() { - return toList(-1); + static SerializableSupplier> listFactory() { + return listFactory(-1); } - static SerializableSupplier> toSet() { + static SerializableSupplier> setFactory() { return AggregateToSetFlowFunction::new; } diff --git a/runtime/src/main/java/com/fluxtion/runtime/dataflow/helpers/GroupingFactory.java b/runtime/src/main/java/com/fluxtion/runtime/dataflow/helpers/GroupingFactory.java index 464639661..73311bb9e 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/dataflow/helpers/GroupingFactory.java +++ b/runtime/src/main/java/com/fluxtion/runtime/dataflow/helpers/GroupingFactory.java @@ -31,7 +31,7 @@ public SerializableFunction getKeyFunction() { } public GroupByFlowFunctionWrapper, AggregateToListFlowFunction> groupByToList() { - SerializableSupplier> list = Collectors.toList(); + SerializableSupplier> list = Collectors.listFactory(); return new GroupByFlowFunctionWrapper<>(keyFunction, Mappers::identity, list); } diff --git a/runtime/src/main/java/com/fluxtion/runtime/dataflow/helpers/Predicates.java b/runtime/src/main/java/com/fluxtion/runtime/dataflow/helpers/Predicates.java index e58da23bc..d1a544eb0 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/dataflow/helpers/Predicates.java +++ b/runtime/src/main/java/com/fluxtion/runtime/dataflow/helpers/Predicates.java @@ -241,4 +241,13 @@ public void reset() { init(); } } + + @Value + class PredicateWrapper { + SerializableSupplier predicate; + + public boolean test(Object o) { + return predicate.get(); + } + } } diff --git a/runtime/src/main/java/com/fluxtion/runtime/lifecycle/Lifecycle.java b/runtime/src/main/java/com/fluxtion/runtime/lifecycle/Lifecycle.java index 46794fb6d..01265d800 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/lifecycle/Lifecycle.java +++ b/runtime/src/main/java/com/fluxtion/runtime/lifecycle/Lifecycle.java @@ -64,4 +64,6 @@ default void start() { */ default void stop() { } + + enum LifecycleEvent {Init, TearDown, Start, Stop, BatchPause, BatchEnd} } diff --git a/runtime/src/main/java/com/fluxtion/runtime/node/InstanceSupplierNode.java b/runtime/src/main/java/com/fluxtion/runtime/node/InstanceSupplierNode.java index 1cec96684..a713f75d8 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/node/InstanceSupplierNode.java +++ b/runtime/src/main/java/com/fluxtion/runtime/node/InstanceSupplierNode.java @@ -8,7 +8,7 @@ import com.fluxtion.runtime.annotations.builder.SepNode; @SepNode -public class InstanceSupplierNode extends SingleNamedNode implements NamedNode, InstanceSupplier { +public class InstanceSupplierNode extends SingleNamedNode implements InstanceSupplier { @Inject private final EventProcessorContext context; diff --git a/runtime/src/main/java/com/fluxtion/runtime/output/SinkPublisher.java b/runtime/src/main/java/com/fluxtion/runtime/output/SinkPublisher.java index c1d124bf1..00dfa56b4 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/output/SinkPublisher.java +++ b/runtime/src/main/java/com/fluxtion/runtime/output/SinkPublisher.java @@ -1,16 +1,17 @@ package com.fluxtion.runtime.output; import com.fluxtion.runtime.annotations.OnEventHandler; -import com.fluxtion.runtime.node.NamedNode; +import com.fluxtion.runtime.annotations.builder.AssignToField; +import com.fluxtion.runtime.node.SingleNamedNode; import java.util.function.Consumer; import java.util.function.DoubleConsumer; import java.util.function.IntConsumer; import java.util.function.LongConsumer; -public class SinkPublisher implements NamedNode { +public class SinkPublisher extends SingleNamedNode { - private final String filterString; + private transient final String filterString; private Consumer sink; private IntConsumer intConsumer; @@ -18,7 +19,8 @@ public class SinkPublisher implements NamedNode { private DoubleConsumer doubleConsumer; - public SinkPublisher(String filterString) { + public SinkPublisher(@AssignToField("name") String filterString) { + super(filterString); this.filterString = filterString; } @@ -58,8 +60,4 @@ public void publishLong(long value) { longConsumer.accept(value); } - @Override - public String getName() { - return filterString + "_Sink"; - } } diff --git a/runtime/src/main/java/com/fluxtion/runtime/partition/Partitioner.java b/runtime/src/main/java/com/fluxtion/runtime/partition/Partitioner.java index f2bfdf090..321b03e6c 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/partition/Partitioner.java +++ b/runtime/src/main/java/com/fluxtion/runtime/partition/Partitioner.java @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2018 V12 Technology Ltd. * * This program is free software: you can redistribute it and/or modify @@ -11,16 +11,16 @@ * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License - * along with this program. If not, see + * along with this program. If not, see * . */ package com.fluxtion.runtime.partition; import com.fluxtion.runtime.StaticEventProcessor; -//import com.fluxtion.api.event.Event; import com.fluxtion.runtime.lifecycle.BatchHandler; import com.fluxtion.runtime.lifecycle.Lifecycle; import com.fluxtion.runtime.partition.LambdaReflection.SerializableFunction; + import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Arrays; @@ -36,31 +36,31 @@ * to that instance. Partitioning allows a separate memory context for an * EventHandler, this can be useful when the structure of processing is repeated * but the state is different for each instance.

    - * - For example monitoring the fuel level on a fleet of cars is the same + *

    + * For example monitoring the fuel level on a fleet of cars is the same * processing for each car, but an individual car will have a unique fuel level. * In this case the StaticEventProcessor can be partitioned on vehicle * identification number. *

    - * - The StaticEventProcessor instance will be re-used or a new one created when + *

    + * The StaticEventProcessor instance will be re-used or a new one created when * new events are received. The {@link #partition(SerializableFunction)} methods provide * functions that map keys from an incoming event. the key is used * manage StaticEventProcessor instances in an underlying map. If no key/value * mapping is found then a new StaticEventProcessor is created and handles the * incoming message. *

    - * + *

    * New instances are created with s {@link Supplier} factory. Optionally an * initialiser can be provided that can access the newly created * StaticEventProcessor before any messages are processed. Using the car/fuel * analogy the initialiser function may set a reference to a global fuel monitor * from each newly created car processor. * - * @author gregp * @param + * @author gregp */ -public class Partitioner< E extends StaticEventProcessor> implements StaticEventProcessor, Lifecycle, BatchHandler { +public class Partitioner implements StaticEventProcessor, Lifecycle, BatchHandler { private HashMap class2Function; private HashMap class2MultiFunction; @@ -77,7 +77,7 @@ public class Partitioner< E extends StaticEventProcessor> implements StaticEvent /** * Create a partitioner with a factory and initialiser function. * - * @param factory factory creating instances of EventHandlers + * @param factory factory creating instances of EventHandlers * @param initialiser Initialisation function applied to new EventHandlers */ public Partitioner(Supplier factory, Consumer initialiser) { @@ -94,7 +94,6 @@ public Partitioner(Supplier factory, Consumer initialiser) { } /** - * * Create a partitioner with a factory. * * @param factory factory creating instances of EventHandlers @@ -115,7 +114,7 @@ public Partitioner(Supplier factory) { * * * @param - * @param Generated key + * @param Generated key * @param partitionKeyGen key mapping function */ public void keyPartitioner(Function partitionKeyGen) { @@ -126,8 +125,8 @@ public void keyPartitioner(Function partitionK * Register a partition key generator function that creates keys from a * property on an incoming event. an incoming Event * - * @param The incoming event - * @param The key type + * @param The incoming event + * @param The key type * @param supplier Key value supplier */ public void partition(SerializableFunction supplier) { @@ -139,8 +138,8 @@ public void partition(SerializableFunction supplier) { * Register a partition key generator function that creates keys from a set * of properties on an incoming event. an incoming Event * - * @param The incoming event - * @param The key type + * @param The incoming event + * @param The key type * @param supplier Key value suppliers */ public void partition(SerializableFunction... supplier) { @@ -174,8 +173,8 @@ public void onEvent(Object e) { } } } - - public E getProcessor(Object key){ + + public E getProcessor(Object key) { return handlerMap.get(key); } diff --git a/runtime/src/main/java/com/fluxtion/runtime/serializer/MapBuilder.java b/runtime/src/main/java/com/fluxtion/runtime/serializer/MapBuilder.java new file mode 100644 index 000000000..3f92066d4 --- /dev/null +++ b/runtime/src/main/java/com/fluxtion/runtime/serializer/MapBuilder.java @@ -0,0 +1,25 @@ +package com.fluxtion.runtime.serializer; + +import java.util.HashMap; +import java.util.Map; + +public class MapBuilder { + + private MapBuilder() { + } + + public static MapBuilder builder() { + return new MapBuilder(); + } + + private final HashMap map = new HashMap(); + + public Map build() { + return map; + } + + public MapBuilder put(Object key, Object value) { + map.put(key, value); + return this; + } +} diff --git a/runtime/src/main/java/com/fluxtion/runtime/time/Clock.java b/runtime/src/main/java/com/fluxtion/runtime/time/Clock.java index aa2694f5b..ce90193a5 100644 --- a/runtime/src/main/java/com/fluxtion/runtime/time/Clock.java +++ b/runtime/src/main/java/com/fluxtion/runtime/time/Clock.java @@ -11,13 +11,13 @@ * Server Side License for more details. * * You should have received a copy of the Server Side Public License - * along with this program. If not, see + * along with this program. If not, see * . */ package com.fluxtion.runtime.time; -import com.fluxtion.runtime.annotations.OnEventHandler; import com.fluxtion.runtime.annotations.Initialise; +import com.fluxtion.runtime.annotations.OnEventHandler; import com.fluxtion.runtime.audit.Auditor; import com.fluxtion.runtime.event.Event; import com.fluxtion.runtime.time.ClockStrategy.ClockStrategyEvent; @@ -37,9 +37,10 @@ */ public class Clock implements Auditor, Auditor.FirstAfterEvent { - private long eventTime; - private long processTime; + private transient long eventTime; + private transient long processTime; private ClockStrategy wallClock; + public static final Clock DEFAULT_CLOCK = new Clock(); @Override public void eventReceived(Event event) { @@ -61,7 +62,7 @@ public void nodeRegistered(Object node, String nodeName) {/*NoOp*/ public void setClockStrategy(ClockStrategyEvent event) { this.wallClock = event.getStrategy(); } - + /** * The time the last event was received by the processor * diff --git a/runtime/src/test/java/com/fluxtion/runtime/partition/PartitionerTest.java b/runtime/src/test/java/com/fluxtion/runtime/partition/PartitionerTest.java index 0c7f45674..b256cb352 100644 --- a/runtime/src/test/java/com/fluxtion/runtime/partition/PartitionerTest.java +++ b/runtime/src/test/java/com/fluxtion/runtime/partition/PartitionerTest.java @@ -1,4 +1,4 @@ -/* +/* * Copyright (C) 2018 V12 Technology Ltd. * * This program is free software: you can redistribute it and/or modify @@ -11,7 +11,7 @@ * Server Side Public License for more details. * * You should have received a copy of the Server Side Public License - * along with this program. If not, see + * along with this program. If not, see * . */ package com.fluxtion.runtime.partition; @@ -30,7 +30,6 @@ import static org.hamcrest.MatcherAssert.assertThat; /** - * * @author gregp */ public class PartitionerTest {