diff --git a/.circleci/config.yml b/.circleci/config.yml
index 7188b583..e8a142bf 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -2,17 +2,27 @@ version: 2
jobs:
build:
working_directory: ~/fn-java-fdk
- machine:
- java:
- version: oraclejdk8
+ machine: true
environment:
# store_artifacts doesn't shell substitute so the variable
# definitions are duplicated in those steps too.
FDK_ARTIFACT_DIR: /tmp/artifacts/fdk
TEST_ARTIFACT_DIR: /tmp/artifacts/tests
- STAGING_DIR: /tmp/staging-repository
+ REPOSITORY_LOCATION: /tmp/staging_repo
steps:
- checkout
+ - run:
+ name: Update Docker to latest
+ command: ./.circleci/install-docker.sh
+ - run:
+ name: Install fn binary (as it is needed for the integration tests)
+ command: ./.circleci/install-fn.sh
+ - run:
+ name: Workaround for https://issues.apache.org/jira/browse/SUREFIRE-1588
+ command: ./.circleci/fix-java-for-surefire.sh
+ - run:
+ name: Install junit-merge
+ command: npm install -g junit-merge
- run:
name: Set release to latest branch version
command: |
@@ -22,83 +32,22 @@ jobs:
name: Determine the release version
command: ./.circleci/update-versions.sh
- run:
- name: Build and deploy locally
+ name: Build and Test FDK
command: |
- rm -rf $STAGING_DIR
- mkdir -p $STAGING_DIR
- mvn deploy -DaltDeploymentRepository=localStagingDir::default::file://"$STAGING_DIR"
- - store_test_results:
- path: runtime/target/surefire-reports
- - store_test_results:
- path: testing/target/surefire-reports
-
+ export FN_FDK_VERSION=$(cat ./release.version)
+ ./build.sh
- run:
- name: Copy FDK artifacts to upload folder
+ name: Run integration tests
command: |
- mkdir -p "$FDK_ARTIFACT_DIR"
- cp -a api/target/*.jar "$FDK_ARTIFACT_DIR"
- cp -a runtime/target/*.jar "$FDK_ARTIFACT_DIR"
- - store_artifacts:
- name: Upload FDK artifacts
- path: /tmp/artifacts/fdk
-
- - run:
- name: Update Docker to latest
- command: ./.circleci/install-docker.sh
-
+ export FN_JAVA_FDK_VERSION=$(cat release.version)
+ ./integration-tests/run_tests_ci.sh
+ timeout: 1200
- run:
name: Login to Docker
command: |
if [[ "${CIRCLE_BRANCH}" == "master" && -z "${CIRCLE_PR_REPONAME}" ]]; then
docker login -u $DOCKER_USER -p $DOCKER_PASS
fi
-
- - run:
- name: Build fn-java-fdk Docker build image
- command: |
- cd build-image
- ./docker-build.sh -t fnproject/fn-java-fdk-build:$(cat ../release.version) .
-
- - run:
- name: Build fn-java-fdk-jdk9 Docker build image
- command: |
- cd build-image
- ./docker-build.sh -f Dockerfile-jdk9 -t fnproject/fn-java-fdk-build:jdk9-$(cat ../release.version) .
- - run:
- name: Build fn-java-fdk Docker runtime image
- command: |
- cd runtime
- docker build -t fnproject/fn-java-fdk:$(cat ../release.version) .
-
- - run:
- name: Build fn-java-fdk-jdk9 Docker runtime image
- command: |
- cd runtime
- docker build -f Dockerfile-jdk9 -t fnproject/fn-java-fdk:jdk9-$(cat ../release.version) .
-
- - run:
- name: Install fn binary (as it is needed for the integration tests)
- command: ./.circleci/install-fn.sh
-
- - run:
- name: Run integration tests
- command: REPOSITORY_LOCATION="$STAGING_DIR" FN_JAVA_FDK_VERSION=$(cat release.version) ./integration-tests/run-local.sh
- timeout: 1200
-
- - run:
- name: Copy integration test results to test artifact dir
- command: |
- mkdir -p "$TEST_ARTIFACT_DIR"
- for test_dir in ./integration-tests/main/test-*; do
- test_name="$(basename "$test_dir")"
- mkdir "${TEST_ARTIFACT_DIR}/${test_name}"
- cp -a $(find "$test_dir" -maxdepth 1 -type f) "${TEST_ARTIFACT_DIR}/${test_name}"
- done
- find "${TEST_ARTIFACT_DIR}"
- when: always
- - store_artifacts:
- name: Upload integration test results to artifacts
- path: /tmp/artifacts/tests
- deploy:
name: Release new version
command: |
@@ -108,4 +57,12 @@ jobs:
git branch --set-upstream-to=origin/${CIRCLE_BRANCH} ${CIRCLE_BRANCH}
./.circleci/release.sh
fi
+ - run:
+ name: Gather test results
+ when: always
+ command: |
+ junit-merge $(find . -wholename "*/target/surefire-reports/*.xml") --createDir -o test_results/merged_results.xml
+ - store_test_results:
+ when: always
+ path: test_results
diff --git a/.circleci/fix-java-for-surefire.sh b/.circleci/fix-java-for-surefire.sh
new file mode 100755
index 00000000..fb00731c
--- /dev/null
+++ b/.circleci/fix-java-for-surefire.sh
@@ -0,0 +1,19 @@
+#!/usr/bin/env bash
+
+ set -ex
+
+ cat << 'EOF' > $HOME/.m2/settings.xml
+
+
+
+ SUREFIRE-1588
+
+ true
+
+
+ -Djdk.net.URLClassPath.disableClassPathURLCheck=true
+
+
+
+
+ EOF
\ No newline at end of file
diff --git a/.circleci/install-go.sh b/.circleci/install-go.sh
deleted file mode 100755
index b61512dc..00000000
--- a/.circleci/install-go.sh
+++ /dev/null
@@ -1,32 +0,0 @@
-#!/usr/bin/env bash
-
-: ${GOVERSION:=1.8.3}
-: ${OS:=linux}
-: ${ARCH:=amd64}
-
-set -ex
-
-go version
-go env GOROOT
-
-# Remove previous Go version
-sudo rm -rf /usr/local/go
-
-# Install Go
-BUILD_DIR="/tmp/go-${GOVERSION}.${OS}.${ARCH}"
-mkdir -p "$BUILD_DIR"
-pushd "$BUILD_DIR"
- wget https://storage.googleapis.com/golang/go${GOVERSION}.${OS}-${ARCH}.tar.gz
- sudo tar -C /usr/local -xzf go${GOVERSION}.${OS}-${ARCH}.tar.gz
- go get -u github.com/golang/dep/...
-popd
-
-mkdir -p "${GOPATH}/bin"
-
-# Install Glide
-if ! type glide 2>&1 > /dev/null; then
- curl https://glide.sh/get | sh
-fi
-
-go version
-go env GOROOT
diff --git a/.circleci/release.sh b/.circleci/release.sh
index f4a3983e..7a8b83ad 100755
--- a/.circleci/release.sh
+++ b/.circleci/release.sh
@@ -6,9 +6,11 @@ USER=fnproject
SERVICE=fn-java-fdk
RUNTIME_IMAGE=${SERVICE}
BUILD_IMAGE=${SERVICE}-build
+NATIVE_INIT_IMAGE=fn-java-native-init
+NATIVE_BUILD_IMAGE=fn-java-native
release_version=$(cat release.version)
-if [[ $release_version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ; then
+if [[ ${release_version} =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ; then
echo "Deploying version $release_version"
else
echo Invalid version $release_version
@@ -21,22 +23,21 @@ version_parts=(${release_version//./ })
new_minor=$((${version_parts[2]}+1))
new_version="${version_parts[0]}.${version_parts[1]}.$new_minor"
-if [[ $new_version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ; then
+if [[ ${new_version} =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ; then
echo "Next version $new_version"
else
- echo Invalid new version $new_version
+ echo Invalid new version ${new_version}
exit 1
fi
+# Push result to git
-# Deploy to bintray
-mvn -s ./settings-deploy.xml \
- -DskipTests \
- -DaltDeploymentRepository="fnproject-release-repo::default::$MVN_RELEASE_REPO" \
- -Dfnproject-release-repo.username="$MVN_RELEASE_USER" \
- -Dfnproject-release-repo.password="$MVN_RELEASE_PASSWORD" \
- -DdeployAtEnd=true \
- clean deploy
+echo ${new_version} > release.version
+git tag -a "$release_version" -m "version $release_version"
+git add release.version
+git commit -m "$SERVICE: post-$release_version version bump [skip ci]"
+git push
+git push origin "$release_version"
# Regenerate runtime and build images and push them
@@ -44,40 +45,56 @@ mvn -s ./settings-deploy.xml \
moving_version=${release_version%.*}-latest
## jdk8 runtime
- docker tag $USER/$RUNTIME_IMAGE:${release_version} $USER/$RUNTIME_IMAGE:latest
- docker tag $USER/$RUNTIME_IMAGE:${release_version} $USER/$RUNTIME_IMAGE:${moving_version}
- docker push $USER/$RUNTIME_IMAGE:latest
- docker push $USER/$RUNTIME_IMAGE:${release_version}
- docker push $USER/$RUNTIME_IMAGE:${moving_version}
+ docker tag ${USER}/${RUNTIME_IMAGE}:${release_version} ${USER}/${RUNTIME_IMAGE}:latest
+ docker tag ${USER}/${RUNTIME_IMAGE}:${release_version} ${USER}/${RUNTIME_IMAGE}:${moving_version}
+ docker push ${USER}/${RUNTIME_IMAGE}:latest
+ docker push ${USER}/${RUNTIME_IMAGE}:${release_version}
+ docker push ${USER}/${RUNTIME_IMAGE}:${moving_version}
## jdk8 build
- docker tag $USER/$BUILD_IMAGE:${release_version} $USER/$BUILD_IMAGE:latest
- docker tag $USER/$BUILD_IMAGE:${release_version} $USER/$BUILD_IMAGE:${moving_version}
- docker push $USER/$BUILD_IMAGE:latest
- docker push $USER/$BUILD_IMAGE:${release_version}
- docker push $USER/$BUILD_IMAGE:${moving_version}
-
- ## jdk9 runtime
- docker tag $USER/$RUNTIME_IMAGE:jdk9-${release_version} $USER/$RUNTIME_IMAGE:jdk9-latest
- docker tag $USER/$RUNTIME_IMAGE:jdk9-${release_version} $USER/$RUNTIME_IMAGE:jdk9-${moving_version}
- docker push $USER/$RUNTIME_IMAGE:jdk9-latest
- docker push $USER/$RUNTIME_IMAGE:jdk9-${release_version}
- docker push $USER/$RUNTIME_IMAGE:jdk9-${moving_version}
-
- ## jdk9 build
- docker tag $USER/$BUILD_IMAGE:jdk9-${release_version} $USER/$BUILD_IMAGE:jdk9-latest
- docker tag $USER/$BUILD_IMAGE:jdk9-${release_version} $USER/$BUILD_IMAGE:jdk9-${moving_version}
- docker push $USER/$BUILD_IMAGE:jdk9-latest
- docker push $USER/$BUILD_IMAGE:jdk9-${release_version}
- docker push $USER/$BUILD_IMAGE:jdk9-${moving_version}
+ docker tag ${USER}/${BUILD_IMAGE}:${release_version} ${USER}/${BUILD_IMAGE}:latest
+ docker tag ${USER}/${BUILD_IMAGE}:${release_version} ${USER}/${BUILD_IMAGE}:${moving_version}
+ docker push ${USER}/${BUILD_IMAGE}:latest
+ docker push ${USER}/${BUILD_IMAGE}:${release_version}
+ docker push ${USER}/${BUILD_IMAGE}:${moving_version}
+
+ ## jre11 runtime
+ docker tag ${USER}/${RUNTIME_IMAGE}:jre11-${release_version} ${USER}/${RUNTIME_IMAGE}:jre11-latest
+ docker tag ${USER}/${RUNTIME_IMAGE}:jre11-${release_version} ${USER}/${RUNTIME_IMAGE}:jre11-${moving_version}
+ docker push ${USER}/${RUNTIME_IMAGE}:jre11-latest
+ docker push ${USER}/${RUNTIME_IMAGE}:jre11-${release_version}
+ docker push ${USER}/${RUNTIME_IMAGE}:jre11-${moving_version}
+
+ ## jdk11 build
+ docker tag ${USER}/${BUILD_IMAGE}:jdk11-${release_version} ${USER}/${BUILD_IMAGE}:jdk11-latest
+ docker tag ${USER}/${BUILD_IMAGE}:jdk11-${release_version} ${USER}/${BUILD_IMAGE}:jdk11-${moving_version}
+ docker push ${USER}/${BUILD_IMAGE}:jdk11-latest
+ docker push ${USER}/${BUILD_IMAGE}:jdk11-${release_version}
+ docker push ${USER}/${BUILD_IMAGE}:jdk11-${moving_version}
+
+ ## native init image
+ docker tag ${USER}/${NATIVE_INIT_IMAGE}:${release_version} ${USER}/${NATIVE_INIT_IMAGE}:latest
+ docker tag ${USER}/${NATIVE_INIT_IMAGE}:${release_version} ${USER}/${NATIVE_INIT_IMAGE}:${moving_version}
+ docker push ${USER}/${NATIVE_INIT_IMAGE}:latest
+ docker push ${USER}/${NATIVE_INIT_IMAGE}:${release_version}
+ docker push ${USER}/${NATIVE_INIT_IMAGE}:${moving_version}
+
)
+(
+ if [ -f images/build-native/native_build.image ] ; then
+ native_build_image=$(cat images/build-native/native_build.image)
+ docker tag ${native_build_image} ${USER}/${NATIVE_BUILD_IMAGE}:latest
+ docker push ${USER}/${NATIVE_BUILD_IMAGE}:latest
+ docker push ${native_build_image}
+ fi
+)
-# Push result to git
-echo $new_version > release.version
-git tag -a "$release_version" -m "version $release_version"
-git add release.version
-git commit -m "$SERVICE: post-$release_version version bump [skip ci]"
-git push
-git push origin "$release_version"
+# Deploy to bintray
+mvn -s ./settings-deploy.xml \
+ -DskipTests \
+ -DaltDeploymentRepository="fnproject-release-repo::default::${MVN_RELEASE_REPO}" \
+ -Dfnproject-release-repo.username="${MVN_RELEASE_USER}" \
+ -Dfnproject-release-repo.password="${MVN_RELEASE_PASSWORD}" \
+ clean deploy
diff --git a/.circleci/update-versions.sh b/.circleci/update-versions.sh
index 1490bd2e..394a3b76 100755
--- a/.circleci/update-versions.sh
+++ b/.circleci/update-versions.sh
@@ -1,4 +1,9 @@
#!/bin/bash
+#
+# this is instead for the maven release plugin (which sucks) - this sets the versions across the project before a build
+# for branch builds these are ignored (nothing is deployed)
+# For master releases this sets the latest version that this branch would be released as
+#
release_version=$(cat release.version)
if [[ $release_version =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] ; then
@@ -14,6 +19,6 @@ mvn versions:set -D newVersion=${release_version} versions:update-child-modules
# We need to replace the example dependency versions also
# (sed syntax for portability between MacOS and gnu)
find . -name pom.xml |
- xargs -n 1 sed -i.bak -e "s|.*|${release_version}|"
+ xargs -n 1 sed -i.bak -e "s|.*|${release_version}|"
find . -name pom.xml.bak -delete
diff --git a/.gitignore b/.gitignore
index 1fff5367..1e68a65a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -12,3 +12,7 @@ logs/
*.versionsBackup
.gradle
examples/gradle-build/build
+**/*.classpath
+**/*.project
+**/*.settings
+
diff --git a/README.md b/README.md
index 81eb9874..de02a132 100644
--- a/README.md
+++ b/README.md
@@ -1,331 +1,24 @@
-# Fn Java Functions Developer Kit (FDK)
[![CircleCI](https://circleci.com/gh/fnproject/fdk-java.svg?style=svg&circle-token=348bec5610c34421f6c436ab8f6a18e153cb1c01)](https://circleci.com/gh/fnproject/fdk-java)
-This project adds support for writing functions in Java on the [Fn
-platform](https://github.com/fnproject/fn), with full support for Java 9
-as the default out of the box.
+# Function Development Kit for Java (FDK for Java)
-# FAQ
-Some common questions are answered in [our FAQ](docs/FAQ.md).
+The Function Development Kit for Java makes it easy to build and deploy Java functions to Fn with full support for Java 11+ as the default out of the box.
-# Quick Start Tutorial
+Some of the FDK for Java features include:
-By following this step-by-step guide you will learn to create, run and deploy
-a simple app written in Java on Fn.
+- Parsing input and writing output
+- Flexible data binding to Java objects
+- Function testing using JUnit rules
+- And more!
-## Pre-requisites
+## Learn about the Fn Project
-Before you get started you will need the following things:
+New to Fn Project? If you want to learn more about using the Fn Project to power your next project, start with the [official documentation](https://github.com/fnproject/docs).
-* The [Fn CLI](https://github.com/fnproject/cli) tool
-* [Docker-ce 17.06+ installed locally](https://docs.docker.com/engine/installation/)
+## Using the Function Development Kit for Java
-### Install the Fn CLI tool
+For detailed instructions on using the FDK to build and deploy Java functions to Fn, please see the official FDK developer guide in our docs repo here: https://github.com/fnproject/docs/blob/master/fdks/fdk-java/README.md.
-To install the Fn CLI tool, just run the following:
-
-```
-curl -LSs https://raw.githubusercontent.com/fnproject/cli/master/install | sh
-```
-
-This will download a shell script and execute it. If the script asks for
-a password, that is because it invokes sudo.
-
-## Your first Function
-
-### 1. Create your first Java Function:
-
-```bash
-$ mkdir hello-java-function && cd hello-java-function
-$ fn init --runtime=java --name your_dockerhub_account/hello
-Runtime: java
-function boilerplate generated.
-func.yaml created
-```
-
-This creates the boilerplate for a new Java Function based on Maven and Oracle
-Java 9. The `pom.xml` includes a dependency on the latest version of the Fn
-Java FDK that is useful for developing your Java functions.
-
-You can now import this project into your favourite IDE as normal.
-
-### 2. Deep dive into your first Java Function:
-We'll now take a look at what makes up our new Java Function. First, lets take
-a look at the `func.yaml`:
-
-```bash
-$ cat func.yaml
-name: your_dockerhub_account/hello
-version: 0.0.1
-runtime: java
-cmd: com.example.fn.HelloFunction::handleRequest
-```
-
-The `cmd` field determines which method is called when your funciton is
-invoked. In the generated Function, the `func.yaml` references
-`com.example.fn.HelloFunction::handleRequest`. Your functions will likely live
-in different classes, and this field should always point to the method to
-execute, with the following syntax:
-
-```text
-cmd: ::
-```
-
-For more information about the fields in `func.yaml`, refer to the [Fn platform
-documentation](https://github.com/fnproject/fn/blob/master/docs/function-file.md)
-about it.
-
-Let's also have a brief look at the source:
-`src/main/java/com/example/fn/HelloFunction.java`:
-
-```java
-package com.example.fn;
-
-public class HelloFunction {
-
- public String handleRequest(String input) {
- String name = (input == null || input.isEmpty()) ? "world" : input;
-
- return "Hello, " + name + "!";
- }
-
-}
-```
-
-The function takes some optional input and returns a greeting dependent on it.
-
-### 3. Run your first Java Function:
-You are now ready to run your Function locally using the Fn CLI tool.
-
-```bash
-$ fn build
-Building image your_dockerhub_account/hello:0.0.1
-Sending build context to Docker daemon 14.34kB
-Step 1/11 : FROM fnproject/fn-java-fdk-build:jdk9-latest as build-stage
- ---> 5435658a63ac
-Step 2/11 : WORKDIR /function
- ---> 37340c5aa451
-
-...
-
-Step 5/11 : RUN mvn package dependency:copy-dependencies -DincludeScope=runtime -DskipTests=true -Dmdep.prependGroupId=true -DoutputDirectory=target --fail-never
----> Running in 58b3b1397ba2
-[INFO] Scanning for projects...
-Downloading: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-compiler-plugin/3.3/maven-compiler-plugin-3.3.pom
-Downloaded: https://repo.maven.apache.org/maven2/org/apache/maven/plugins/maven-compiler-plugin/3.3/maven-compiler-plugin-3.3.pom (11 kB at 21 kB/s)
-
-...
-
-[INFO] ------------------------------------------------------------------------
-[INFO] BUILD SUCCESS
-[INFO] ------------------------------------------------------------------------
-[INFO] Total time: 2.228 s
-[INFO] Finished at: 2017-06-27T12:06:59Z
-[INFO] Final Memory: 18M/143M
-[INFO] ------------------------------------------------------------------------
-
-...
-
-Function your_dockerhub_account/hello:0.0.1 built successfully.
-
-$ fn run
-Hello, world!
-```
-
-The next time you run this, it will execute much quicker as your dependencies
-are cached. Try passing in some input this time:
-
-```bash
-$ echo -n "Universe" | fn run
-...
-Hello, Universe!
-```
-
-### 4. Testing your function
-The Fn Java FDK includes a testing library providing useful [JUnit
-4](http://junit.org/junit4/) rules to test functions. Look at the test in
-`src/test/java/com/example/fn/HelloFunctionTest.java`:
-
-```java
-package com.example.fn;
-
-import com.fnproject.fn.testing.*;
-import org.junit.*;
-
-import static org.junit.Assert.*;
-
-public class HelloFunctionTest {
-
- @Rule
- public final FnTestingRule testing = FnTestingRule.createDefault();
-
- @Test
- public void shouldReturnGreeting() {
- testing.givenEvent().enqueue();
- testing.thenRun(HelloFunction.class, "handleRequest");
-
- FnResult result = testing.getOnlyResult();
- assertEquals("Hello, world!", result.getBodyAsString());
- }
-
-}
-```
-
-This test is very simple: it just enqueues an event with empty input and then
-runs the function, checking its output. Under the hood, the `FnTestingRule` is
-actually instantiating the same runtime wrapping function invocations, so that
-during the test your function will be invoked in exactly the same way that it
-would when deployed.
-
-There is much more functionality to construct tests in the testing library.
-Testing functions is covered in more detail in [Testing
-Functions](docs/TestingFunctions.md).
-
-### 5. Run using HTTP and the local Fn server
-The previous example used `fn run` to run a function directly via docker, you
-can also use the Fn server locally to test the deployment of your function and
-the HTTP calls to your functions.
-
-Open another terminal and start the Fn server:
-
-```bash
-$ fn start
-```
-
-Then in your original terminal create an app:
-
-```bash
-$ fn apps create java-app
-Successfully created app: java-app
-```
-
-Now deploy your Function using the `fn deploy` command. This will bump the
-function's version up, rebuild it, and push the image to the Docker registry,
-ready to be used in the function deployment. Finally it will create a route on
-the local Fn server, corresponding to your function.
-
-We are using the `--local` flag to tell fn to skip pushing the image anywhere
-as we are just going to run this on our local fn server that we started with
-`fn start` above.
-
-```bash
-$ fn deploy --app java-app --local
-...
-Bumped to version 0.0.2
-Building image hello:0.0.2
-Sending build context to Docker daemon 14.34kB
-
-...
-
-Successfully built bf2b7fa55520
-Successfully tagged your_dockerhub_account/hello:0.0.2
-Updating route /hello-java-function using image your_dockerhub_account/hello:0.0.2...
-```
-
-Call the Function via the Fn CLI:
-
-```bash
-$ fn call java-app /hello-java-function
-Hello, world!
-```
-
-You can also call the Function via curl:
-
-```bash
-$ curl http://localhost:8080/r/java-app/hello-java-function
-Hello, world!
-```
-
-### 6. Something more interesting
-The Fn Java FDK supports [flexible data binding](docs/DataBinding.md) to make
-it easier for you to map function input and output data to Java objects.
-
-Below is an example to of a Function that returns a POJO which will be
-serialized to JSON using Jackson:
-
-```java
-package com.example.fn;
-
-public class PojoFunction {
-
- public static class Greeting {
- public final String name;
- public final String salutation;
-
- public Greeting(String salutation, String name) {
- this.salutation = salutation;
- this.name = name;
- }
- }
-
- public Greeting greet(String name) {
- if (name == null || name.isEmpty())
- name = "World";
-
- return new Greeting("Hello", name);
- }
-
-}
-```
-
-Update your `func.yaml` to reference the new method:
-
-```yaml
-cmd: com.example.fn.PojoFunction::greet
-```
-
-Now run your new function:
-
-```bash
-$ fn run
-...
-{"name":"World","salutation":"Hello"}
-
-$ echo -n Michael | fn run
-...
-{"name":"Michael","salutation":"Hello"}
-```
-
-## 7. Where do I go from here?
-
-Learn more about the Fn Java FDK by reading the next tutorials in the series.
-Also check out the examples in the [`examples` directory](examples) for some
-functions demonstrating different features of the Fn Java FDK.
-
-### Configuring your function
-
-If you want to set up the state of your function object before the function is
-invoked, and to use external configuration variables that you can set up with
-the Fn tool, have a look at the [Function
-Configuration](docs/FunctionConfiguration.md) tutorial.
-
-### Input and output bindings
-
-You have the option of taking more control of how serialization and
-deserialization is performed by defining your own bindings.
-
-See the [Data Binding](docs/DataBinding.md) tutorial for other out-of-the-box
-options and the [Extending Data Binding](docs/ExtendingDataBinding.md) tutorial
-for how to define and use your own bindings.
-
-### Asynchronous workflows
-
-Suppose you want to call out to some other function from yours - perhaps
-a function written in a different language, or even one maintained by
-a different team. Maybe you then want to do some processing on the result. Or
-even have your function interact asynchronously with a completely different
-system. Perhaps you also need to maintain some state for the duration of your
-function, but you don't want to pay for execution time while you're waiting for
-someone else to do their work.
-
-If this sounds like you, then have a look at the [Fn Flow
-quickstart](docs/FnFlowsUserGuide.md).
-
-# Get help
-
- * Come over and chat to us on the [fnproject Slack](https://join.slack.com/t/fnproject/shared_invite/enQtMjIwNzc5MTE4ODg3LTdlYjE2YzU1MjAxODNhNGUzOGNhMmU2OTNhZmEwOTcxZDQxNGJiZmFiMzNiMTk0NjU2NTIxZGEyNjI0YmY4NTA).
- * Raise an issue in [our github](https://github.com/fnproject/fn-java-fdk/).
-
-# Contributing
+## Contributing to the Function Development Kit for Java
Please see [CONTRIBUTING.md](CONTRIBUTING.md).
diff --git a/api/pom.xml b/api/pom.xml
index 224d57d5..a7a27704 100644
--- a/api/pom.xml
+++ b/api/pom.xml
@@ -9,18 +9,25 @@
4.0.0
-
- UTF-8
-
-
api
+
+
+ junit
+ junit
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+ org.apache.maven.pluginsmaven-javadoc-plugin
- 3.0.0-M1attach-javadocs
@@ -33,7 +40,6 @@
org.netbeans.toolssigtest-maven-plugin
- 1.0
@@ -44,17 +50,9 @@
src/main/api/snapshot.sigfilestrictcheck
- com.fnproject.fn.api,com.fnproject.fn.api.exception,com.fnproject.fn.api.flow
+ com.fnproject.fn.api,com.fnproject.fn.api.exception
-
-
- junit
- junit
- 4.12
- test
-
-
diff --git a/api/src/main/api/snapshot.sigfile b/api/src/main/api/snapshot.sigfile
index 1f743e18..2ed59945 100644
--- a/api/src/main/api/snapshot.sigfile
+++ b/api/src/main/api/snapshot.sigfile
@@ -6,17 +6,52 @@ CLSS public abstract interface !annotation com.fnproject.fn.api.FnConfiguration
anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[METHOD])
intf java.lang.annotation.Annotation
+CLSS public abstract interface !annotation com.fnproject.fn.api.FnFeature
+ anno 0 java.lang.annotation.Repeatable(java.lang.Class extends java.lang.annotation.Annotation> value=class com.fnproject.fn.api.FnFeatures)
+ anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
+ anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE])
+intf java.lang.annotation.Annotation
+meth public abstract java.lang.Class extends com.fnproject.fn.api.RuntimeFeature> value()
+
+CLSS public abstract interface !annotation com.fnproject.fn.api.FnFeatures
+ anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
+ anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE])
+intf java.lang.annotation.Annotation
+meth public abstract com.fnproject.fn.api.FnFeature[] value()
+
CLSS public abstract interface com.fnproject.fn.api.FunctionInvoker
+innr public final static !enum Phase
meth public abstract java.util.Optional tryInvoke(com.fnproject.fn.api.InvocationContext,com.fnproject.fn.api.InputEvent)
+CLSS public final static !enum com.fnproject.fn.api.FunctionInvoker$Phase
+ outer com.fnproject.fn.api.FunctionInvoker
+fld public final static com.fnproject.fn.api.FunctionInvoker$Phase Call
+fld public final static com.fnproject.fn.api.FunctionInvoker$Phase PreCall
+meth public static com.fnproject.fn.api.FunctionInvoker$Phase valueOf(java.lang.String)
+meth public static com.fnproject.fn.api.FunctionInvoker$Phase[] values()
+supr java.lang.Enum
+
CLSS public final com.fnproject.fn.api.Headers
-meth public com.fnproject.fn.api.Headers withHeader(java.lang.String,java.lang.String)
-meth public java.util.Map getAll()
+intf java.io.Serializable
+meth public java.util.Map getAll()
+meth public !varargs com.fnproject.fn.api.Headers addHeader(java.lang.String,java.lang.String,java.lang.String[])
+meth public !varargs com.fnproject.fn.api.Headers setHeader(java.lang.String,java.lang.String,java.lang.String[])
+meth public boolean equals(java.lang.Object)
+meth public com.fnproject.fn.api.Headers removeHeader(java.lang.String)
+meth public com.fnproject.fn.api.Headers setHeader(java.lang.String,java.util.Collection)
+meth public com.fnproject.fn.api.Headers setHeaders(java.util.Map>)
+meth public int hashCode()
+meth public java.lang.String toString()
+meth public java.util.Collection keys()
+meth public java.util.List getAllValues(java.lang.String)
+meth public java.util.Map> asMap()
meth public java.util.Optional get(java.lang.String)
meth public static com.fnproject.fn.api.Headers emptyHeaders()
meth public static com.fnproject.fn.api.Headers fromMap(java.util.Map)
+meth public static com.fnproject.fn.api.Headers fromMultiHeaderMap(java.util.Map>)
+meth public static java.lang.String canonicalKey(java.lang.String)
supr java.lang.Object
-hfds headers
+hfds emptyHeaders,headerName,headers
CLSS public abstract interface !annotation com.fnproject.fn.api.InputBinding
anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
@@ -31,15 +66,16 @@ CLSS public abstract interface com.fnproject.fn.api.InputEvent
intf java.io.Closeable
meth public abstract <%0 extends java.lang.Object> {%%0} consumeBody(java.util.function.Function)
meth public abstract com.fnproject.fn.api.Headers getHeaders()
-meth public abstract com.fnproject.fn.api.QueryParameters getQueryParameters()
-meth public abstract java.lang.String getAppName()
-meth public abstract java.lang.String getMethod()
-meth public abstract java.lang.String getRequestUrl()
-meth public abstract java.lang.String getRoute()
+meth public abstract java.lang.String getCallID()
+meth public abstract java.time.Instant getDeadline()
CLSS public abstract interface com.fnproject.fn.api.InvocationContext
+meth public abstract !varargs void setResponseHeader(java.lang.String,java.lang.String,java.lang.String[])
+meth public abstract com.fnproject.fn.api.Headers getRequestHeaders()
meth public abstract com.fnproject.fn.api.RuntimeContext getRuntimeContext()
meth public abstract void addListener(com.fnproject.fn.api.InvocationListener)
+meth public abstract void addResponseHeader(java.lang.String,java.lang.String)
+meth public void setResponseContentType(java.lang.String)
CLSS public abstract interface com.fnproject.fn.api.InvocationListener
intf java.util.EventListener
@@ -64,16 +100,29 @@ CLSS public abstract interface com.fnproject.fn.api.OutputCoercion
meth public abstract java.util.Optional wrapFunctionResult(com.fnproject.fn.api.InvocationContext,com.fnproject.fn.api.MethodWrapper,java.lang.Object)
CLSS public abstract interface com.fnproject.fn.api.OutputEvent
-fld public final static int FAILURE = 500
-fld public final static int SUCCESS = 200
+fld public final static java.lang.String CONTENT_TYPE_HEADER = "Content-Type"
+innr public final static !enum Status
meth public abstract com.fnproject.fn.api.Headers getHeaders()
-meth public abstract int getStatusCode()
-meth public abstract java.util.Optional getContentType()
+meth public abstract com.fnproject.fn.api.OutputEvent$Status getStatus()
meth public abstract void writeToOutput(java.io.OutputStream) throws java.io.IOException
meth public boolean isSuccess()
-meth public static com.fnproject.fn.api.OutputEvent emptyResult(int)
-meth public static com.fnproject.fn.api.OutputEvent fromBytes(byte[],int,java.lang.String)
-meth public static com.fnproject.fn.api.OutputEvent fromBytes(byte[],int,java.lang.String,com.fnproject.fn.api.Headers)
+meth public com.fnproject.fn.api.OutputEvent withHeaders(com.fnproject.fn.api.Headers)
+meth public java.util.Optional getContentType()
+meth public static com.fnproject.fn.api.OutputEvent emptyResult(com.fnproject.fn.api.OutputEvent$Status)
+meth public static com.fnproject.fn.api.OutputEvent fromBytes(byte[],com.fnproject.fn.api.OutputEvent$Status,java.lang.String)
+meth public static com.fnproject.fn.api.OutputEvent fromBytes(byte[],com.fnproject.fn.api.OutputEvent$Status,java.lang.String,com.fnproject.fn.api.Headers)
+
+CLSS public final static !enum com.fnproject.fn.api.OutputEvent$Status
+ outer com.fnproject.fn.api.OutputEvent
+fld public final static com.fnproject.fn.api.OutputEvent$Status FunctionError
+fld public final static com.fnproject.fn.api.OutputEvent$Status FunctionTimeout
+fld public final static com.fnproject.fn.api.OutputEvent$Status InternalError
+fld public final static com.fnproject.fn.api.OutputEvent$Status Success
+meth public int getCode()
+meth public static com.fnproject.fn.api.OutputEvent$Status valueOf(java.lang.String)
+meth public static com.fnproject.fn.api.OutputEvent$Status[] values()
+supr java.lang.Enum
+hfds code
CLSS public abstract interface com.fnproject.fn.api.QueryParameters
meth public abstract java.util.List getValues(java.lang.String)
@@ -83,15 +132,21 @@ meth public abstract java.util.Optional get(java.lang.String)
CLSS public abstract interface com.fnproject.fn.api.RuntimeContext
meth public abstract <%0 extends java.lang.Object> java.util.Optional<{%%0}> getAttribute(java.lang.String,java.lang.Class<{%%0}>)
meth public abstract com.fnproject.fn.api.MethodWrapper getMethod()
+meth public abstract java.lang.String getAppID()
+meth public abstract java.lang.String getFunctionID()
meth public abstract java.util.List getInputCoercions(com.fnproject.fn.api.MethodWrapper,int)
meth public abstract java.util.List getOutputCoercions(java.lang.reflect.Method)
meth public abstract java.util.Map getConfiguration()
meth public abstract java.util.Optional getInvokeInstance()
meth public abstract java.util.Optional getConfigurationByKey(java.lang.String)
meth public abstract void addInputCoercion(com.fnproject.fn.api.InputCoercion)
+meth public abstract void addInvoker(com.fnproject.fn.api.FunctionInvoker,com.fnproject.fn.api.FunctionInvoker$Phase)
meth public abstract void addOutputCoercion(com.fnproject.fn.api.OutputCoercion)
meth public abstract void setAttribute(java.lang.String,java.lang.Object)
-meth public abstract void setInvoker(com.fnproject.fn.api.FunctionInvoker)
+meth public void setInvoker(com.fnproject.fn.api.FunctionInvoker)
+
+CLSS public abstract interface com.fnproject.fn.api.RuntimeFeature
+meth public abstract void initialize(com.fnproject.fn.api.RuntimeContext)
CLSS public abstract interface com.fnproject.fn.api.TypeWrapper
meth public abstract java.lang.Class> getParameterClass()
@@ -116,201 +171,16 @@ cons public init(java.lang.String)
cons public init(java.lang.String,java.lang.Exception)
supr java.lang.RuntimeException
-CLSS public abstract interface com.fnproject.fn.api.flow.Flow
-innr public final static !enum FlowState
-intf java.io.Serializable
-meth public <%0 extends java.io.Serializable, %1 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> invokeFunction(java.lang.String,{%%1},java.lang.Class<{%%0}>)
-meth public <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture invokeFunction(java.lang.String,{%%0})
-meth public abstract !varargs com.fnproject.fn.api.flow.FlowFuture anyOf(com.fnproject.fn.api.flow.FlowFuture>[])
-meth public abstract !varargs com.fnproject.fn.api.flow.FlowFuture allOf(com.fnproject.fn.api.flow.FlowFuture>[])
-meth public abstract <%0 extends java.io.Serializable, %1 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> invokeFunction(java.lang.String,com.fnproject.fn.api.flow.HttpMethod,com.fnproject.fn.api.Headers,{%%1},java.lang.Class<{%%0}>)
-meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture invokeFunction(java.lang.String,com.fnproject.fn.api.flow.HttpMethod,com.fnproject.fn.api.Headers,{%%0})
-meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> completedValue({%%0})
-meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> createFlowFuture()
-meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> failedFuture(java.lang.Throwable)
-meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> supply(com.fnproject.fn.api.flow.Flows$SerCallable<{%%0}>)
-meth public abstract com.fnproject.fn.api.flow.Flow addTerminationHook(com.fnproject.fn.api.flow.Flows$SerConsumer)
-meth public abstract com.fnproject.fn.api.flow.FlowFuture invokeFunction(java.lang.String,com.fnproject.fn.api.flow.HttpMethod,com.fnproject.fn.api.Headers,byte[])
-meth public abstract com.fnproject.fn.api.flow.FlowFuture delay(long,java.util.concurrent.TimeUnit)
-meth public abstract com.fnproject.fn.api.flow.FlowFuture supply(com.fnproject.fn.api.flow.Flows$SerRunnable)
-meth public com.fnproject.fn.api.flow.FlowFuture invokeFunction(java.lang.String,com.fnproject.fn.api.flow.HttpMethod)
-meth public com.fnproject.fn.api.flow.FlowFuture invokeFunction(java.lang.String,com.fnproject.fn.api.flow.HttpMethod,com.fnproject.fn.api.Headers)
-
-CLSS public final static !enum com.fnproject.fn.api.flow.Flow$FlowState
- outer com.fnproject.fn.api.flow.Flow
-fld public final static com.fnproject.fn.api.flow.Flow$FlowState CANCELLED
-fld public final static com.fnproject.fn.api.flow.Flow$FlowState FAILED
-fld public final static com.fnproject.fn.api.flow.Flow$FlowState KILLED
-fld public final static com.fnproject.fn.api.flow.Flow$FlowState SUCCEEDED
-fld public final static com.fnproject.fn.api.flow.Flow$FlowState UNKNOWN
-meth public static com.fnproject.fn.api.flow.Flow$FlowState valueOf(java.lang.String)
-meth public static com.fnproject.fn.api.flow.Flow$FlowState[] values()
-supr java.lang.Enum
-
-CLSS public com.fnproject.fn.api.flow.FlowCompletionException
-cons public init(java.lang.String)
-cons public init(java.lang.String,java.lang.Throwable)
-cons public init(java.lang.Throwable)
-supr java.lang.RuntimeException
-CLSS public abstract interface com.fnproject.fn.api.flow.FlowFuture<%0 extends java.lang.Object>
-intf java.io.Serializable
-meth public abstract <%0 extends java.lang.Object, %1 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%1}> thenCombine(com.fnproject.fn.api.flow.FlowFuture extends {%%0}>,com.fnproject.fn.api.flow.Flows$SerBiFunction super {com.fnproject.fn.api.flow.FlowFuture%0},? super {%%0},? extends {%%1}>)
-meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture thenAcceptBoth(com.fnproject.fn.api.flow.FlowFuture<{%%0}>,com.fnproject.fn.api.flow.Flows$SerBiConsumer<{com.fnproject.fn.api.flow.FlowFuture%0},{%%0}>)
-meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> applyToEither(com.fnproject.fn.api.flow.FlowFuture extends {com.fnproject.fn.api.flow.FlowFuture%0}>,com.fnproject.fn.api.flow.Flows$SerFunction<{com.fnproject.fn.api.flow.FlowFuture%0},{%%0}>)
-meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> handle(com.fnproject.fn.api.flow.Flows$SerBiFunction super {com.fnproject.fn.api.flow.FlowFuture%0},java.lang.Throwable,? extends {%%0}>)
-meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> thenApply(com.fnproject.fn.api.flow.Flows$SerFunction<{com.fnproject.fn.api.flow.FlowFuture%0},{%%0}>)
-meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> thenCompose(com.fnproject.fn.api.flow.Flows$SerFunction<{com.fnproject.fn.api.flow.FlowFuture%0},com.fnproject.fn.api.flow.FlowFuture<{%%0}>>)
-meth public abstract boolean cancel()
-meth public abstract boolean complete({com.fnproject.fn.api.flow.FlowFuture%0})
-meth public abstract boolean completeExceptionally(java.lang.Throwable)
-meth public abstract com.fnproject.fn.api.flow.FlowFuture acceptEither(com.fnproject.fn.api.flow.FlowFuture extends {com.fnproject.fn.api.flow.FlowFuture%0}>,com.fnproject.fn.api.flow.Flows$SerConsumer<{com.fnproject.fn.api.flow.FlowFuture%0}>)
-meth public abstract com.fnproject.fn.api.flow.FlowFuture thenAccept(com.fnproject.fn.api.flow.Flows$SerConsumer<{com.fnproject.fn.api.flow.FlowFuture%0}>)
-meth public abstract com.fnproject.fn.api.flow.FlowFuture thenRun(com.fnproject.fn.api.flow.Flows$SerRunnable)
-meth public abstract com.fnproject.fn.api.flow.FlowFuture<{com.fnproject.fn.api.flow.FlowFuture%0}> exceptionally(com.fnproject.fn.api.flow.Flows$SerFunction)
-meth public abstract com.fnproject.fn.api.flow.FlowFuture<{com.fnproject.fn.api.flow.FlowFuture%0}> exceptionallyCompose(com.fnproject.fn.api.flow.Flows$SerFunction>)
-meth public abstract com.fnproject.fn.api.flow.FlowFuture<{com.fnproject.fn.api.flow.FlowFuture%0}> whenComplete(com.fnproject.fn.api.flow.Flows$SerBiConsumer<{com.fnproject.fn.api.flow.FlowFuture%0},java.lang.Throwable>)
-meth public abstract {com.fnproject.fn.api.flow.FlowFuture%0} get()
-meth public abstract {com.fnproject.fn.api.flow.FlowFuture%0} get(long,java.util.concurrent.TimeUnit) throws java.util.concurrent.TimeoutException
-meth public abstract {com.fnproject.fn.api.flow.FlowFuture%0} getNow({com.fnproject.fn.api.flow.FlowFuture%0})
-
-CLSS public final com.fnproject.fn.api.flow.Flows
-innr public abstract interface static FlowSource
-innr public abstract interface static SerBiConsumer
-innr public abstract interface static SerBiFunction
-innr public abstract interface static SerCallable
-innr public abstract interface static SerConsumer
-innr public abstract interface static SerFunction
-innr public abstract interface static SerRunnable
-innr public abstract interface static SerSupplier
-meth public static com.fnproject.fn.api.flow.Flow currentFlow()
-meth public static com.fnproject.fn.api.flow.Flows$FlowSource getCurrentFlowSource()
-meth public static void setCurrentFlowSource(com.fnproject.fn.api.flow.Flows$FlowSource)
-supr java.lang.Object
-hfds flowSource
-
-CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$FlowSource
- outer com.fnproject.fn.api.flow.Flows
-meth public abstract com.fnproject.fn.api.flow.Flow currentFlow()
-
-CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerBiConsumer<%0 extends java.lang.Object, %1 extends java.lang.Object>
- outer com.fnproject.fn.api.flow.Flows
- anno 0 java.lang.FunctionalInterface()
-intf java.io.Serializable
-intf java.util.function.BiConsumer<{com.fnproject.fn.api.flow.Flows$SerBiConsumer%0},{com.fnproject.fn.api.flow.Flows$SerBiConsumer%1}>
-
-CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerBiFunction<%0 extends java.lang.Object, %1 extends java.lang.Object, %2 extends java.lang.Object>
- outer com.fnproject.fn.api.flow.Flows
- anno 0 java.lang.FunctionalInterface()
-intf java.io.Serializable
-intf java.util.function.BiFunction<{com.fnproject.fn.api.flow.Flows$SerBiFunction%0},{com.fnproject.fn.api.flow.Flows$SerBiFunction%1},{com.fnproject.fn.api.flow.Flows$SerBiFunction%2}>
-
-CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerCallable<%0 extends java.lang.Object>
- outer com.fnproject.fn.api.flow.Flows
- anno 0 java.lang.FunctionalInterface()
-intf java.io.Serializable
-intf java.util.concurrent.Callable<{com.fnproject.fn.api.flow.Flows$SerCallable%0}>
-
-CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerConsumer<%0 extends java.lang.Object>
- outer com.fnproject.fn.api.flow.Flows
- anno 0 java.lang.FunctionalInterface()
-intf java.io.Serializable
-intf java.util.function.Consumer<{com.fnproject.fn.api.flow.Flows$SerConsumer%0}>
-
-CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerFunction<%0 extends java.lang.Object, %1 extends java.lang.Object>
- outer com.fnproject.fn.api.flow.Flows
- anno 0 java.lang.FunctionalInterface()
-intf java.io.Serializable
-intf java.util.function.Function<{com.fnproject.fn.api.flow.Flows$SerFunction%0},{com.fnproject.fn.api.flow.Flows$SerFunction%1}>
-
-CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerRunnable
- outer com.fnproject.fn.api.flow.Flows
- anno 0 java.lang.FunctionalInterface()
-intf java.io.Serializable
-intf java.lang.Runnable
-
-CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerSupplier<%0 extends java.lang.Object>
- outer com.fnproject.fn.api.flow.Flows
- anno 0 java.lang.FunctionalInterface()
-intf java.io.Serializable
-intf java.util.function.Supplier<{com.fnproject.fn.api.flow.Flows$SerSupplier%0}>
-
-CLSS public com.fnproject.fn.api.flow.FunctionInvocationException
-cons public init(com.fnproject.fn.api.flow.HttpResponse)
-meth public com.fnproject.fn.api.flow.HttpResponse getFunctionResponse()
-supr java.lang.RuntimeException
-hfds functionResponse
-
-CLSS public com.fnproject.fn.api.flow.FunctionInvokeFailedException
-cons public init(java.lang.String)
-supr com.fnproject.fn.api.flow.PlatformException
-
-CLSS public com.fnproject.fn.api.flow.FunctionTimeoutException
-cons public init(java.lang.String)
-supr com.fnproject.fn.api.flow.PlatformException
-
-CLSS public final !enum com.fnproject.fn.api.flow.HttpMethod
-fld public final static com.fnproject.fn.api.flow.HttpMethod DELETE
-fld public final static com.fnproject.fn.api.flow.HttpMethod GET
-fld public final static com.fnproject.fn.api.flow.HttpMethod HEAD
-fld public final static com.fnproject.fn.api.flow.HttpMethod OPTIONS
-fld public final static com.fnproject.fn.api.flow.HttpMethod PATCH
-fld public final static com.fnproject.fn.api.flow.HttpMethod POST
-fld public final static com.fnproject.fn.api.flow.HttpMethod PUT
-meth public java.lang.String toString()
-meth public static com.fnproject.fn.api.flow.HttpMethod valueOf(java.lang.String)
-meth public static com.fnproject.fn.api.flow.HttpMethod[] values()
-supr java.lang.Enum
-hfds verb
-
-CLSS public abstract interface com.fnproject.fn.api.flow.HttpRequest
-meth public abstract byte[] getBodyAsBytes()
-meth public abstract com.fnproject.fn.api.Headers getHeaders()
-meth public abstract com.fnproject.fn.api.flow.HttpMethod getMethod()
-
-CLSS public abstract interface com.fnproject.fn.api.flow.HttpResponse
-meth public abstract byte[] getBodyAsBytes()
-meth public abstract com.fnproject.fn.api.Headers getHeaders()
-meth public abstract int getStatusCode()
-
-CLSS public com.fnproject.fn.api.flow.InvalidStageResponseException
-cons public init(java.lang.String)
-supr com.fnproject.fn.api.flow.PlatformException
-
-CLSS public com.fnproject.fn.api.flow.LambdaSerializationException
-cons public init(java.lang.String)
-cons public init(java.lang.String,java.lang.Exception)
-supr com.fnproject.fn.api.flow.FlowCompletionException
-
-CLSS public com.fnproject.fn.api.flow.PlatformException
-cons public init(java.lang.String)
-cons public init(java.lang.String,java.lang.Throwable)
-cons public init(java.lang.Throwable)
-meth public java.lang.Throwable fillInStackTrace()
-supr com.fnproject.fn.api.flow.FlowCompletionException
-
-CLSS public com.fnproject.fn.api.flow.ResultSerializationException
-cons public init(java.lang.String,java.lang.Throwable)
-supr com.fnproject.fn.api.flow.FlowCompletionException
-
-CLSS public com.fnproject.fn.api.flow.StageInvokeFailedException
-cons public init(java.lang.String)
-supr com.fnproject.fn.api.flow.PlatformException
-
-CLSS public com.fnproject.fn.api.flow.StageLostException
-cons public init(java.lang.String)
-supr com.fnproject.fn.api.flow.PlatformException
-
-CLSS public com.fnproject.fn.api.flow.StageTimeoutException
-cons public init(java.lang.String)
-supr com.fnproject.fn.api.flow.PlatformException
-
-CLSS public final com.fnproject.fn.api.flow.WrappedFunctionException
-cons public init(java.lang.Throwable)
-intf java.io.Serializable
-meth public java.lang.Class> getOriginalExceptionType()
-supr java.lang.RuntimeException
-hfds originalExceptionType
+CLSS public abstract interface com.fnproject.fn.api.httpgateway.HTTPGatewayContext
+meth public abstract InvocationContext getInvocationContext()
+meth public abstract Headers getHeaders()
+meth public abstract String getRequestURL()
+meth public abstract String getMethod()
+meth public abstract QueryParameters getQueryParameters()
+meth public abstract void addResponseHeader(String key, String value)
+meth public abstract void setResponseHeader(String key, String v1, String... vs)
+meth public abstract void setStatusCode(int code)
CLSS public abstract interface java.io.Closeable
intf java.lang.AutoCloseable
@@ -350,12 +220,6 @@ cons public init(java.lang.Throwable)
supr java.lang.Throwable
hfds serialVersionUID
-CLSS public abstract interface !annotation java.lang.FunctionalInterface
- anno 0 java.lang.annotation.Documented()
- anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
- anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE])
-intf java.lang.annotation.Annotation
-
CLSS public java.lang.Object
cons public init()
meth protected java.lang.Object clone() throws java.lang.CloneNotSupportedException
@@ -370,10 +234,6 @@ meth public final void wait(long,int) throws java.lang.InterruptedException
meth public int hashCode()
meth public java.lang.String toString()
-CLSS public abstract interface java.lang.Runnable
- anno 0 java.lang.FunctionalInterface()
-meth public abstract void run()
-
CLSS public java.lang.RuntimeException
cons protected init(java.lang.String,java.lang.Throwable,boolean,boolean)
cons public init()
@@ -419,6 +279,13 @@ CLSS public abstract interface !annotation java.lang.annotation.Documented
anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[ANNOTATION_TYPE])
intf java.lang.annotation.Annotation
+CLSS public abstract interface !annotation java.lang.annotation.Repeatable
+ anno 0 java.lang.annotation.Documented()
+ anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
+ anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[ANNOTATION_TYPE])
+intf java.lang.annotation.Annotation
+meth public abstract java.lang.Class extends java.lang.annotation.Annotation> value()
+
CLSS public abstract interface !annotation java.lang.annotation.Retention
anno 0 java.lang.annotation.Documented()
anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
@@ -435,33 +302,3 @@ meth public abstract java.lang.annotation.ElementType[] value()
CLSS public abstract interface java.util.EventListener
-CLSS public abstract interface java.util.concurrent.Callable<%0 extends java.lang.Object>
- anno 0 java.lang.FunctionalInterface()
-meth public abstract {java.util.concurrent.Callable%0} call() throws java.lang.Exception
-
-CLSS public abstract interface java.util.function.BiConsumer<%0 extends java.lang.Object, %1 extends java.lang.Object>
- anno 0 java.lang.FunctionalInterface()
-meth public abstract void accept({java.util.function.BiConsumer%0},{java.util.function.BiConsumer%1})
-meth public java.util.function.BiConsumer<{java.util.function.BiConsumer%0},{java.util.function.BiConsumer%1}> andThen(java.util.function.BiConsumer super {java.util.function.BiConsumer%0},? super {java.util.function.BiConsumer%1}>)
-
-CLSS public abstract interface java.util.function.BiFunction<%0 extends java.lang.Object, %1 extends java.lang.Object, %2 extends java.lang.Object>
- anno 0 java.lang.FunctionalInterface()
-meth public <%0 extends java.lang.Object> java.util.function.BiFunction<{java.util.function.BiFunction%0},{java.util.function.BiFunction%1},{%%0}> andThen(java.util.function.Function super {java.util.function.BiFunction%2},? extends {%%0}>)
-meth public abstract {java.util.function.BiFunction%2} apply({java.util.function.BiFunction%0},{java.util.function.BiFunction%1})
-
-CLSS public abstract interface java.util.function.Consumer<%0 extends java.lang.Object>
- anno 0 java.lang.FunctionalInterface()
-meth public abstract void accept({java.util.function.Consumer%0})
-meth public java.util.function.Consumer<{java.util.function.Consumer%0}> andThen(java.util.function.Consumer super {java.util.function.Consumer%0}>)
-
-CLSS public abstract interface java.util.function.Function<%0 extends java.lang.Object, %1 extends java.lang.Object>
- anno 0 java.lang.FunctionalInterface()
-meth public <%0 extends java.lang.Object> java.util.function.Function<{%%0},{java.util.function.Function%1}> compose(java.util.function.Function super {%%0},? extends {java.util.function.Function%0}>)
-meth public <%0 extends java.lang.Object> java.util.function.Function<{java.util.function.Function%0},{%%0}> andThen(java.util.function.Function super {java.util.function.Function%1},? extends {%%0}>)
-meth public abstract {java.util.function.Function%1} apply({java.util.function.Function%0})
-meth public static <%0 extends java.lang.Object> java.util.function.Function<{%%0},{%%0}> identity()
-
-CLSS public abstract interface java.util.function.Supplier<%0 extends java.lang.Object>
- anno 0 java.lang.FunctionalInterface()
-meth public abstract {java.util.function.Supplier%0} get()
-
diff --git a/api/src/main/java/com/fnproject/fn/api/FnFeature.java b/api/src/main/java/com/fnproject/fn/api/FnFeature.java
new file mode 100644
index 00000000..83f9dd77
--- /dev/null
+++ b/api/src/main/java/com/fnproject/fn/api/FnFeature.java
@@ -0,0 +1,19 @@
+package com.fnproject.fn.api;
+
+import java.lang.annotation.*;
+
+/**
+ * Annotation to be used in user function classes to enable runtime-wide feature.
+ *
+ * Runtime features are initialized at the point that the function class is loaded but prior to the call chain.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+@Repeatable(FnFeatures.class)
+public @interface FnFeature {
+ /**
+ * The feature class to load this must have a zero-arg public constructor
+ * @return feature class
+ */
+ Class extends RuntimeFeature> value();
+}
diff --git a/api/src/main/java/com/fnproject/fn/api/FnFeatures.java b/api/src/main/java/com/fnproject/fn/api/FnFeatures.java
new file mode 100644
index 00000000..3db2a4dc
--- /dev/null
+++ b/api/src/main/java/com/fnproject/fn/api/FnFeatures.java
@@ -0,0 +1,17 @@
+package com.fnproject.fn.api;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+
+/**
+ * Annotation to be used in user function classes to enable runtime-wide feature.
+ *
+ * Runtime features are initialized at the point that the function class is loaded but prior to the call chain.
+ */
+@Retention(RetentionPolicy.RUNTIME)
+@Target(ElementType.TYPE)
+public @interface FnFeatures {
+ FnFeature[] value();
+}
diff --git a/api/src/main/java/com/fnproject/fn/api/FunctionInvoker.java b/api/src/main/java/com/fnproject/fn/api/FunctionInvoker.java
index 7f1fa29a..70b9fd5d 100644
--- a/api/src/main/java/com/fnproject/fn/api/FunctionInvoker.java
+++ b/api/src/main/java/com/fnproject/fn/api/FunctionInvoker.java
@@ -6,6 +6,22 @@
* Handles the invocation of a given function call
*/
public interface FunctionInvoker {
+ /**
+ * Phase determines a loose ordering for invocation handler processing
+ * this should be used with {@link RuntimeContext#addInvoker(FunctionInvoker, Phase)} to add new invoke handlers to a runtime
+ */
+ enum Phase {
+ /**
+ * The Pre-Call phase runs before the main function call, all {@link FunctionInvoker} handlers added at this phase are tried prior to calling the {@link Phase#Call} phase
+ * This phase is typically used for handlers that /may/ intercept the request based on request attributes
+ */
+ PreCall,
+ /**
+ * The Call Phase indicates invokers that should handle call values - typically a given runtime will only be handled by one of these
+ */
+ Call
+ }
+
/**
* Optionally handles an invocation chain for this function
*
diff --git a/api/src/main/java/com/fnproject/fn/api/Headers.java b/api/src/main/java/com/fnproject/fn/api/Headers.java
index 2e069c03..7029e909 100644
--- a/api/src/main/java/com/fnproject/fn/api/Headers.java
+++ b/api/src/main/java/com/fnproject/fn/api/Headers.java
@@ -1,16 +1,62 @@
package com.fnproject.fn.api;
+import java.io.Serializable;
import java.util.*;
+import java.util.regex.Pattern;
+import java.util.stream.Stream;
/**
- * Represents the headers on an HTTP request or response. Multiple headers with the same key are collapsed into a single
- * entry where the values are concatenated by commas as per the HTTP spec (RFC 7230).
+ * Represents a set of String-String[] header attributes, per HTTP headers.
+ *
+ * Internally header keys are always canonicalized using HTTP header conventions
+ *
+ * Headers objects are immutable
+ *
+ * Keys are are stored and compared in a case-insensitive way and are canonicalised according to RFC 7230 conventions such that :
+ *
+ *
+ *
a-header
+ *
A-Header
+ *
A-HeaDer
+ *
+ * are all equivalent - keys are returned in the canonical form (lower cased except for leading characters)
+ * Where keys do not comply with HTTP header naming they are left as is.
*/
-public final class Headers {
- private Map headers;
+public final class Headers implements Serializable {
+ private static final Headers emptyHeaders = new Headers(Collections.emptyMap());
+ private Map> headers;
+
+ private Headers(Map> headersIn) {
+ this.headers = headersIn;
+ }
+
+ private static Pattern headerName = Pattern.compile("[A-Za-z0-9!#%&'*+-.^_`|~]+");
+
+ public Map getAll() {
+ return headers;
+ }
+
+ /**
+ * Calculates the canonical key (cf RFC 7230) for a header
+ *
+ * If the header contains invalid characters it returns the original header
+ *
+ * @param key the header key to canonicalise
+ * @return a canonical key or the original key if the input contains invalid character
+ */
+ public static String canonicalKey(String key) {
+ if (!headerName.matcher(key).matches()) {
+ return key;
+ }
+ String parts[] = key.split("-", -1);
+ for (int i = 0; i < parts.length; i++) {
+ String p = parts[i];
+ if (p.length() > 0) {
+ parts[i] = p.substring(0, 1).toUpperCase() + p.substring(1).toLowerCase();
+ }
+ }
+ return String.join("-", parts);
- private Headers(Map headers) {
- this.headers = headers;
}
/**
@@ -21,6 +67,23 @@ private Headers(Map headers) {
* @return {@code Headers} built from headers map
*/
public static Headers fromMap(Map headers) {
+ Objects.requireNonNull(headers, "headersIn");
+ Map> h = new HashMap<>();
+ headers.forEach((k, v) -> h.put(canonicalKey(k), Collections.singletonList(v)));
+ return new Headers(Collections.unmodifiableMap(new HashMap<>(h)));
+ }
+
+ /**
+ * Build a headers object from a map composed of (name, value) entries, we take a copy of the map and
+ * disallow any further modification
+ *
+ * @param headers underlying collection of header entries to copy
+ * @return {@code Headers} built from headers map
+ */
+ public static Headers fromMultiHeaderMap(Map> headers) {
+ Map> hm = new HashMap<>();
+
+ headers.forEach((k, vs) -> hm.put(canonicalKey(k), new ArrayList<>(vs)));
return new Headers(Collections.unmodifiableMap(new HashMap<>(Objects.requireNonNull(headers))));
}
@@ -30,43 +93,136 @@ public static Headers fromMap(Map headers) {
* @return empty headers
*/
public static Headers emptyHeaders() {
- return new Headers(Collections.emptyMap());
+ return emptyHeaders;
}
/**
- * Creates a new headers object with the specified header added
+ * Sets a map of headers, overwriting any headers in the current headers with the respective values
*
+ * @param vals a map of headers
+ * @return a new headers object with thos headers set
+ */
+ public Headers setHeaders(Map> vals) {
+ Objects.requireNonNull(vals, "vals");
+ Map> nm = new HashMap<>(headers);
+ vals.forEach((k, vs) -> {
+ vs.forEach(v -> Objects.requireNonNull(v, "header list contains null entries"));
+ nm.put(canonicalKey(k), vs);
+ });
+ return new Headers(nm);
+ }
+
+ /**
+ * Creates a new headers object with the specified header added - if a header with the same key existed it the new value is appended
+ *
* This will overwrite an existing header with an exact name match
+ *
* @param key new header key
- * @param value new header value
+ * @param v1 new header value
+ * @param vs additional header values to set
* @return a new headers object with the specified header added
*/
- public Headers withHeader(String key, String value){
- Map newHeaders = new HashMap<>();
- newHeaders.putAll(getAll());
- newHeaders.put(key,value);
- return new Headers(newHeaders);
+ public Headers addHeader(String key, String v1, String... vs) {
+ Objects.requireNonNull(key, "key");
+ Objects.requireNonNull(key, "value");
+
+ String canonKey = canonicalKey(key);
+
+ Map> nm = new HashMap<>(headers);
+ List current = nm.get(canonKey);
+
+ if (current == null) {
+ List s = new ArrayList<>();
+ s.add(v1);
+ s.addAll(Arrays.asList(vs));
+
+ nm.put(canonKey, Collections.unmodifiableList(s));
+ } else {
+ List s = new ArrayList<>(current);
+ s.add(v1);
+ s.addAll(Arrays.asList(vs));
+ nm.put(canonKey, Collections.unmodifiableList(s));
+ }
+ return new Headers(nm);
+
}
/**
- * Returns the header matching the specified key. This matches headers in a case-insensitive way and substitutes
- * underscore and hyphen characters such that : "CONTENT_TYPE" and "Content-type" are equivalent. If no matching
- * header is found then {@code Optional.empty} is returned.
+ * Creates a new headers object with the specified header set - this overwrites any existin values
*
- * Multiple headers are collapsed by {@code fn} into a single header entry delimited by commas (see
- * RFC7230 Sec 3.2.2 for details), for example
+ * This will overwrite an existing header with an exact name match
*
- *
- * Accept: text/html
- * Accept: text/plain
- *
+ * @param key new header key
+ * @param v1 new header value
+ * @param vs more header values to set
+ * @return a new headers object with the specified header added
+ */
+ public Headers setHeader(String key, String v1, String... vs) {
+ Objects.requireNonNull(key, "key");
+ Objects.requireNonNull(v1, "v1");
+ Stream.of(vs).forEach((v) -> Objects.requireNonNull(v, "vs"));
+
+ Map> nm = new HashMap<>(headers);
+ List s = new ArrayList<>();
+ s.add(v1);
+ s.addAll(Arrays.asList(vs));
+ nm.put(canonicalKey(key), Collections.unmodifiableList(s));
+ return new Headers(Collections.unmodifiableMap(nm));
+ }
+
+
+ /**
+ * Creates a new headers object with the specified headers set - this overwrites any existin values
+ *
+ * This will overwrite an existing header with an exact name match
*
- * is collapsed into
+ * @param key new header key
+ * @param vs header values to set
+ * @return a new headers object with the specified header added
+ */
+ public Headers setHeader(String key, Collection vs) {
+ Objects.requireNonNull(key, "key");
+ Objects.requireNonNull(vs, "vs");
+ if (vs.size() == 0) {
+ throw new IllegalArgumentException("can't set keys to an empty list");
+ }
+ vs.forEach((v) -> Objects.requireNonNull(v, "vs"));
+
+ Map> nm = new HashMap<>(headers);
+ nm.put(canonicalKey(key), Collections.unmodifiableList(new ArrayList<>(vs)));
+ return new Headers(Collections.unmodifiableMap(nm));
+
+ }
+
+ /**
+ * Creates a new headers object with the specified headers remove - this overwrites any existin values
+ *
+ * This will overwrite an existing header with an exact name match
*
- *
- * Accept: text/html, text/plain
- *
+ * @param key new header key
+ * @return a new headers object with the specified header removed
+ */
+ public Headers removeHeader(String key) {
+ Objects.requireNonNull(key, "key");
+
+ String canonKey = canonicalKey(key);
+ if (!headers.containsKey(canonKey)) {
+ return this;
+ }
+
+ Map> nm = new HashMap<>(headers);
+ nm.remove(canonKey);
+ return new Headers(Collections.unmodifiableMap(nm));
+
+ }
+
+ /**
+ * Returns the header matching the specified key. This matches headers in a case-insensitive way and substitutes
+ * underscore and hyphen characters such that : "CONTENT_TYPE_HEADER" and "Content-type" are equivalent. If no matching
+ * header is found then {@code Optional.empty} is returned.
+ *
+ * When multiple headers are present then the first value is returned- see { #getAllValues(String key)} to get all values for a header
*
* @param key match key
* @return a header matching key or empty if no header matches.
@@ -74,20 +230,60 @@ public Headers withHeader(String key, String value){
*/
public Optional get(String key) {
Objects.requireNonNull(key, "Key cannot be null");
- return getAll().entrySet().stream()
- .filter((e) -> e.getKey()
- .replaceAll("-", "_")
- .equalsIgnoreCase(key.replaceAll("-", "_")))
- .map(Map.Entry::getValue)
- .findFirst();
+ String canonKey = canonicalKey(key);
+
+ List val = headers.get(canonKey);
+ if (val == null){
+ return Optional.empty();
+ }
+ return Optional.of(val.get(0));
}
/**
- * The function invocation headers passed on the request
+ * Returns a collection of current header keys
*
- * @return a map of Invocation headers.
+ * @return a collection of keys
*/
- public Map getAll() {
+ public Collection keys() {
+ return headers.keySet();
+ }
+
+ /**
+ * Returns the headers as a map
+ *
+ * @return a map of key-values
+ */
+ public Map> asMap() {
return headers;
}
+
+ /**
+ * GetAllValues returns all values for a header or an empty list if the header has no values
+ * @param key the Header key
+ * @return a possibly empty list of values
+ */
+ public List getAllValues(String key) {
+ return headers.getOrDefault(canonicalKey(key), Collections.emptyList());
+ }
+
+ public int hashCode() {
+ return headers.hashCode();
+ }
+
+
+ public boolean equals(Object other) {
+ if (!(other instanceof Headers)) {
+ return false;
+ }
+ if (other == this) {
+ return true;
+ }
+ return headers.equals(((Headers) other).headers);
+ }
+
+ @Override
+ public String toString() {
+ return Objects.toString(headers);
+ }
+
}
diff --git a/api/src/main/java/com/fnproject/fn/api/InputEvent.java b/api/src/main/java/com/fnproject/fn/api/InputEvent.java
index 2ecab8aa..cda6cca3 100644
--- a/api/src/main/java/com/fnproject/fn/api/InputEvent.java
+++ b/api/src/main/java/com/fnproject/fn/api/InputEvent.java
@@ -2,6 +2,7 @@
import java.io.Closeable;
import java.io.InputStream;
+import java.time.Instant;
import java.util.function.Function;
public interface InputEvent extends Closeable {
@@ -17,29 +18,21 @@ public interface InputEvent extends Closeable {
*/
T consumeBody(Function dest);
- /**
- * The application name associated with this function
- *
- * @return an application name
- */
- String getAppName();
- /**
- * @return The route (including preceding slash) of this function call
- */
- String getRoute();
/**
- * @return The full request URL of this function invocation
+ * return the current call ID for this event
+ * @return a call ID
*/
- String getRequestUrl();
+ String getCallID();
+
/**
- * The HTTP method used to invoke this function
+ * The deadline by which this event should be processed - this is information and is intended to help you determine how long you should spend processing your event - if you exceed this deadline Fn will terminate your container.
*
- * @return an UpperCase HTTP method
+ * @return a deadline relative to the current system clock that the event must be processed by
*/
- String getMethod();
+ Instant getDeadline();
/**
@@ -49,11 +42,5 @@ public interface InputEvent extends Closeable {
*/
Headers getHeaders();
- /**
- * The query parameters of the function invocation
- *
- * @return an immutable map of query parameters parsed from the request URL
- */
- QueryParameters getQueryParameters();
}
diff --git a/api/src/main/java/com/fnproject/fn/api/InvocationContext.java b/api/src/main/java/com/fnproject/fn/api/InvocationContext.java
index 3b3395a1..080e3bc6 100644
--- a/api/src/main/java/com/fnproject/fn/api/InvocationContext.java
+++ b/api/src/main/java/com/fnproject/fn/api/InvocationContext.java
@@ -9,6 +9,11 @@
*/
public interface InvocationContext {
+ /**
+ * Returns the {@link RuntimeContext} associated with this invocation context
+ *
+ * @return a runtime context
+ */
RuntimeContext getRuntimeContext();
/**
@@ -19,4 +24,39 @@ public interface InvocationContext {
*/
void addListener(InvocationListener listener);
+
+ /**
+ * Returns the current request headers for the invocation
+ *
+ * @return the headers passed into the function
+ */
+ Headers getRequestHeaders();
+
+ /**
+ * Sets the response content type, this will override the default content type of the output
+ *
+ * @param contentType a mime type for the response
+ */
+ default void setResponseContentType(String contentType) {
+ this.setResponseHeader(OutputEvent.CONTENT_TYPE_HEADER, contentType);
+ }
+
+ /**
+ * Adds a response header to the outbound event
+ *
+ * @param key header key
+ * @param value header value
+ */
+ void addResponseHeader(String key, String value);
+
+ /**
+ * Sets a response header to the outbound event, overriding a previous value.
+ *
+ * Headers set in this way override any headers returned by the function or any middleware on the function
+ *
+ * @param key header key
+ * @param v1 first value to set
+ * @param vs other values to set header to
+ */
+ void setResponseHeader(String key, String v1, String... vs);
}
diff --git a/api/src/main/java/com/fnproject/fn/api/OutputEvent.java b/api/src/main/java/com/fnproject/fn/api/OutputEvent.java
index ef13983c..0b93b68f 100644
--- a/api/src/main/java/com/fnproject/fn/api/OutputEvent.java
+++ b/api/src/main/java/com/fnproject/fn/api/OutputEvent.java
@@ -3,45 +3,82 @@
import java.io.IOException;
import java.io.OutputStream;
+import java.util.Objects;
import java.util.Optional;
/**
* Wrapper for an outgoing fn event
*/
public interface OutputEvent {
+
+ String CONTENT_TYPE_HEADER = "Content-Type";
+
+ /**
+ * The outcome status of this function event
+ * This determines how the platform will reflect this error to the customer and how it will treat the container after an error
+ */
+ enum Status {
+ /**
+ * The event was successfully processed
+ */
+ Success(200),
+ /**
+ * The Function code raised unhandled exception
+ */
+ FunctionError(502),
+ /**
+ * The Function code did not respond within a given timeout
+ */
+ FunctionTimeout(504),
+ /**
+ * An internal error occurred in the FDK
+ */
+ InternalError(500);
+
+ private final int code;
+
+ Status(int code) {
+ this.code = code;
+ }
+
+ public int getCode() {
+ return this.code;
+ }
+
+ }
+
+
/**
- * Report the HTTP status code of this event.
- * For default-format functions, this value is mapped into a success/failure value as follows:
- * status codes in the range [100, 400) are considered successful; anything else is a failure.
+ * Report the outcome status code of this event.
*
- * @return the status code associated with this event
+ * @return the status associated with this event
*/
- int getStatusCode();
+ Status getStatus();
- int SUCCESS = 200;
- int FAILURE = 500;
/**
* Report the boolean success of this event.
* For default-format functions, this is used to map the HTTP status code into a straight success/failure.
+ *
* @return true if the output event results from a successful invocation.
*/
default boolean isSuccess() {
- return 100 <= getStatusCode() && getStatusCode() < 400;
+ return getStatus() == Status.Success;
}
/**
- * The indicative content type of the response.
+ * The content type of the response.
*
- * This will only be used when the function format is HTTP
*
* @return The name of the content type.
*/
- Optional getContentType();
+ default Optional getContentType(){
+ return getHeaders().get(CONTENT_TYPE_HEADER);
+ }
/**
* Any additional {@link Headers} that should be supplied along with the content
- *
+ *
* These are only used when the function format is HTTP
*
* @return the headers to add
@@ -51,51 +88,79 @@ default boolean isSuccess() {
/**
* Write the body of the output to a stream
*
- * @param out an outputstream to emit the body of the event
- * @throws IOException OutputStream exceptions percolate up through this method
+ * @param out an outputstream to emit the body of the event
+ * @throws IOException OutputStream exceptions percolate up through this method
*/
void writeToOutput(OutputStream out) throws IOException;
+ /**
+ * Creates a new output event based on this one with the headers overriding
+ * @param headers the headers use in place of this event
+ * @return a new output event with these set
+ */
+ default OutputEvent withHeaders(Headers headers) {
+ Objects.requireNonNull(headers, "headers");
+
+ OutputEvent a = this;
+ return new OutputEvent() {
+
+ @Override
+ public Status getStatus() {
+ return a.getStatus();
+ }
+
+ @Override
+ public Headers getHeaders() {
+ return headers;
+ }
+
+ @Override
+ public void writeToOutput(OutputStream out) throws IOException {
+ a.writeToOutput(out);
+ }
+ };
+ }
+
/**
* Create an output event from a byte array
*
* @param bytes the byte array to write to the output
- * @param statusCode the status code to report
+ * @param status the status code to report
* @param contentType the content type to present on HTTP responses
* @return a new output event
*/
- static OutputEvent fromBytes(byte[] bytes, int statusCode, String contentType) {
- return fromBytes(bytes, statusCode, contentType, Headers.emptyHeaders());
- }
+ static OutputEvent fromBytes(byte[] bytes, Status status, String contentType) {
+ return fromBytes(bytes, status, contentType, Headers.emptyHeaders());
+ }
/**
* Create an output event from a byte array
*
* @param bytes the byte array to write to the output
- * @param statusCode the HTTP status code of this event
- * @param contentType the content type to present on HTTP responses
+ * @param status the status code of this event
+ * @param contentType the content type to present on HTTP responses or null
* @param headers any additional headers to supply with HTTP responses
* @return a new output event
*/
- static OutputEvent fromBytes(byte[] bytes, int statusCode, String contentType, Headers headers) {
- if (statusCode < 100 || 600 <= statusCode) {
- throw new IllegalArgumentException("Valid status codes must lie in the range [100, 599]");
- }
+ static OutputEvent fromBytes(final byte[] bytes, final Status status, final String contentType, final Headers headers) {
+ Objects.requireNonNull(bytes, "bytes");
+ Objects.requireNonNull(status, "status");
+ Objects.requireNonNull(headers, "headers");
+
+ final Headers newHeaders = contentType== null?Headers.emptyHeaders():headers.setHeader("Content-Type",contentType);
return new OutputEvent() {
@Override
- public int getStatusCode() {
- return statusCode;
+ public Status getStatus() {
+ return status;
}
- @Override
- public Optional getContentType() {
- return Optional.ofNullable(contentType);
- }
@Override
- public Headers getHeaders() { return headers; }
+ public Headers getHeaders() {
+ return newHeaders;
+ }
@Override
public void writeToOutput(OutputStream out) throws IOException {
@@ -104,24 +169,25 @@ public void writeToOutput(OutputStream out) throws IOException {
};
}
- static OutputEvent emptyResult(int statusCode) {
- if (statusCode < 100 || 600 <= statusCode) {
- throw new IllegalArgumentException("Valid status codes must lie in the range [100, 599]");
- }
+ /**
+ * Returns an output event with an empty body and a given status
+ * @param status the status of the event
+ * @return a new output event
+ */
+ static OutputEvent emptyResult(final Status status) {
+ Objects.requireNonNull(status, "status");
+
return new OutputEvent() {
@Override
- public int getStatusCode() {
- return statusCode;
+ public Status getStatus() {
+ return status;
}
@Override
- public Optional getContentType() {
- return Optional.empty();
+ public Headers getHeaders() {
+ return Headers.emptyHeaders();
}
- @Override
- public Headers getHeaders() { return Headers.emptyHeaders(); }
-
@Override
public void writeToOutput(OutputStream out) throws IOException {
diff --git a/api/src/main/java/com/fnproject/fn/api/QueryParameters.java b/api/src/main/java/com/fnproject/fn/api/QueryParameters.java
index 08514054..f0dacd05 100644
--- a/api/src/main/java/com/fnproject/fn/api/QueryParameters.java
+++ b/api/src/main/java/com/fnproject/fn/api/QueryParameters.java
@@ -7,7 +7,7 @@
/**
* Wrapper for query parameters map parsed from the URL of a function invocation.
*/
-public interface QueryParameters {
+public interface QueryParameters {
/**
* Find the first entry for {@code key} if it exists otherwise returns {@code Optional.empty}
*
diff --git a/api/src/main/java/com/fnproject/fn/api/RuntimeContext.java b/api/src/main/java/com/fnproject/fn/api/RuntimeContext.java
index 865a6d10..08466cb7 100644
--- a/api/src/main/java/com/fnproject/fn/api/RuntimeContext.java
+++ b/api/src/main/java/com/fnproject/fn/api/RuntimeContext.java
@@ -13,6 +13,20 @@
* of a function; they will not change between multiple invocations of a hot function.
*/
public interface RuntimeContext {
+
+
+ /**
+ * The application ID of the application associated with this function
+ * @return an application ID
+ */
+ String getAppID();
+
+ /**
+ * THe function ID of the function
+ * @return a function ID
+ */
+ String getFunctionID();
+
/**
* Create an instance of the user specified class on which the target function to invoke is declared.
*
@@ -77,7 +91,7 @@ public interface RuntimeContext {
*
* @param targetMethod The user function method
* @param param The index of the parameter
- * @return a list of configured input coercions to apply to the given parameter
+ * @return a list of configured input coercions to apply to the given parameter
*/
List getInputCoercions(MethodWrapper targetMethod, int param);
@@ -105,7 +119,21 @@ public interface RuntimeContext {
* Set an {@link FunctionInvoker} for this function. The invoker will override
* the built in function invoker, although the cloud threads invoker will still
* have precedence so that cloud threads can be used from functions using custom invokers.
+ *
* @param invoker The {@link FunctionInvoker} to add.
+ * @deprecated this is equivalent to {@link #addInvoker(FunctionInvoker, FunctionInvoker.Phase)} with a phase of {@link FunctionInvoker.Phase#Call}
+ */
+ default void setInvoker(FunctionInvoker invoker) {
+ addInvoker(invoker, FunctionInvoker.Phase.Call);
+ }
+
+
+ /**
+ * Adds an FunctionInvoker handler to the runtime - new FunctionInvokers are added at the head of the specific phase they apply to so ordering may be important
+ *
+ *
+ * @param invoker an invoker to use to handle a given call
+ * @param phase the phase at which to add the invoke
*/
- void setInvoker(FunctionInvoker invoker);
+ void addInvoker(FunctionInvoker invoker, FunctionInvoker.Phase phase);
}
diff --git a/api/src/main/java/com/fnproject/fn/api/RuntimeFeature.java b/api/src/main/java/com/fnproject/fn/api/RuntimeFeature.java
new file mode 100644
index 00000000..43365794
--- /dev/null
+++ b/api/src/main/java/com/fnproject/fn/api/RuntimeFeature.java
@@ -0,0 +1,17 @@
+package com.fnproject.fn.api;
+
+/**
+ * RuntimeFeatures are classes that configure the Fn Runtime prior to startup and can be loaded by annotating the function class with a {@link FnFeature} annotation
+ * Created on 10/09/2018.
+ *
+ * (c) 2018 Oracle Corporation
+ */
+public interface RuntimeFeature {
+
+ /**
+ * Initialize the runtime context for this function
+ *
+ * @param context a runtime context to initalize
+ */
+ void initialize(RuntimeContext context);
+}
diff --git a/api/src/main/java/com/fnproject/fn/api/httpgateway/HTTPGatewayContext.java b/api/src/main/java/com/fnproject/fn/api/httpgateway/HTTPGatewayContext.java
new file mode 100644
index 00000000..d97c0edb
--- /dev/null
+++ b/api/src/main/java/com/fnproject/fn/api/httpgateway/HTTPGatewayContext.java
@@ -0,0 +1,84 @@
+package com.fnproject.fn.api.httpgateway;
+
+import com.fnproject.fn.api.Headers;
+import com.fnproject.fn.api.InvocationContext;
+import com.fnproject.fn.api.QueryParameters;
+
+/**
+ * A context for accessing and setting HTTP Gateway atributes such aas headers and query parameters from a function call
+ *
+ * Created on 19/09/2018.
+ *
+ * (c) 2018 Oracle Corporation
+ */
+public interface HTTPGatewayContext {
+
+ /**
+ * Returns the underlying invocation context behind this HTTP context
+ *
+ * @return an invocation context related to this function
+ */
+ InvocationContext getInvocationContext();
+
+
+ /**
+ * Returns the HTTP headers for the request associated with this function call
+ * If no headers were set this will return an empty headers object
+ *
+ * @return the incoming HTTP headers sent in the gateway request
+ */
+ Headers getHeaders();
+
+
+ /**
+ * Returns the fully qualified request URI that the function was called with, including query parameters
+ *
+ * @return the request URI of the function
+ */
+ String getRequestURL();
+
+
+ /**
+ * Returns the incoming request method for the HTTP
+ *
+ * @return the HTTP method set on this call
+ */
+ String getMethod();
+
+ /**
+ * Returns the query parameters of the request
+ *
+ * @return a query parameters object
+ */
+ QueryParameters getQueryParameters();
+
+
+ /**
+ * Adds a response header to the outbound event
+ *
+ * @param key header key
+ * @param value header value
+ */
+ void addResponseHeader(String key, String value);
+
+ /**
+ * Sets a response header to the outbound event, overriding a previous value.
+ *
+ * Headers set in this way override any headers returned by the function or any middleware on the function
+ *
+ * Setting the "Content-Type" response header also sets this on the underlying Invocation context
+ *
+ * @param key header key
+ * @param v1 first value to set
+ * @param vs other values to set header to
+ */
+ void setResponseHeader(String key, String v1, String... vs);
+
+ /**
+ * Sets the HTTP status code of the response
+ *
+ * @param code an HTTP status code
+ * @throws IllegalArgumentException if the code is < 100 or >l=600
+ */
+ void setStatusCode(int code);
+}
diff --git a/api/src/test/java/com/fnproject/fn/api/HeadersTest.java b/api/src/test/java/com/fnproject/fn/api/HeadersTest.java
new file mode 100644
index 00000000..946ed305
--- /dev/null
+++ b/api/src/test/java/com/fnproject/fn/api/HeadersTest.java
@@ -0,0 +1,32 @@
+package com.fnproject.fn.api;
+
+import org.junit.Test;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+/**
+ * Created on 10/09/2018.
+ *
+ * (c) 2018 Oracle Corporation
+ */
+public class HeadersTest {
+
+ @Test
+ public void shouldCanonicalizeHeaders(){
+ for (String[] v : new String[][] {
+ {"",""},
+ {"a","A"},
+ {"fn-ID-","Fn-Id-"},
+ {"myHeader-VaLue","Myheader-Value"},
+ {" Not a Header "," Not a Header "},
+ {"-","-"},
+ {"--","--"},
+ {"a-","A-"},
+ {"-a","-A"}
+ }){
+ assertThat(Headers.canonicalKey(v[0])).isEqualTo(v[1]);
+ }
+ }
+
+
+}
diff --git a/build-image/docker-build.sh b/build-image/docker-build.sh
deleted file mode 100755
index d5dc37b1..00000000
--- a/build-image/docker-build.sh
+++ /dev/null
@@ -1,12 +0,0 @@
-#!/bin/bash -ex
-
-cd /tmp/staging-repository && python -mSimpleHTTPServer 18080 1>>/tmp/http-logs 2>&1 &
-SRV_PROCESS=$!
-
-if [ -n "$DOCKER_LOCALHOST" ]; then
- REPO_ENV="--build-arg FN_REPO_URL=http://$DOCKER_LOCALHOST:18080"
-fi
-
-docker build $REPO_ENV $*
-
-kill $SRV_PROCESS
diff --git a/build.sh b/build.sh
new file mode 100755
index 00000000..b19ac5a0
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,56 @@
+#!/usr/bin/env bash
+
+set -e
+set -x
+mkdir -p /tmp/staging_repo
+rm -rf /tmp/staging_repo/*
+
+BUILD_VERSION=${FN_FDK_VERSION:-1.0.0-SNAPSHOT}
+export REPOSITORY_LOCATION=${REPOSITORY_LOCATION:-/tmp/staging_repo}
+
+while [ $# -ne 0 ]
+do
+ case "$1" in
+ --build-native-java)
+ BUILD_NATIVE_JAVA=true
+ ;;
+ esac
+ shift
+done
+
+(
+ runtime/src/main/c/rebuild_so.sh
+)
+
+mvn -B deploy -DaltDeploymentRepository=localStagingDir::default::file://${REPOSITORY_LOCATION}
+
+(
+ cd images/build
+ ./docker-build.sh -t fnproject/fn-java-fdk-build:${BUILD_VERSION} .
+)
+
+(
+ cd images/build
+ ./docker-build.sh -f Dockerfile-jdk11 -t fnproject/fn-java-fdk-build:jdk11-${BUILD_VERSION} .
+)
+
+(
+ cd runtime
+ docker build -t fnproject/fn-java-fdk:${BUILD_VERSION} -f ../images/runtime/Dockerfile .
+)
+
+(
+ cd runtime
+ docker build -f ../images/runtime/Dockerfile-jre11 -t fnproject/fn-java-fdk:jre11-${BUILD_VERSION} .
+)
+
+(
+ workdir=$(pwd)/runtime
+ cd images/build-native
+ ./docker-build.sh ${workdir}
+)
+
+(
+ cd images/init-native
+ ./docker-build.sh
+)
\ No newline at end of file
diff --git a/build_in_docker.sh b/build_in_docker.sh
new file mode 100644
index 00000000..c9f5aee9
--- /dev/null
+++ b/build_in_docker.sh
@@ -0,0 +1,2 @@
+#!/usr/bin/env bash
+docker run --rm --name compile -v "$(pwd)":/usr/src/mymaven -w /usr/src/mymaven svenruppert/maven-3.5-jdk-08 mvn clean install
diff --git a/docs/ExtendingDataBinding.md b/docs/ExtendingDataBinding.md
index bc8195c9..26a8a2d2 100644
--- a/docs/ExtendingDataBinding.md
+++ b/docs/ExtendingDataBinding.md
@@ -19,7 +19,7 @@ First of all, let's create a new function project. If you haven't done it alread
```shell
$ fn start &
-$ fn apps create java-app
+$ fn create app java-app
Successfully created app: java-app
```
diff --git a/docs/FAQ.md b/docs/FAQ.md
index 375650b5..2477c4ab 100644
--- a/docs/FAQ.md
+++ b/docs/FAQ.md
@@ -12,11 +12,7 @@ The FDK is comprised of:
- a build-time Docker image for repeatable builds.
### Is the FDK required in order to run Java on Fn?
-No. You can still write Java functions on Fn without using the FDK. However using the FDK will make several things easier for you:
- 1. A curated base image for Java 8 and Java 9 means that you don't have to build and maintain your own image. These images contain optimizations for quick JVM startup times.
- 1. Accessing configuration from Fn is easy through FDK APIs.
- 1. Input and output type coercion reduces the amount of serialization and formatting boilerplate that you have to write.
- 1. A JUnit rule provides a realistic test harness for you to test your function in isolation.
+Yes - The FDK implements the IO/contract with the FN service and is required to receive events from the platform
### What is Fn Flow?
Fn Flow is a [Java API](https://github.com/fnproject/fn-java-fdk/blob/master/docs/FnFlowsUserGuide.md) and [corresponding service](https://github.com/fnproject/flow) that helps you create complex, long-running, fault-tolerant functions using a promises-style asynchronous API. Check out the [Fn Flow docs](https://github.com/fnproject/fn-java-fdk/blob/master/docs/FnFlowsUserGuide.md) for more information.
diff --git a/docs/FnFlowsAdvancedTopics.md b/docs/FnFlowsAdvancedTopics.md
index ac2f75a5..efd40e6d 100644
--- a/docs/FnFlowsAdvancedTopics.md
+++ b/docs/FnFlowsAdvancedTopics.md
@@ -61,6 +61,7 @@ An important consideration is that, if your lambda captures fields from your
function class, then that class must also be Serializable:
```java
+@FnFeature(FlowFeature.class)
public class MyFunction{
private String config = "foo";
@@ -82,6 +83,7 @@ E.g. making `MyFunction` serializable will work as the function instance object
will be captured alongside the lambda:
```java
+@FnFeature(FlowFeature.class)
public class MyFunction implements Serializable{
private String config = "foo";
@@ -104,6 +106,7 @@ prior to passing them, removing the need to make the function class
serializable. For example:
```java
+@FnFeature(FlowFeature.class)
public class MyFunction{
private final Database db; // non-serializable object
private final String config = "foo";
@@ -128,6 +131,7 @@ Alternatively, you can make non-serializable fields `transient` and construct
them on the fly:
```java
+@FnFeature(FlowFeature.class)
public class MyFunction implements Serialiable{
private final transient Database db; // non-serializable object
private final String config = "foo";
@@ -298,6 +302,7 @@ exception.
E.g.:
```java
+@FnFeature(FlowFeature.class)
public class MyFunction{
public static class MyException extends RuntimeException{
public MyException(String message){
diff --git a/docs/FnFlowsUserGuide.md b/docs/FnFlowsUserGuide.md
index 91ed4ca4..7834fe0c 100644
--- a/docs/FnFlowsUserGuide.md
+++ b/docs/FnFlowsUserGuide.md
@@ -60,7 +60,7 @@ $ fn start
Similarly, start the Flows server server and point it at the functions server API URL:
```
-$ DOCKER_LOCALHOST=$(docker inspect --type container -f '{{.NetworkSettings.Gateway}}' functions)
+$ DOCKER_LOCALHOST=$(docker inspect --type container -f '{{.NetworkSettings.Gateway}}' fnserver)
$ docker run --rm \
-p 8081:8081 \
@@ -99,6 +99,19 @@ func.yaml created
```
+### Add the Flow runtime to your function
+
+In your `pom.xml` add a depdendency on `flow-runtime` :
+
+```$ml
+
+ com.fnproject.fn
+ flow-runtime
+ ${fdk.version}
+
+
+```
+
### Create a Flow within your Function
You will create a function that produces the nth prime number and then returns
@@ -117,7 +130,10 @@ package com.example.fn;
import com.fnproject.fn.api.flow.Flow;
import com.fnproject.fn.api.flow.Flows;
+import com.fnproject.fn.runtime.flow.FlowFeature;
+import com.fnproject.fn.api.FnFeature;
+@FnFeature(FlowFeature.class)
public class PrimeFunction {
public String handleRequest(int nth) {
@@ -166,7 +182,7 @@ path: /primes
Create your app and deploy your function:
```
-$ fn apps create flows-example
+$ fn create app flows-example
Successfully created app: flows-example
$ fn deploy --app flows-example
@@ -178,20 +194,27 @@ Configure your function to talk to the local flow service endpoint:
```
$ DOCKER_LOCALHOST=$(docker inspect --type container -f '{{.NetworkSettings.Gateway}}' functions)
-$ fn apps config set flows-example COMPLETER_BASE_URL "http://$DOCKER_LOCALHOST:8081"
+$ fn config app flows-example COMPLETER_BASE_URL "http://$DOCKER_LOCALHOST:8081"
```
### Run your Flow function
-You can now run your function using `fn call` or HTTP and curl:
+You can now run your function using `fn invoke` or HTTP.
```
-$ echo 10 | fn call flows-example /primes
+$ echo 10 | fn invoke flows-example primes
The 10th prime number is 29
```
+To invoke your function via HTTP, you need to know its invocation endpoint (or the function needs to have an HTTP trigger defined).
+
+```
+$ fn inspect fn flows-examples primes
```
-$ curl -XPOST -d "10" http://localhost:8080/r/flows-example/primes
+
+Take note of the `fnproject.io/fn/invokeEndpoint` URL and invoke it (ex. using curl).
+
+$ curl -X POST -d "10" http://localhost:8080/invoke/...
The 10th prime number is 29
```
diff --git a/docs/HTTPGatewayFunctions.md b/docs/HTTPGatewayFunctions.md
new file mode 100644
index 00000000..86afd87a
--- /dev/null
+++ b/docs/HTTPGatewayFunctions.md
@@ -0,0 +1,36 @@
+# Accessing HTTP Information From Functions
+
+Functions can be used to handle events, RPC calls or HTTP requests. When you are writing a function that handles an HTTP request you frequently need access to the HTTP headers of the incoming request or need to set HTTP headers or the status code on the outbound respsonse.
+
+
+In Fn for Java, when your function is being served by an HTTP trigger (or another compatible HTTP gateway) you can get access to both the incoming request headers for your function by adding a 'com.fnproject.fn.api.httpgateway.HTTPGatewayContext' parameter to your function's parameters.
+
+
+ Using this allows you to :
+
+ * Read incoming headers
+ * Access the method and request URL for the trigger
+ * Write outbound headers to the response
+ * Set the status code of the response
+
+
+ For example this function reads a request header the method and request URL, sets an response header and sets the response status code to perform an HTTP redirect.
+
+```java
+package com.fnproject.fn.examples;
+import com.fnproject.fn.api.httpgateway.HTTPGatewayContext;
+
+
+public class RedirectFunction {
+
+ public void redirect(HTTPGatewayContext hctx) {
+ System.err.println("Request URL is:" + hctx.getRequestURL());
+ System.err.println("Trace ID" + hctx.getHeaders().get("My-Trace-ID").orElse("N/A"));
+
+ hctx.setResponseHeader("Location","http://example.com");
+ hctx.setStatusCode(302);
+
+ }
+}
+
+```
diff --git a/docs/TestingFunctions.md b/docs/TestingFunctions.md
index ffabd74d..9127b16e 100644
--- a/docs/TestingFunctions.md
+++ b/docs/TestingFunctions.md
@@ -16,7 +16,7 @@ To import the testing library add the following dependency to your Maven project
com.fnproject.fntesting
- 1.0.0-SNAPSHOT
+ ${fdk.version}test
```
@@ -155,9 +155,33 @@ You can test that this is all handled correctly as follows:
# Testing Fn Flows
-You can use `FnTestingRule` to test [Fn Flows](FnFlowsUserGuide.md) within your functions. If flow stages are started by functions within `thenRun` then the testing rule will execute the stages of those flows locally, returning when all spawned flows are complete.
+You can use `FlowTesting` to test [Fn Flows](FnFlowsUserGuide.md) within your functions. If flow stages are started by functions within `thenRun` then the testing rule will execute the stages of those flows locally, returning when all spawned flows are complete.
-`FnTestingRule` supports mocking the behaviour of Fn functions invoked by the `invokeFunction()` API within flows.
+Start by importing the `flow-testing` library into your functino in `test` scope:
+
+```xml
+
+ com.fnproject.fn
+ flow-testing
+ ${fdk.version}
+ test
+
+```
+
+Then create a `FlowTesting` field in your test class, passing the `FnTesting` rule as a parameter:
+
+```java
+import com.fnproject.fn.testing.FnTestingRule;
+import com.fnproject.fn.testing.flow.FlowTesting;
+
+public class FunctionTest {
+ @Rule
+ public final FnTestingRule testing = FnTestingRule.createDefault();
+
+ private final FlowTesting flowTesting = FlowTesting.create(testing);
+```
+
+`FlowTesting` supports mocking the behaviour of Fn functions invoked by the `invokeFunction()` API within flows.
You can specify that the invocation a function returns a valid value (as a byte array):
@@ -165,7 +189,7 @@ You can specify that the invocation a function returns a valid value (as a byte
@Test
public void callsRemoteFunctionWhichSucceeds() {
- testing.givenFn("example/other-function").withResult("blah".getBytes());
+ flowTesting.givenFn("example/other-function").withResult("blah".getBytes());
// ...
@@ -178,8 +202,8 @@ Or you can specify that the invocation a function will cause a user error or a p
@Test
public void callsRemoteFunctionWhichCausesAnError() {
- testing.givenFn("example/other-function").withFunctionError();
- testing.givenFn("example/other-function-2").withPlatformError();
+ flowTesting.givenFn("example/other-function").withFunctionError();
+ flowTesting.givenFn("example/other-function-2").withPlatformError();
// ...
@@ -196,7 +220,7 @@ used to check some behavior:
@Test
public void callsRemoteFunction() {
- testing.givenFn("example/other-function").withAction( (data) -> { called.set(true); return data; } );
+ flowTesting.givenFn("example/other-function").withAction( (data) -> { called.set(true); return data; } );
called.set(false);
@@ -221,7 +245,7 @@ If you need to share objects or static data between your test classes and your f
```java
testing.addSharedClass(MyClassWithStaticState.class); // Shares only the specific class
testing.addSharedPrefix("com.example.MyClassWithStaticState"); // Shares the class and anything under it
- testing.addSharedPrefix("com.example.mysubpackage."); // Shares anyhting under a package
+ testing.addSharedPrefix("com.example.mysubpackage."); // Shares anything under a package
```
While it is possible, it is not generally correct to share the function class itself with the test Class Loader - doing so may result in unexpected (not representative of the real fn platform) initialisation of static fields on the class. With Flows sharing the test class may also result in concurrent access to static data (via `@FnConfiguration` methods).
\ No newline at end of file
diff --git a/examples/README.md b/examples/README.md
index 4995a246..0514760b 100644
--- a/examples/README.md
+++ b/examples/README.md
@@ -1,39 +1,39 @@
-# `fn` Java FDK Example Projects
+# Fn Java FDK Example Projects
In this directory you will find some example projects demonstrating different
-features of the `fn` Java FDK:
+features of the Fn Java FDK:
* Plain java code support (`string-reverse`)
* Functional testing of your functions (`regex-query` and `qr-code`)
* Built-in JSON coercion (`regex-query`)
* [InputEvent and OutputEvent](/docs/DataBinding.md) handling (`qr-code`)
-## 1. String reverse
+## (1) [String reverse](string-reverse/README.md)
This function takes a string and returns the reverse of the string.
-The `fn` Java FDK runtime will handle marshalling data into your
-functions without the function having to have any knowledge of the FDK API.
+The Fn Java FDK handles marshalling data into your
+functions without the function having any knowledge of the FDK API.
-## 2. Regex query
+## (2) Regex query
This function takes a JSON object containing a `text` field and a `regex`
field and return a JSON object with a list of matches in the `matches`
field. It demonstrates the builtin JSON support of the fn Java
-wrapper (provided through Jackson) and how the platform handles serialisation
+wrapper (provided through Jackson) and how the platform handles serialization
of POJO return values.
-## 3. QR Code gen
+## (3) QR Code gen
This function parses the query parameters of a GET request (through the
`InputEvent` passed into the function) to generate a QR code. It demonstrates
the `InputEvent` and `OutputEvent` interfaces which provide low level
access to data entering the `fn` Java FDK.
-## 4. Asynchronous thumbnails generation
+## (4) Asynchronous thumbnails generation
This example showcases the Fn Flow asynchronous execution API, by
creating a workflow that takes an image and asynchronously generates three
thumbnails for it, then uploads them to an object storage.
-## 5. Gradle build
+## (5) Gradle build
This shows how to use Gradle to build functions using the Java FDK.
diff --git a/examples/async-thumbnails/README.md b/examples/async-thumbnails/README.md
index 10616498..b3cff27d 100644
--- a/examples/async-thumbnails/README.md
+++ b/examples/async-thumbnails/README.md
@@ -32,9 +32,8 @@ this example. Run:
```
This will start a local functions service, a local flow completion
-service, and will set up a `myapp` application and three routes: `/resize128`,
-`/resize256` and `/resize512`. The routes are implemented as Fn functions
-which just invoke `imagemagick` to convert the images to the specified sizes.
+service, and will set up a `myapp` application and three functions: `resize128`,
+`resize256` and `resize512`. These functions just invoke `imagemagick` to convert the images to the specified sizes.
The setup script also starts a docker container with an object storage daemon
based on `minio` (with access key `alpha` and secret key `betabetabetabeta`).
@@ -48,14 +47,9 @@ docker container, so that you can verify when the thumbnails are uploaded.
Build the function locally:
```bash
-$ fn build
+$ fn deploy --local --app myapp
```
-Create a route to host the function:
-
-```bash
-$ fn routes create myapp /async-thumbnails
-```
Configure the app. In order to do this you must determine the IP address of the
storage server docker container:
@@ -68,18 +62,18 @@ $ docker inspect --type container -f '{{range .NetworkSettings.Networks}}{{.IPAd
and then use it as the storage host:
```bash
-$ fn routes config set myapp /async-thumbnails OBJECT_STORAGE_URL http://172.17.0.4:9000
+$ fn config app myapp OBJECT_STORAGE_URL http://172.17.0.4:9000
myapp /async-thumbnails updated OBJECT_STORAGE_URL with http://172.17.0.4:9000
-$ fn routes config set myapp /async-thumbnails OBJECT_STORAGE_ACCESS alpha
+$ fn config app myapp OBJECT_STORAGE_ACCESS alpha
myapp /async-thumbnails updated OBJECT_STORAGE_ACCESS with alpha
-$ fn routes config set myapp /async-thumbnails OBJECT_STORAGE_SECRET betabetabetabeta
+$ fn config app myapp OBJECT_STORAGE_SECRET betabetabetabeta
myapp /async-thumbnails updated OBJECT_STORAGE_SECRET with betabetabetabeta
```
Invoke the function by passing the provided test image:
```bash
-$ curl -X POST --data-binary @test-image.png -H "Content-type: application/octet-stream" "http://localhost:8080/r/myapp/async-thumbnails"
+$ curl -X POST --data-binary @test-image.png -H "Content-type: application/octet-stream" "http://localhost:8080/t/myapp/async-thumbnails"
{"imageId":"bd74fff4-0388-4c6f-82f2-8cde9ba9b6fc"}
```
@@ -116,6 +110,13 @@ public class ThumbnailsFunction {
.orElseThrow(() -> new RuntimeException("Missing configuration: OBJECT_STORAGE_ACCESS"));
storageSecretKey = ctx.getConfigurationByKey("OBJECT_STORAGE_SECRET")
.orElseThrow(() -> new RuntimeException("Missing configuration: OBJECT_STORAGE_SECRET"));
+
+ resize128ID = ctx.getConfigurationByKey("RESIZE_128_FN_ID")
+ .orElseThrow(() -> new RuntimeException("Missing configuration: RESIZE_128_FN_ID"));
+ resize256ID = ctx.getConfigurationByKey("RESIZE_256_FN_ID")
+ .orElseThrow(() -> new RuntimeException("Missing configuration: RESIZE_256_FN_ID"));
+ resize512ID = ctx.getConfigurationByKey("RESIZE_512_FN_ID")
+ .orElseThrow(() -> new RuntimeException("Missing configuration: RESIZE_512_FN_ID"));
}
// ...
@@ -155,11 +156,11 @@ public class ThumbnailsFunction {
Flow runtime = Flows.currentFlow();
runtime.allOf(
- runtime.invokeFunction("myapp/resize128", HttpMethod.POST, Headers.emptyHeaders(), imageBuffer)
+ runtime.invokeFunction(resize128ID, HttpMethod.POST, Headers.emptyHeaders(), imageBuffer)
.thenAccept((img) -> objectUpload(img.getBodyAsBytes(), id + "-128.png")),
- runtime.invokeFunction("myapp/resize256", HttpMethod.POST, Headers.emptyHeaders(), imageBuffer)
+ runtime.invokeFunction(resize256ID, HttpMethod.POST, Headers.emptyHeaders(), imageBuffer)
.thenAccept((img) -> objectUpload(img.getBodyAsBytes(), id + "-256.png")),
- runtime.invokeFunction("myapp/resize512", HttpMethod.POST, Headers.emptyHeaders(), imageBuffer)
+ runtime.invokeFunction(resize512ID, HttpMethod.POST, Headers.emptyHeaders(), imageBuffer)
.thenAccept((img) -> objectUpload(img.getBodyAsBytes(), id + "-512.png")),
runtime.supply(() -> objectUpload(imageBuffer, id + ".png"))
);
@@ -218,8 +219,9 @@ in [Testing Functions](../../docs/TestingFunctions.md).
```java
public class ThumbnailsFunctionTest {
- @Rule
- public final FnTestingRule testing = FnTestingRule.createDefault();
+ @Rule
+ public final FnTestingRule fn = FnTestingRule.createDefault();
+ private final FlowTesting flow = FlowTesting.create(fn);
// ...
}
@@ -259,20 +261,22 @@ public class ThumbnailsFunctionTest {
@Test
public void testThumbnail() {
- testing
- .setConfig("OBJECT_STORAGE_URL", "http://localhost:" + mockServer.port())
- .setConfig("OBJECT_STORAGE_ACCESS", "alpha")
- .setConfig("OBJECT_STORAGE_SECRET", "betabetabetabeta")
+ fn.setConfig("OBJECT_STORAGE_URL", "http://localhost:" + mockServer.port())
+ .setConfig("OBJECT_STORAGE_ACCESS", "alpha")
+ .setConfig("OBJECT_STORAGE_SECRET", "betabetabetabeta")
+ .setConfig("RESIZE_128_FN_ID","myapp/resize128")
+ .setConfig("RESIZE_256_FN_ID","myapp/resize256")
+ .setConfig("RESIZE_512_FN_ID","myapp/resize512");
- .givenFn("myapp/resize128")
+ flow.givenFn("myapp/resize128")
.withAction((data) -> "128".getBytes())
.givenFn("myapp/resize256")
.withAction((data) -> "256".getBytes())
.givenFn("myapp/resize512")
.withAction((data) -> "512".getBytes())
- .givenEvent()
+ fn.givenEvent()
.withBody("testing".getBytes())
.enqueue();
@@ -301,21 +305,23 @@ public class ThumbnailsFunctionTest {
@Test
public void anExternalFunctionFailure() {
- testing
- .setConfig("OBJECT_STORAGE_URL", "http://localhost:" + mockServer.port())
- .setConfig("OBJECT_STORAGE_ACCESS", "alpha")
- .setConfig("OBJECT_STORAGE_SECRET", "betabetabetabeta")
-
- .givenFn("myapp/resize128")
- .withResult("128".getBytes())
- .givenFn("myapp/resize256")
- .withResult("256".getBytes())
- .givenFn("myapp/resize512")
- .withFunctionError()
-
- .givenEvent()
- .withBody("testing".getBytes())
- .enqueue();
+ fn.setConfig("OBJECT_STORAGE_URL", "http://localhost:" + mockServer.port())
+ .setConfig("OBJECT_STORAGE_ACCESS", "alpha")
+ .setConfig("OBJECT_STORAGE_SECRET", "betabetabetabeta")
+ .setConfig("RESIZE_128_FN_ID","myapp/resize128")
+ .setConfig("RESIZE_256_FN_ID","myapp/resize256")
+ .setConfig("RESIZE_512_FN_ID","myapp/resize512");
+
+ flow.givenFn("myapp/resize128")
+ .withResult("128".getBytes())
+ .givenFn("myapp/resize256")
+ .withResult("256".getBytes())
+ .givenFn("myapp/resize512")
+ .withFunctionError();
+
+ fn.givenEvent()
+ .withBody("testing".getBytes())
+ .enqueue();
// Mock the http endpoint
mockMinio();
diff --git a/examples/async-thumbnails/func.yaml b/examples/async-thumbnails/func.yaml
index d6003dc4..7bd97826 100644
--- a/examples/async-thumbnails/func.yaml
+++ b/examples/async-thumbnails/func.yaml
@@ -1,7 +1,11 @@
-name: fn-example/async-thumbnails
-version: 0.0.1
+schema_version: 20180708
+name: async-thumbnails
+version: 0.0.8
runtime: java
cmd: com.fnproject.fn.examples.ThumbnailsFunction::handleRequest
-path: /async-thumbnails
-format: http
-timeout: 30
+format: http-stream
+timeout: 120
+triggers:
+- name: async-thumbnails
+ type: http
+ source: /async-thumbnails
diff --git a/examples/async-thumbnails/pom.xml b/examples/async-thumbnails/pom.xml
index e554bee8..c65684ac 100644
--- a/examples/async-thumbnails/pom.xml
+++ b/examples/async-thumbnails/pom.xml
@@ -6,8 +6,11 @@
UTF-8
- 1.0.0-SNAPSHOT
+ UTF-8
+
+ 1.0.0-SNAPSHOT2.8.47
+ 2.9.10com.fnproject.fn.examples
@@ -15,25 +18,48 @@
1.0.0-SNAPSHOT
+
com.fnproject.fnapi
- ${fnproject.version}
+ ${fdk.version}
+
+
+ com.fnproject.fn
+ flow-runtime
+ ${fdk.version}commons-netcommons-net3.6
+
+ com.fasterxml.jackson.core
+ jackson-databind
+ ${jackson.version}
+ io.miniominio
- 3.0.5
+ 5.0.1
+
+
+ com.fnproject.fn
+ testing-core
+ ${fdk.version}
+ test
+
+
+ com.fnproject.fn
+ flow-testing
+ ${fdk.version}
+ testcom.fnproject.fn
- testing
- ${fnproject.version}
+ testing-junit4
+ ${fdk.version}test
@@ -46,7 +72,7 @@
com.github.tomakehurstwiremock
- 2.7.1
+ 2.19.0
@@ -55,7 +81,7 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.3
+ 3.8.01.8
diff --git a/examples/async-thumbnails/run.sh b/examples/async-thumbnails/run.sh
index 76315c0f..68bd6574 100755
--- a/examples/async-thumbnails/run.sh
+++ b/examples/async-thumbnails/run.sh
@@ -1,15 +1,11 @@
#!/bin/bash
+set -e
-fn build
+fn --verbose deploy --app myapp --local
-fn routes create myapp /async-thumbnails
-STORAGE_SERVER_IP=`docker inspect --type container -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' example-storage-server`
-fn routes config set myapp /async-thumbnails OBJECT_STORAGE_URL http://${STORAGE_SERVER_IP}:9000
-fn routes config set myapp /async-thumbnails OBJECT_STORAGE_ACCESS alpha
-fn routes config set myapp /async-thumbnails OBJECT_STORAGE_SECRET betabetabetabeta
-
-curl -X POST --data-binary @test-image.png -H "Content-type: application/octet-stream" "http://localhost:8080/r/myapp/async-thumbnails"
+echo "Calling function"
+curl -v -X POST --data-binary @test-image.png -H "Content-type: application/octet-stream" "http://localhost:8080/t/myapp/async-thumbnails"
echo "Contents of bucket"
mc ls -r example-storage-server
diff --git a/examples/async-thumbnails/setup/resize128/func.yaml b/examples/async-thumbnails/setup/resize128/func.yaml
index b7a5071b..27013369 100644
--- a/examples/async-thumbnails/setup/resize128/func.yaml
+++ b/examples/async-thumbnails/setup/resize128/func.yaml
@@ -1,4 +1,5 @@
-name: example/resize128
-version: 0.0.1
+schema_version: 20180708
+name: resize128
+version: 0.0.5
entrypoint: convert - -resize 128x128 -
-path: /resize128
+format: default
diff --git a/examples/async-thumbnails/setup/resize256/func.yaml b/examples/async-thumbnails/setup/resize256/func.yaml
index 9261f2f6..cd5b4032 100644
--- a/examples/async-thumbnails/setup/resize256/func.yaml
+++ b/examples/async-thumbnails/setup/resize256/func.yaml
@@ -1,4 +1,5 @@
-name: example/resize256
-version: 0.0.1
+schema_version: 20180708
+name: resize256
+version: 0.0.5
entrypoint: convert - -resize 256x256 -
-path: /resize256
+format: default
diff --git a/examples/async-thumbnails/setup/resize512/func.yaml b/examples/async-thumbnails/setup/resize512/func.yaml
index 8ee1d02f..82d9a844 100644
--- a/examples/async-thumbnails/setup/resize512/func.yaml
+++ b/examples/async-thumbnails/setup/resize512/func.yaml
@@ -1,4 +1,5 @@
-name: example/resize512
-version: 0.0.1
+schema_version: 20180708
+name: resize512
+version: 0.0.8
entrypoint: convert - -resize 512x512 -
-path: /resize512
+format: default
diff --git a/examples/async-thumbnails/setup/setup.sh b/examples/async-thumbnails/setup/setup.sh
index 501c0e60..96c3e3a4 100755
--- a/examples/async-thumbnails/setup/setup.sh
+++ b/examples/async-thumbnails/setup/setup.sh
@@ -51,26 +51,21 @@ fi
STORAGE_SERVER_IP=`docker inspect --type container -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' example-storage-server`
# Start functions server if not there
-if [[ -z `docker ps | grep "functions"` ]]; then
- docker run -d --name functions \
- -e NO_PROXY="$STORAGE_SERVER_IP:$NO_PROXY" \
- -p 8080:8080 \
- -v /var/run/docker.sock:/var/run/docker.sock \
- "$FUNCTIONS_IMAGE"
- # Give it time to start up
+if [[ -z `docker ps | grep "fnserver"` ]]; then
+ fn start -d
sleep 3
else
echo "Functions server is already up."
fi
# Get its IP
-FUNCTIONS_SERVER_IP=`docker inspect --type container -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' functions`
+FUNCTIONS_SERVER_IP=`docker inspect --type container -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' fnserver`
# Start flow service if not there
if [[ -z `docker ps | grep "flow-service"` ]]; then
docker run -d --name flow-service \
-e LOG_LEVEL=debug \
-e NO_PROXY="$FUNCTIONS_SERVER_IP:$NO_PROXY" \
- -e API_URL=http://$FUNCTIONS_SERVER_IP:8080/r \
+ -e API_URL=http://$FUNCTIONS_SERVER_IP:8080/invoke \
-p 8081:8081 \
"$COMPLETER_IMAGE"
# Give it time to start up
@@ -82,46 +77,41 @@ fi
COMPLETER_SERVER_IP=`docker inspect --type container -f '{{range .NetworkSettings.Networks}}{{.IPAddress}}{{end}}' flow-service`
# Create app and routes
-if [[ `fn apps list` == *"myapp"* ]]; then
+if [[ `fn list apps` == *"myapp"* ]]; then
echo "App myapp is already there."
else
- fn apps create myapp
- fn apps config set myapp COMPLETER_BASE_URL http://10.167.103.193:8081
+ fn create app myapp
fi
-if [[ `fn routes list myapp` == *"/resize128"* ]]; then
- echo "Route /resize128 is already there."
-else
- # This works around proxy issues
- cd $SCRIPT_DIR/resize128 && \
- docker build -t example/resize128:0.0.1 \
- --build-arg http_proxy=$http_proxy \
- --build-arg https_proxy=$https_proxy \
- . && \
- fn routes create myapp /resize128
-fi
-if [[ `fn routes list myapp` == *"/resize256"* ]]; then
- echo "Route /resize256 is already there."
-else
- # This works around proxy issues
- cd $SCRIPT_DIR/resize256 && \
- docker build -t example/resize256:0.0.1 \
- --build-arg http_proxy=$http_proxy \
- --build-arg https_proxy=$https_proxy \
- . && \
- fn routes create myapp /resize256
-fi
-if [[ `fn routes list myapp` == *"/resize512"* ]]; then
- echo "Route /resize512 is already there."
-else
- # This works around proxy issues
- cd $SCRIPT_DIR/resize512 && \
- docker build -t example/resize512:0.0.1 \
- --build-arg http_proxy=$http_proxy \
- --build-arg https_proxy=$https_proxy \
- . && \
- fn routes create myapp /resize512
-fi
+
+fn config app myapp COMPLETER_BASE_URL http://${COMPLETER_SERVER_IP}:8081
+fn config app myapp OBJECT_STORAGE_URL http://${STORAGE_SERVER_IP}:9000
+fn config app myapp OBJECT_STORAGE_ACCESS alpha
+fn config app myapp OBJECT_STORAGE_SECRET betabetabetabeta
+
+(
+ cd ${SCRIPT_DIR}/resize128
+ fn deploy --app myapp --local
+)
+
+fn config app myapp RESIZE_128_FN_ID $(fn list functions myapp | grep resize128 | awk '{print $3}')
+
+(
+ cd ${SCRIPT_DIR}/resize256
+ fn deploy --app myapp --local
+)
+
+fn config app myapp RESIZE_256_FN_ID $(fn list functions myapp | grep resize256 | awk '{print $3}')
+
+
+(
+ cd ${SCRIPT_DIR}/resize512
+ fn deploy --app myapp --local
+)
+
+fn config app myapp RESIZE_512_FN_ID $(fn list functions myapp | grep resize512 | awk '{print $3}')
+
+
if mc config host list | grep example-storage-server &>/dev/null ; then
diff --git a/examples/async-thumbnails/src/main/java/com/fnproject/fn/examples/ThumbnailsFunction.java b/examples/async-thumbnails/src/main/java/com/fnproject/fn/examples/ThumbnailsFunction.java
index 7ad601de..5f6c1dee 100644
--- a/examples/async-thumbnails/src/main/java/com/fnproject/fn/examples/ThumbnailsFunction.java
+++ b/examples/async-thumbnails/src/main/java/com/fnproject/fn/examples/ThumbnailsFunction.java
@@ -1,8 +1,10 @@
package com.fnproject.fn.examples;
+import com.fnproject.fn.api.FnFeature;
import com.fnproject.fn.api.Headers;
import com.fnproject.fn.api.RuntimeContext;
import com.fnproject.fn.api.flow.Flow;
+import com.fnproject.fn.runtime.flow.FlowFeature;
import com.fnproject.fn.api.flow.Flows;
import com.fnproject.fn.api.flow.HttpMethod;
import io.minio.MinioClient;
@@ -10,12 +12,17 @@
import java.io.ByteArrayInputStream;
import java.io.Serializable;
+@FnFeature(FlowFeature.class)
public class ThumbnailsFunction implements Serializable {
private final String storageUrl;
private final String storageAccessKey;
private final String storageSecretKey;
+ private final String resize128ID;
+ private final String resize256ID;
+ private final String resize512ID;
+
public ThumbnailsFunction(RuntimeContext ctx) {
storageUrl = ctx.getConfigurationByKey("OBJECT_STORAGE_URL")
.orElseThrow(() -> new RuntimeException("Missing configuration: OBJECT_STORAGE_URL"));
@@ -23,6 +30,14 @@ public ThumbnailsFunction(RuntimeContext ctx) {
.orElseThrow(() -> new RuntimeException("Missing configuration: OBJECT_STORAGE_ACCESS"));
storageSecretKey = ctx.getConfigurationByKey("OBJECT_STORAGE_SECRET")
.orElseThrow(() -> new RuntimeException("Missing configuration: OBJECT_STORAGE_SECRET"));
+
+ resize128ID = ctx.getConfigurationByKey("RESIZE_128_FN_ID")
+ .orElseThrow(() -> new RuntimeException("Missing configuration: RESIZE_128_FN_ID"));
+ resize256ID = ctx.getConfigurationByKey("RESIZE_256_FN_ID")
+ .orElseThrow(() -> new RuntimeException("Missing configuration: RESIZE_256_FN_ID"));
+ resize512ID = ctx.getConfigurationByKey("RESIZE_512_FN_ID")
+ .orElseThrow(() -> new RuntimeException("Missing configuration: RESIZE_512_FN_ID"));
+
}
public class Response {
@@ -35,11 +50,11 @@ public Response handleRequest(byte[] imageBuffer) {
Flow runtime = Flows.currentFlow();
runtime.allOf(
- runtime.invokeFunction("myapp/resize128", HttpMethod.POST, Headers.emptyHeaders(), imageBuffer)
+ runtime.invokeFunction(resize128ID, HttpMethod.POST, Headers.emptyHeaders(), imageBuffer)
.thenAccept((img) -> objectUpload(img.getBodyAsBytes(), id + "-128.png")),
- runtime.invokeFunction("myapp/resize256", HttpMethod.POST, Headers.emptyHeaders(), imageBuffer)
+ runtime.invokeFunction(resize256ID, HttpMethod.POST, Headers.emptyHeaders(), imageBuffer)
.thenAccept((img) -> objectUpload(img.getBodyAsBytes(), id + "-256.png")),
- runtime.invokeFunction("myapp/resize512", HttpMethod.POST, Headers.emptyHeaders(), imageBuffer)
+ runtime.invokeFunction(resize512ID, HttpMethod.POST, Headers.emptyHeaders(), imageBuffer)
.thenAccept((img) -> objectUpload(img.getBodyAsBytes(), id + "-512.png")),
runtime.supply(() -> objectUpload(imageBuffer, id + ".png"))
);
diff --git a/examples/async-thumbnails/src/test/java/com/fnproject/fn/examples/ThumbnailsFunctionTest.java b/examples/async-thumbnails/src/test/java/com/fnproject/fn/examples/ThumbnailsFunctionTest.java
index 675ca691..8179ea10 100644
--- a/examples/async-thumbnails/src/test/java/com/fnproject/fn/examples/ThumbnailsFunctionTest.java
+++ b/examples/async-thumbnails/src/test/java/com/fnproject/fn/examples/ThumbnailsFunctionTest.java
@@ -1,7 +1,7 @@
package com.fnproject.fn.examples;
-import com.fnproject.fn.examples.ThumbnailsFunction;
import com.fnproject.fn.testing.FnTestingRule;
+import com.fnproject.fn.testing.flow.FlowTesting;
import com.github.tomakehurst.wiremock.client.WireMock;
import com.github.tomakehurst.wiremock.junit.WireMockRule;
import org.junit.Rule;
@@ -12,65 +12,76 @@
public class ThumbnailsFunctionTest {
@Rule
- public final FnTestingRule testing = FnTestingRule.createDefault();
+ public final FnTestingRule fn = FnTestingRule.createDefault();
+ private final FlowTesting flow = FlowTesting.create(fn);
@Rule
public final WireMockRule mockServer = new WireMockRule(0);
@Test
public void testThumbnail() {
- testing
-
- .setConfig("OBJECT_STORAGE_URL", "http://localhost:" + mockServer.port())
- .setConfig("OBJECT_STORAGE_ACCESS", "alpha")
- .setConfig("OBJECT_STORAGE_SECRET", "betabetabetabeta")
-
- .givenFn("myapp/resize128")
- .withAction((data) -> "128".getBytes())
- .givenFn("myapp/resize256")
- .withAction((data) -> "256".getBytes())
- .givenFn("myapp/resize512")
- .withAction((data) -> "512".getBytes())
-
- .givenEvent()
- .withBody("testing".getBytes())
- .enqueue();
+ fn
+ .setConfig("OBJECT_STORAGE_URL", "http://localhost:" + mockServer.port())
+ .setConfig("OBJECT_STORAGE_ACCESS", "alpha")
+ .setConfig("OBJECT_STORAGE_SECRET", "betabetabetabeta")
+ .setConfig("RESIZE_128_FN_ID","myapp/resize128")
+ .setConfig("RESIZE_256_FN_ID","myapp/resize256")
+ .setConfig("RESIZE_512_FN_ID","myapp/resize512");
+
+
+ flow
+ .givenFn("myapp/resize128")
+ .withAction((data) -> "128".getBytes())
+ .givenFn("myapp/resize256")
+ .withAction((data) -> "256".getBytes())
+ .givenFn("myapp/resize512")
+ .withAction((data) -> "512".getBytes());
+
+ fn
+ .givenEvent()
+ .withBody("fn".getBytes())
+ .enqueue();
// Mock the http endpoint
mockMinio();
- testing.thenRun(ThumbnailsFunction.class, "handleRequest");
+ fn.thenRun(ThumbnailsFunction.class, "handleRequest");
// Check the final image uploads were performed
- mockServer.verify(1, putRequestedFor(urlMatching("/alpha/.*\\.png")).withRequestBody(equalTo("testing")));
- mockServer.verify(1, putRequestedFor(urlMatching("/alpha/.*\\.png")).withRequestBody(equalTo("128")));
- mockServer.verify(1, putRequestedFor(urlMatching("/alpha/.*\\.png")).withRequestBody(equalTo("256")));
- mockServer.verify(1, putRequestedFor(urlMatching("/alpha/.*\\.png")).withRequestBody(equalTo("512")));
+ mockServer.verify(putRequestedFor(urlMatching("/alpha/.*\\.png")).withRequestBody(containing("fn")));
+ mockServer.verify(putRequestedFor(urlMatching("/alpha/.*\\.png")).withRequestBody(containing("128")));
+ mockServer.verify(putRequestedFor(urlMatching("/alpha/.*\\.png")).withRequestBody(containing("256")));
+ mockServer.verify(putRequestedFor(urlMatching("/alpha/.*\\.png")).withRequestBody(containing("512")));
mockServer.verify(4, putRequestedFor(urlMatching(".*")));
}
@Test
public void anExternalFunctionFailure() {
- testing
- .setConfig("OBJECT_STORAGE_URL", "http://localhost:" + mockServer.port())
- .setConfig("OBJECT_STORAGE_ACCESS", "alpha")
- .setConfig("OBJECT_STORAGE_SECRET", "betabetabetabeta")
-
- .givenFn("myapp/resize128")
- .withResult("128".getBytes())
- .givenFn("myapp/resize256")
- .withResult("256".getBytes())
- .givenFn("myapp/resize512")
- .withFunctionError()
-
- .givenEvent()
- .withBody("testing".getBytes())
- .enqueue();
+ fn
+ .setConfig("OBJECT_STORAGE_URL", "http://localhost:" + mockServer.port())
+ .setConfig("OBJECT_STORAGE_ACCESS", "alpha")
+ .setConfig("OBJECT_STORAGE_SECRET", "betabetabetabeta")
+ .setConfig("RESIZE_128_FN_ID","myapp/resize128")
+ .setConfig("RESIZE_256_FN_ID","myapp/resize256")
+ .setConfig("RESIZE_512_FN_ID","myapp/resize512");;
+
+ flow
+ .givenFn("myapp/resize128")
+ .withResult("128".getBytes())
+ .givenFn("myapp/resize256")
+ .withResult("256".getBytes())
+ .givenFn("myapp/resize512")
+ .withFunctionError();
+
+ fn
+ .givenEvent()
+ .withBody("fn".getBytes())
+ .enqueue();
// Mock the http endpoint
mockMinio();
- testing.thenRun(ThumbnailsFunction.class, "handleRequest");
+ fn.thenRun(ThumbnailsFunction.class, "handleRequest");
// Confirm that one image upload didn't happen
mockServer.verify(0, putRequestedFor(urlMatching("/alpha/.*\\.png")).withRequestBody(equalTo("512")));
@@ -82,15 +93,15 @@ public void anExternalFunctionFailure() {
private void mockMinio() {
mockServer.stubFor(get(urlMatching("/alpha.*"))
- .willReturn(aResponse().withBody(
- "\n" +
- "\n" +
- " alpha\n" +
- " \n" +
- " 0\n" +
- " 100\n" +
- " false\n" +
- "")));
+ .willReturn(aResponse().withBody(
+ "\n" +
+ "\n" +
+ " alpha\n" +
+ " \n" +
+ " 0\n" +
+ " 100\n" +
+ " false\n" +
+ "")));
mockServer.stubFor(WireMock.head(urlMatching("/alpha.*")).willReturn(aResponse().withStatus(200)));
diff --git a/examples/gradle-build/README.md b/examples/gradle-build/README.md
index 269072c7..3214e0b3 100644
--- a/examples/gradle-build/README.md
+++ b/examples/gradle-build/README.md
@@ -4,11 +4,11 @@ Fn uses Maven by default for builds. This is an example that uses Fn's `docker`
The example consists of a `Dockerfile` that builds the function using gradle and copies the function's dependencies to `build/deps` and a func.yaml that uses that `Dockerfile` to build the function.
-Note that FDK versions are hard-coded in this example, you may need to update them manually to more recent version.
+Note that fdk.versions are hard-coded in this example, you may need to update them manually to more recent version.
Key points:
* [Dockerfile](Dockerfile) - contains the containerised docker build (based on dockerhub library/gradle images) and image build - this includes the gradle invocation
* The `cacheDeps` task in `build.gradle` is invoked within the Dockerfile - The task pulls down dependencies into the container gradle cache to speed up docker builds.
* The `copyDeps` task in `build.gradle` copies the functions compile deps
-* This uses JDK 8 by default - you can change this to Java 9 by changing : `FROM gradle:4.5.1-jdk8 as build-stage` to `FROM gradle:4.5.1-jdk9 as build-stage` and `FROM fnproject/fn-java-fdk:1.0.56` to `FROM fnproject/fn-java-fdk:jdk9-1.0.56`
\ No newline at end of file
+* This uses JDK 8 by default - you can change this to Java 11 by changing : `FROM gradle:4.5.1-jdk8 as build-stage` to `FROM gradle:4.5.1-jre11 as build-stage` and `FROM fnproject/fn-java-fdk:1.0.85` to `FROM fnproject/fn-java-fdk:jre11-1.0.85`
\ No newline at end of file
diff --git a/examples/gradle-build/src/test/java/com/example/fn/HelloFunctionTest.java b/examples/gradle-build/src/test/java/com/example/fn/HelloFunctionTest.java
index e6b7a5e3..bd46e52d 100644
--- a/examples/gradle-build/src/test/java/com/example/fn/HelloFunctionTest.java
+++ b/examples/gradle-build/src/test/java/com/example/fn/HelloFunctionTest.java
@@ -1,8 +1,5 @@
package com.example.fn;
-import com.fnproject.fn.testing.*;
-import org.junit.*;
-
import static org.junit.Assert.*;
public class HelloFunctionTest {
diff --git a/examples/pom.xml b/examples/pom.xml
index 219291f4..c00977ea 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -13,10 +13,6 @@
1.0.0-SNAPSHOT
-
- UTF-8
-
-
string-reverseregex-query
@@ -31,7 +27,6 @@
org.apache.maven.pluginsmaven-deploy-plugin
- 2.8.2true
diff --git a/examples/qr-code/README.md b/examples/qr-code/README.md
index d511c970..eda10787 100644
--- a/examples/qr-code/README.md
+++ b/examples/qr-code/README.md
@@ -31,8 +31,8 @@ $ fn build
Create an app and route to host the function
```bash
-$ fn apps create qr-app
-$ fn routes create qr-app /qr
+$ fn create app qr-app
+$ fn create route qr-app /qr
```
Invoke the function to create a QR code
@@ -54,21 +54,22 @@ of the example function. The body of the function is shown below:
```java
-public OutputEvent create(InputEvent event) throws MalformedURLException, UnsupportedEncodingException {
- String decodedUrl = URLDecoder.decode(event.getRequestUrl(), "utf-8");
- QueryParameters params = getParams(decodedUrl);
- ImageType type = getFormat(params.getFirst("format").orElse("png"));
- String contents = params.getFirst("contents").orElse("");
-
- ByteArrayOutputStream stream = QRCode.from(contents).to(type).stream();
- System.err.println("Generated QR Code for contents: " + contents);
- return OutputEvent.fromBytes(stream.toByteArray(), true, getMimeType(type));
-}
+
+ public byte[] create(HTTPGatewayContext hctx) {
+ ImageType type = getFormat(hctx.getQueryParameters().get("format").orElse(defaultFormat));
+ System.err.println("Default format: " + type.toString());
+ String contents = hctx.getQueryParameters().get("contents").orElseThrow(() -> new RuntimeException("Contents must be provided to the QR code"));
+
+ ByteArrayOutputStream stream = QRCode.from(contents).to(type).stream();
+ System.err.println("Generated QR Code for contents: " + contents);
+
+ hctx.setResponseHeader("Content-Type", getMimeType(type));
+ return stream.toByteArray();
+ }
+
```
-The fn Java FDK facilitates access to the internal events representing the
-invocation of the function, `InputEvent`, and response of the function,
-`OutputEvent`, for more fine grained control of the platform. See
+The fn Java FDK facilitates access to the HTTP context of events triggered from HTTP gateways via `HTTPGatewayContext` , and response of the function as a bye array, for more fine grained control of the platform. See
[Data Binding](/docs/DataBinding.md) for further information on the types
of input that the fn Java FDK provides.
@@ -116,17 +117,17 @@ this to handle invocations of functions and retrieving function results
```java
...
- @Test
- public void textHelloWorld() throws Exception {
- fn.givenEvent()
- .withRequestUrl("http://www.example.com/qr?contents=" + URLEncoder.encode("hello world", "utf-8"))
- .withMethod("GET")
- .enqueue();
- fn.thenRun(QRGen.class, "create");
-
- assertArrayEquals(readTestFile("qr-code-text-hello-world.png"), fn.getOnlyResult().getBodyAsBytes());
- }
-...
+ @Test
+ public void textHelloWorld() throws Exception {
+ String content = "hello world";
+ fn.givenEvent()
+ .withHeader("Fn-Http-Request-Url", "http://www.example.com/qr?contents=hello+world&format=png")
+ .withHeader("Fn-Http-Method","GET")
+ .enqueue();
+ fn.thenRun(QRGen.class, "create");
+
+ assertEquals(content, decode(fn.getOnlyResult().getBodyAsBytes()));
+ }
```
Input events are constructed using `fn.givenEvent()` providing an `FnEventBuilder`
diff --git a/examples/qr-code/pom.xml b/examples/qr-code/pom.xml
index e7941657..f2a60dfc 100644
--- a/examples/qr-code/pom.xml
+++ b/examples/qr-code/pom.xml
@@ -7,7 +7,7 @@
UTF-8
- 1.0.0-SNAPSHOT
+ 1.0.0-SNAPSHOTcom.fnproject.fn.examples
@@ -24,7 +24,7 @@
com.fnproject.fnapi
- ${fnproject.version}
+ ${fdk.version}
@@ -36,7 +36,13 @@
com.fnproject.fntesting
- ${fnproject.version}
+ ${fdk.version}
+ test
+
+
+ com.fnproject.fn
+ testing-junit4
+ ${fdk.version}test
@@ -45,7 +51,7 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.3
+ 3.8.01.8
diff --git a/examples/qr-code/src/main/java/com/fnproject/fn/examples/QRGen.java b/examples/qr-code/src/main/java/com/fnproject/fn/examples/QRGen.java
index a95caa3d..9f233e29 100644
--- a/examples/qr-code/src/main/java/com/fnproject/fn/examples/QRGen.java
+++ b/examples/qr-code/src/main/java/com/fnproject/fn/examples/QRGen.java
@@ -1,13 +1,11 @@
package com.fnproject.fn.examples;
-import com.fnproject.fn.api.InputEvent;
-import com.fnproject.fn.api.OutputEvent;
import com.fnproject.fn.api.RuntimeContext;
+import com.fnproject.fn.api.httpgateway.HTTPGatewayContext;
import net.glxn.qrgen.core.image.ImageType;
import net.glxn.qrgen.javase.QRCode;
-import java.io.*;
-import java.net.MalformedURLException;
+import java.io.ByteArrayOutputStream;
public class QRGen {
private final String defaultFormat;
@@ -16,19 +14,20 @@ public QRGen(RuntimeContext ctx) {
defaultFormat = ctx.getConfigurationByKey("FORMAT").orElse("png");
}
- public OutputEvent create(InputEvent event) throws MalformedURLException, UnsupportedEncodingException {
- ImageType type = getFormat(event.getQueryParameters().get("format").orElse(defaultFormat));
+ public byte[] create(HTTPGatewayContext hctx) {
+ ImageType type = getFormat(hctx.getQueryParameters().get("format").orElse(defaultFormat));
System.err.println("Default format: " + type.toString());
- String contents = event.getQueryParameters().get("contents").orElseThrow(() -> new RuntimeException("Contents must be provided to the QR code"));
+ String contents = hctx.getQueryParameters().get("contents").orElseThrow(() -> new RuntimeException("Contents must be provided to the QR code"));
ByteArrayOutputStream stream = QRCode.from(contents).to(type).stream();
System.err.println("Generated QR Code for contents: " + contents);
- return OutputEvent.fromBytes(stream.toByteArray(), OutputEvent.SUCCESS, getMimeType(type));
+ hctx.setResponseHeader("Content-Type", getMimeType(type));
+ return stream.toByteArray();
}
private ImageType getFormat(String extension) {
- switch(extension.toLowerCase()) {
+ switch (extension.toLowerCase()) {
case "png":
return ImageType.PNG;
case "jpg":
@@ -43,8 +42,8 @@ private ImageType getFormat(String extension) {
}
}
- private String getMimeType(ImageType type) {
- switch(type) {
+ private String getMimeType(ImageType type) {
+ switch (type) {
case JPG:
return "image/jpeg";
case GIF:
@@ -56,5 +55,5 @@ private String getMimeType(ImageType type) {
default:
throw new RuntimeException("Invalid ImageType: " + type);
}
- }
+ }
}
diff --git a/examples/qr-code/src/test/java/com/fnproject/fn/examples/QRGenTest.java b/examples/qr-code/src/test/java/com/fnproject/fn/examples/QRGenTest.java
index d727e941..c23ec6c2 100644
--- a/examples/qr-code/src/test/java/com/fnproject/fn/examples/QRGenTest.java
+++ b/examples/qr-code/src/test/java/com/fnproject/fn/examples/QRGenTest.java
@@ -1,7 +1,10 @@
package com.fnproject.fn.examples;
import com.fnproject.fn.testing.FnTestingRule;
-import com.google.zxing.*;
+import com.google.zxing.BinaryBitmap;
+import com.google.zxing.ChecksumException;
+import com.google.zxing.FormatException;
+import com.google.zxing.NotFoundException;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.QRCodeReader;
@@ -23,11 +26,9 @@ public class QRGenTest {
public void textHelloWorld() throws Exception {
String content = "hello world";
fn.givenEvent()
- .withRequestUrl("http://www.example.com/qr")
- .withQueryParameter("contents", content)
- .withQueryParameter("format", "png")
- .withMethod("GET")
- .enqueue();
+ .withHeader("Fn-Http-Request-Url", "http://www.example.com/qr?contents=hello+world&format=png")
+ .withHeader("Fn-Http-Method","GET")
+ .enqueue();
fn.thenRun(QRGen.class, "create");
assertEquals(content, decode(fn.getOnlyResult().getBodyAsBytes()));
@@ -37,10 +38,9 @@ public void textHelloWorld() throws Exception {
public void phoneNumber() throws Exception {
String telephoneNumber = "tel:0-12345-67890";
fn.givenEvent()
- .withRequestUrl("http://www.example.com/qr")
- .withQueryParameter("contents", telephoneNumber)
- .withMethod("GET")
- .enqueue();
+ .withHeader("Fn-Http-Request-Url", "http://www.example.com/qr?contents=tel:0-12345-67890")
+ .withHeader("Fn-Http-Method","GET")
+ .enqueue();
fn.thenRun(QRGen.class, "create");
assertEquals(telephoneNumber, decode(fn.getOnlyResult().getBodyAsBytes()));
@@ -51,14 +51,14 @@ public void formatConfigurationIsUsedIfNoFormatIsProvided() throws Exception {
String contents = "hello world";
fn.setConfig("FORMAT", "jpg");
fn.givenEvent()
- .withRequestUrl("http://www.example.com/qr")
- .withQueryParameter("contents", contents)
- .withMethod("GET")
- .enqueue();
+ .withHeader("Fn-Http-Request-Url", "http://www.example.com/qr?contents=hello+world")
+ .withHeader("Fn-Http-Method","GET")
+ .enqueue();
fn.thenRun(QRGen.class, "create");
assertEquals(contents, decode(fn.getOnlyResult().getBodyAsBytes()));
}
+
private String decode(byte[] imageBytes) throws IOException, NotFoundException, ChecksumException, FormatException {
BinaryBitmap bitmap = readToBitmap(imageBytes);
return new QRCodeReader().decode(bitmap).getText();
diff --git a/examples/regex-query/README.md b/examples/regex-query/README.md
index a9d67155..f7a21c6e 100644
--- a/examples/regex-query/README.md
+++ b/examples/regex-query/README.md
@@ -33,8 +33,8 @@ $ fn build
Create an app and route to host the function
```bash
-$ fn apps create regex-query
-$ fn routes create regex-query /query
+$ fn create app regex-query
+$ fn create route regex-query /query
```
Invoke the function to perform a regex search
diff --git a/examples/regex-query/pom.xml b/examples/regex-query/pom.xml
index 23961362..d72bc766 100644
--- a/examples/regex-query/pom.xml
+++ b/examples/regex-query/pom.xml
@@ -6,8 +6,10 @@
UTF-8
- 1.0.0-SNAPSHOT
- 2.8.9
+ UTF-8
+
+ 1.0.0-SNAPSHOT
+ 2.9.10com.fnproject.fn.examples
@@ -33,8 +35,14 @@
com.fnproject.fn
- testing
- ${fnproject.version}
+ testing-core
+ ${fdk.version}
+ test
+
+
+ com.fnproject.fn
+ testing-junit4
+ ${fdk.version}test
@@ -50,7 +58,7 @@
org.apache.maven.pluginsmaven-compiler-plugin
- 3.3
+ 3.8.01.8
diff --git a/examples/regex-query/src/test/java/com/fnproject/fn/examples/RegexQueryTests.java b/examples/regex-query/src/test/java/com/fnproject/fn/examples/RegexQueryTests.java
index e5a44626..6ccdce56 100644
--- a/examples/regex-query/src/test/java/com/fnproject/fn/examples/RegexQueryTests.java
+++ b/examples/regex-query/src/test/java/com/fnproject/fn/examples/RegexQueryTests.java
@@ -15,7 +15,6 @@ public void matchingSingleCharacter() throws JSONException {
String text = "a";
String regex = ".";
fn.givenEvent()
- .withMethod("POST")
.withBody(String.format("{\"text\": \"%s\", \"regex\": \"%s\"}", text, regex))
.enqueue();
@@ -34,7 +33,6 @@ public void matchingSingleCharacterMultipleTimes() throws JSONException {
String text = "abc";
String regex = ".";
fn.givenEvent()
- .withMethod("POST")
.withBody(String.format("{\"text\": \"%s\", \"regex\": \"%s\"}", text, regex))
.enqueue();
diff --git a/examples/string-reverse/README.md b/examples/string-reverse/README.md
index 9c52dae4..97eb79a0 100644
--- a/examples/string-reverse/README.md
+++ b/examples/string-reverse/README.md
@@ -1,71 +1,75 @@
-# Example oFunctions Project: String Reverse
+# Example Java Function: String Reverse
-This example provides an HTTP endpoint for reversing strings
+This example provides an HTTP trigger endpoint for reversing strings.
```bash
-$ curl -d "Hello, World!" "http://localhost:8080/r/string-reverse-app/reverse"
-!dlroW ,olleH
+$ curl -d "Hello World" http://localhost:8080/t/string-reverse-app/string-reverse
+dlroW olleH
```
## Demonstrated FDK features
-This example uses **no** features of the fn Java FDK; in fact it doesn't have
-a dependency on the fn Java FDK, it just plain old Java code.
+This example uses **none** of the Fn Java FDK features, in fact it doesn't have
+any dependency on the Fn Java FDK. It is just plain old Java code.
## Step by step
-Ensure you have the functions server running using, this will host your
-function and provide the HTTP endpoints that invoke it:
+Ensure you have the Fn server running to host your
+function and provide the HTTP endpoint that invokes it:
-```bash
+(1) Start the server
+
+```sh
$ fn start
```
-Build the function locally
+(2) Create an app for the function
-```bash
-$ fn build
+```sh
+$ fn create app string-reverse-app
```
-Create an app and route to host the function
+(3) Deploy the function to your app from the `string-reverse` directory.
-```bash
-$ fn apps create string-reverse-app
-$ fn routes create string-reverse-app /reverse
+```sh
+fn deploy --app string-reverse-app --local
+```
+
+(4) Invoke the function and reverse the string.
+
+```sh
+echo "Hello World" | fn invoke string-reverse-app string-reverse
+dlroW olleH
```
-Invoke the function to reverse a string
+(5) Invoke the function using curl and a trigger to reverse a string.
```bash
-$ curl -d "Hello, World!" "http://localhost:8080/r/string-reverse-app/reverse"
-!dlroW ,olleH
+$ curl -d "Hello World" http://localhost:8080/t/string-reverse-app/string-reverse
+dlroW olleH
```
## Code walkthrough
The entrypoint to the function is specified in `func.yaml` in the `cmd` key.
-It is set this to `com.fnproject.fn.examples.StringReverse::reverse`. The whole class
+It is set this to `com.example.fn.StringReverse::reverse`. The whole class
`StringReverse` is shown below:
```java
-package com.fnproject.fn.examples;
+package com.example.fn;
public class StringReverse {
public String reverse(String str) {
- StringBuilder builder = new StringBuilder();
- for (int i = str.length() - 1; i >= 0; i--) {
- builder.append(str.charAt(i));
- }
- return builder.toString();
+ return new StringBuilder(str).reverse().toString();
}
}
```
-As you can see, this is plain java with no references to the fn API. The
-fn Java FDK handles the marshalling of the HTTP body into the `str`
+As you can see, this is plain java with no references to the Fn API. The
+Fn Java FDK handles the marshalling of the HTTP body into the `str`
parameter as well as the marshalling of the returned reversed string into the HTTP
response body (see [Data Binding](/docs/DataBinding.md) for more
information on how marshalling is performed).
diff --git a/examples/string-reverse/func.yaml b/examples/string-reverse/func.yaml
index 197eebe2..4d56d44f 100644
--- a/examples/string-reverse/func.yaml
+++ b/examples/string-reverse/func.yaml
@@ -1,7 +1,11 @@
-name: fn-example/string-reverse
-version: 0.0.1
+schema_version: 20180708
+name: string-reverse
+version: 0.0.2
runtime: java
-timeout: 30
-format: http
-cmd: com.fnproject.fn.examples.StringReverse::reverse
-path: /reverse
+build_image: fnproject/fn-java-fdk-build:jdk11-1.0.87
+run_image: fnproject/fn-java-fdk:jre11-1.0.87
+cmd: com.example.fn.StringReverse::reverse
+triggers:
+- name: string-reverse
+ type: http
+ source: /string-reverse
diff --git a/examples/string-reverse/pom.xml b/examples/string-reverse/pom.xml
index 4c380ea1..916d988d 100644
--- a/examples/string-reverse/pom.xml
+++ b/examples/string-reverse/pom.xml
@@ -3,21 +3,50 @@
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
4.0.0
-
-
UTF-8
+ 1.0.87
-
- com.fnproject.fn.examples
+ com.example.fnstring-reverse
- 1.0.0-SNAPSHOT
+ 1.0.0
+
+
+
+ fn-release-repo
+ https://dl.bintray.com/fnproject/fnproject
+
+ true
+
+
+ false
+
+
+
+
+ com.fnproject.fn
+ api
+ ${fdk.version}
+
+
+ com.fnproject.fn
+ testing-core
+ ${fdk.version}
+ test
+
+
+ com.fnproject.fn
+ testing-junit4
+ ${fdk.version}
+ test
+ junitjunit4.12
+ test
@@ -28,8 +57,8 @@
maven-compiler-plugin3.3
-
- 1.8
+
+ 8
@@ -40,13 +69,14 @@
true
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.22.1
+
+ false
+
+
-
-
-
- fn-maven-releases
- https://dl.bintray.com/fnproject/fnproject
-
-
diff --git a/examples/string-reverse/src/main/java/com/example/fn/StringReverse.java b/examples/string-reverse/src/main/java/com/example/fn/StringReverse.java
new file mode 100644
index 00000000..46d0f5fb
--- /dev/null
+++ b/examples/string-reverse/src/main/java/com/example/fn/StringReverse.java
@@ -0,0 +1,7 @@
+package com.example.fn;
+
+public class StringReverse {
+ public String reverse(String str) {
+ return new StringBuilder(str).reverse().toString();
+ }
+}
diff --git a/examples/string-reverse/src/main/java/com/fnproject/fn/examples/StringReverse.java b/examples/string-reverse/src/main/java/com/fnproject/fn/examples/StringReverse.java
deleted file mode 100644
index e95ce08e..00000000
--- a/examples/string-reverse/src/main/java/com/fnproject/fn/examples/StringReverse.java
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.fnproject.fn.examples;
-
-public class StringReverse {
- public String reverse(String str) {
- StringBuilder builder = new StringBuilder();
- for (int i = str.length() - 1; i >= 0; i--) {
- builder.append(str.charAt(i));
- }
- return builder.toString();
- }
-}
diff --git a/examples/string-reverse/src/test/java/com/fnproject/examples/StringReverseTest.java b/examples/string-reverse/src/test/java/com/example/fn/testing/StringReverseTest.java
similarity index 87%
rename from examples/string-reverse/src/test/java/com/fnproject/examples/StringReverseTest.java
rename to examples/string-reverse/src/test/java/com/example/fn/testing/StringReverseTest.java
index 9f67dd2c..e9b59af0 100644
--- a/examples/string-reverse/src/test/java/com/fnproject/examples/StringReverseTest.java
+++ b/examples/string-reverse/src/test/java/com/example/fn/testing/StringReverseTest.java
@@ -1,6 +1,6 @@
-package com.fnproject.examples;
+package com.example.fn.testing;
-import com.fnproject.fn.examples.StringReverse;
+import com.example.fn.StringReverse;
import org.junit.Test;
import static junit.framework.TestCase.assertEquals;
diff --git a/flow-api/pom.xml b/flow-api/pom.xml
new file mode 100644
index 00000000..b965bdb4
--- /dev/null
+++ b/flow-api/pom.xml
@@ -0,0 +1,61 @@
+
+
+
+ fdk
+ com.fnproject.fn
+ 1.0.0-SNAPSHOT
+
+ 4.0.0
+
+ flow-api
+
+
+ com.fnproject.fn
+ api
+
+
+ junit
+ junit
+ test
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-javadoc-plugin
+
+
+ attach-javadocs
+
+ jar
+
+
+
+
+
+ org.netbeans.tools
+ sigtest-maven-plugin
+
+
+
+ check
+
+
+
+
+ src/main/api/snapshot.sigfile
+ strictcheck
+ com.fnproject.fn.api.flow
+
+
+
+
+
diff --git a/flow-api/src/main/api/snapshot.sigfile b/flow-api/src/main/api/snapshot.sigfile
new file mode 100644
index 00000000..9048991c
--- /dev/null
+++ b/flow-api/src/main/api/snapshot.sigfile
@@ -0,0 +1,343 @@
+#Signature file v4.1
+#Version 1.0.0-SNAPSHOT
+
+CLSS public abstract interface com.fnproject.fn.api.flow.Flow
+innr public final static !enum FlowState
+intf java.io.Serializable
+meth public <%0 extends java.io.Serializable, %1 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> invokeFunction(java.lang.String,{%%1},java.lang.Class<{%%0}>)
+meth public <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture invokeFunction(java.lang.String,{%%0})
+meth public abstract !varargs com.fnproject.fn.api.flow.FlowFuture anyOf(com.fnproject.fn.api.flow.FlowFuture>[])
+meth public abstract !varargs com.fnproject.fn.api.flow.FlowFuture allOf(com.fnproject.fn.api.flow.FlowFuture>[])
+meth public abstract <%0 extends java.io.Serializable, %1 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> invokeFunction(java.lang.String,com.fnproject.fn.api.flow.HttpMethod,com.fnproject.fn.api.Headers,{%%1},java.lang.Class<{%%0}>)
+meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture invokeFunction(java.lang.String,com.fnproject.fn.api.flow.HttpMethod,com.fnproject.fn.api.Headers,{%%0})
+meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> completedValue({%%0})
+meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> createFlowFuture()
+meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> failedFuture(java.lang.Throwable)
+meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> supply(com.fnproject.fn.api.flow.Flows$SerCallable<{%%0}>)
+meth public abstract com.fnproject.fn.api.flow.Flow addTerminationHook(com.fnproject.fn.api.flow.Flows$SerConsumer)
+meth public abstract com.fnproject.fn.api.flow.FlowFuture invokeFunction(java.lang.String,com.fnproject.fn.api.flow.HttpMethod,com.fnproject.fn.api.Headers,byte[])
+meth public abstract com.fnproject.fn.api.flow.FlowFuture delay(long,java.util.concurrent.TimeUnit)
+meth public abstract com.fnproject.fn.api.flow.FlowFuture supply(com.fnproject.fn.api.flow.Flows$SerRunnable)
+meth public com.fnproject.fn.api.flow.FlowFuture invokeFunction(java.lang.String,com.fnproject.fn.api.flow.HttpMethod)
+meth public com.fnproject.fn.api.flow.FlowFuture invokeFunction(java.lang.String,com.fnproject.fn.api.flow.HttpMethod,com.fnproject.fn.api.Headers)
+
+CLSS public final static !enum com.fnproject.fn.api.flow.Flow$FlowState
+ outer com.fnproject.fn.api.flow.Flow
+fld public final static com.fnproject.fn.api.flow.Flow$FlowState CANCELLED
+fld public final static com.fnproject.fn.api.flow.Flow$FlowState FAILED
+fld public final static com.fnproject.fn.api.flow.Flow$FlowState KILLED
+fld public final static com.fnproject.fn.api.flow.Flow$FlowState SUCCEEDED
+fld public final static com.fnproject.fn.api.flow.Flow$FlowState UNKNOWN
+meth public static com.fnproject.fn.api.flow.Flow$FlowState valueOf(java.lang.String)
+meth public static com.fnproject.fn.api.flow.Flow$FlowState[] values()
+supr java.lang.Enum
+
+CLSS public com.fnproject.fn.api.flow.FlowCompletionException
+cons public init(java.lang.String)
+cons public init(java.lang.String,java.lang.Throwable)
+cons public init(java.lang.Throwable)
+supr java.lang.RuntimeException
+
+CLSS public abstract interface com.fnproject.fn.api.flow.FlowFuture<%0 extends java.lang.Object>
+intf java.io.Serializable
+meth public abstract <%0 extends java.lang.Object, %1 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%1}> thenCombine(com.fnproject.fn.api.flow.FlowFuture extends {%%0}>,com.fnproject.fn.api.flow.Flows$SerBiFunction super {com.fnproject.fn.api.flow.FlowFuture%0},? super {%%0},? extends {%%1}>)
+meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture thenAcceptBoth(com.fnproject.fn.api.flow.FlowFuture<{%%0}>,com.fnproject.fn.api.flow.Flows$SerBiConsumer<{com.fnproject.fn.api.flow.FlowFuture%0},{%%0}>)
+meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> applyToEither(com.fnproject.fn.api.flow.FlowFuture extends {com.fnproject.fn.api.flow.FlowFuture%0}>,com.fnproject.fn.api.flow.Flows$SerFunction<{com.fnproject.fn.api.flow.FlowFuture%0},{%%0}>)
+meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> handle(com.fnproject.fn.api.flow.Flows$SerBiFunction super {com.fnproject.fn.api.flow.FlowFuture%0},java.lang.Throwable,? extends {%%0}>)
+meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> thenApply(com.fnproject.fn.api.flow.Flows$SerFunction<{com.fnproject.fn.api.flow.FlowFuture%0},{%%0}>)
+meth public abstract <%0 extends java.lang.Object> com.fnproject.fn.api.flow.FlowFuture<{%%0}> thenCompose(com.fnproject.fn.api.flow.Flows$SerFunction<{com.fnproject.fn.api.flow.FlowFuture%0},com.fnproject.fn.api.flow.FlowFuture<{%%0}>>)
+meth public abstract boolean cancel()
+meth public abstract boolean complete({com.fnproject.fn.api.flow.FlowFuture%0})
+meth public abstract boolean completeExceptionally(java.lang.Throwable)
+meth public abstract com.fnproject.fn.api.flow.FlowFuture acceptEither(com.fnproject.fn.api.flow.FlowFuture extends {com.fnproject.fn.api.flow.FlowFuture%0}>,com.fnproject.fn.api.flow.Flows$SerConsumer<{com.fnproject.fn.api.flow.FlowFuture%0}>)
+meth public abstract com.fnproject.fn.api.flow.FlowFuture thenAccept(com.fnproject.fn.api.flow.Flows$SerConsumer<{com.fnproject.fn.api.flow.FlowFuture%0}>)
+meth public abstract com.fnproject.fn.api.flow.FlowFuture thenRun(com.fnproject.fn.api.flow.Flows$SerRunnable)
+meth public abstract com.fnproject.fn.api.flow.FlowFuture<{com.fnproject.fn.api.flow.FlowFuture%0}> exceptionally(com.fnproject.fn.api.flow.Flows$SerFunction)
+meth public abstract com.fnproject.fn.api.flow.FlowFuture<{com.fnproject.fn.api.flow.FlowFuture%0}> exceptionallyCompose(com.fnproject.fn.api.flow.Flows$SerFunction>)
+meth public abstract com.fnproject.fn.api.flow.FlowFuture<{com.fnproject.fn.api.flow.FlowFuture%0}> whenComplete(com.fnproject.fn.api.flow.Flows$SerBiConsumer<{com.fnproject.fn.api.flow.FlowFuture%0},java.lang.Throwable>)
+meth public abstract {com.fnproject.fn.api.flow.FlowFuture%0} get()
+meth public abstract {com.fnproject.fn.api.flow.FlowFuture%0} get(long,java.util.concurrent.TimeUnit) throws java.util.concurrent.TimeoutException
+meth public abstract {com.fnproject.fn.api.flow.FlowFuture%0} getNow({com.fnproject.fn.api.flow.FlowFuture%0})
+
+CLSS public final com.fnproject.fn.api.flow.Flows
+innr public abstract interface static FlowSource
+innr public abstract interface static SerBiConsumer
+innr public abstract interface static SerBiFunction
+innr public abstract interface static SerCallable
+innr public abstract interface static SerConsumer
+innr public abstract interface static SerFunction
+innr public abstract interface static SerRunnable
+innr public abstract interface static SerSupplier
+meth public static com.fnproject.fn.api.flow.Flow currentFlow()
+meth public static com.fnproject.fn.api.flow.Flows$FlowSource getCurrentFlowSource()
+meth public static void setCurrentFlowSource(com.fnproject.fn.api.flow.Flows$FlowSource)
+supr java.lang.Object
+hfds flowSource
+
+CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$FlowSource
+ outer com.fnproject.fn.api.flow.Flows
+meth public abstract com.fnproject.fn.api.flow.Flow currentFlow()
+
+CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerBiConsumer<%0 extends java.lang.Object, %1 extends java.lang.Object>
+ outer com.fnproject.fn.api.flow.Flows
+ anno 0 java.lang.FunctionalInterface()
+intf java.io.Serializable
+intf java.util.function.BiConsumer<{com.fnproject.fn.api.flow.Flows$SerBiConsumer%0},{com.fnproject.fn.api.flow.Flows$SerBiConsumer%1}>
+
+CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerBiFunction<%0 extends java.lang.Object, %1 extends java.lang.Object, %2 extends java.lang.Object>
+ outer com.fnproject.fn.api.flow.Flows
+ anno 0 java.lang.FunctionalInterface()
+intf java.io.Serializable
+intf java.util.function.BiFunction<{com.fnproject.fn.api.flow.Flows$SerBiFunction%0},{com.fnproject.fn.api.flow.Flows$SerBiFunction%1},{com.fnproject.fn.api.flow.Flows$SerBiFunction%2}>
+
+CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerCallable<%0 extends java.lang.Object>
+ outer com.fnproject.fn.api.flow.Flows
+ anno 0 java.lang.FunctionalInterface()
+intf java.io.Serializable
+intf java.util.concurrent.Callable<{com.fnproject.fn.api.flow.Flows$SerCallable%0}>
+
+CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerConsumer<%0 extends java.lang.Object>
+ outer com.fnproject.fn.api.flow.Flows
+ anno 0 java.lang.FunctionalInterface()
+intf java.io.Serializable
+intf java.util.function.Consumer<{com.fnproject.fn.api.flow.Flows$SerConsumer%0}>
+
+CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerFunction<%0 extends java.lang.Object, %1 extends java.lang.Object>
+ outer com.fnproject.fn.api.flow.Flows
+ anno 0 java.lang.FunctionalInterface()
+intf java.io.Serializable
+intf java.util.function.Function<{com.fnproject.fn.api.flow.Flows$SerFunction%0},{com.fnproject.fn.api.flow.Flows$SerFunction%1}>
+
+CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerRunnable
+ outer com.fnproject.fn.api.flow.Flows
+ anno 0 java.lang.FunctionalInterface()
+intf java.io.Serializable
+intf java.lang.Runnable
+
+CLSS public abstract interface static com.fnproject.fn.api.flow.Flows$SerSupplier<%0 extends java.lang.Object>
+ outer com.fnproject.fn.api.flow.Flows
+ anno 0 java.lang.FunctionalInterface()
+intf java.io.Serializable
+intf java.util.function.Supplier<{com.fnproject.fn.api.flow.Flows$SerSupplier%0}>
+
+CLSS public com.fnproject.fn.api.flow.FunctionInvocationException
+cons public init(com.fnproject.fn.api.flow.HttpResponse)
+meth public com.fnproject.fn.api.flow.HttpResponse getFunctionResponse()
+supr java.lang.RuntimeException
+hfds functionResponse
+
+CLSS public com.fnproject.fn.api.flow.FunctionInvokeFailedException
+cons public init(java.lang.String)
+supr com.fnproject.fn.api.flow.PlatformException
+
+CLSS public com.fnproject.fn.api.flow.FunctionTimeoutException
+cons public init(java.lang.String)
+supr com.fnproject.fn.api.flow.PlatformException
+
+CLSS public final !enum com.fnproject.fn.api.flow.HttpMethod
+fld public final static com.fnproject.fn.api.flow.HttpMethod DELETE
+fld public final static com.fnproject.fn.api.flow.HttpMethod GET
+fld public final static com.fnproject.fn.api.flow.HttpMethod HEAD
+fld public final static com.fnproject.fn.api.flow.HttpMethod OPTIONS
+fld public final static com.fnproject.fn.api.flow.HttpMethod PATCH
+fld public final static com.fnproject.fn.api.flow.HttpMethod POST
+fld public final static com.fnproject.fn.api.flow.HttpMethod PUT
+meth public java.lang.String toString()
+meth public static com.fnproject.fn.api.flow.HttpMethod valueOf(java.lang.String)
+meth public static com.fnproject.fn.api.flow.HttpMethod[] values()
+supr java.lang.Enum
+hfds verb
+
+CLSS public abstract interface com.fnproject.fn.api.flow.HttpRequest
+meth public abstract byte[] getBodyAsBytes()
+meth public abstract com.fnproject.fn.api.Headers getHeaders()
+meth public abstract com.fnproject.fn.api.flow.HttpMethod getMethod()
+
+CLSS public abstract interface com.fnproject.fn.api.flow.HttpResponse
+meth public abstract byte[] getBodyAsBytes()
+meth public abstract com.fnproject.fn.api.Headers getHeaders()
+meth public abstract int getStatusCode()
+
+CLSS public com.fnproject.fn.api.flow.InvalidStageResponseException
+cons public init(java.lang.String)
+supr com.fnproject.fn.api.flow.PlatformException
+
+CLSS public com.fnproject.fn.api.flow.LambdaSerializationException
+cons public init(java.lang.String)
+cons public init(java.lang.String,java.lang.Exception)
+supr com.fnproject.fn.api.flow.FlowCompletionException
+
+CLSS public com.fnproject.fn.api.flow.PlatformException
+cons public init(java.lang.String)
+cons public init(java.lang.String,java.lang.Throwable)
+cons public init(java.lang.Throwable)
+meth public java.lang.Throwable fillInStackTrace()
+supr com.fnproject.fn.api.flow.FlowCompletionException
+
+CLSS public com.fnproject.fn.api.flow.ResultSerializationException
+cons public init(java.lang.String,java.lang.Throwable)
+supr com.fnproject.fn.api.flow.FlowCompletionException
+
+CLSS public com.fnproject.fn.api.flow.StageInvokeFailedException
+cons public init(java.lang.String)
+supr com.fnproject.fn.api.flow.PlatformException
+
+CLSS public com.fnproject.fn.api.flow.StageLostException
+cons public init(java.lang.String)
+supr com.fnproject.fn.api.flow.PlatformException
+
+CLSS public com.fnproject.fn.api.flow.StageTimeoutException
+cons public init(java.lang.String)
+supr com.fnproject.fn.api.flow.PlatformException
+
+CLSS public final com.fnproject.fn.api.flow.WrappedFunctionException
+cons public init(java.lang.Throwable)
+intf java.io.Serializable
+meth public java.lang.Class> getOriginalExceptionType()
+supr java.lang.RuntimeException
+hfds originalExceptionType
+
+CLSS public abstract interface java.io.Serializable
+
+CLSS public abstract interface java.lang.Comparable<%0 extends java.lang.Object>
+meth public abstract int compareTo({java.lang.Comparable%0})
+
+CLSS public abstract java.lang.Enum<%0 extends java.lang.Enum<{java.lang.Enum%0}>>
+cons protected init(java.lang.String,int)
+intf java.io.Serializable
+intf java.lang.Comparable<{java.lang.Enum%0}>
+meth protected final java.lang.Object clone() throws java.lang.CloneNotSupportedException
+meth protected final void finalize()
+meth public final boolean equals(java.lang.Object)
+meth public final int compareTo({java.lang.Enum%0})
+meth public final int hashCode()
+meth public final int ordinal()
+meth public final java.lang.Class<{java.lang.Enum%0}> getDeclaringClass()
+meth public final java.lang.String name()
+meth public java.lang.String toString()
+meth public static <%0 extends java.lang.Enum<{%%0}>> {%%0} valueOf(java.lang.Class<{%%0}>,java.lang.String)
+supr java.lang.Object
+hfds name,ordinal
+
+CLSS public java.lang.Exception
+cons protected init(java.lang.String,java.lang.Throwable,boolean,boolean)
+cons public init()
+cons public init(java.lang.String)
+cons public init(java.lang.String,java.lang.Throwable)
+cons public init(java.lang.Throwable)
+supr java.lang.Throwable
+hfds serialVersionUID
+
+CLSS public abstract interface !annotation java.lang.FunctionalInterface
+ anno 0 java.lang.annotation.Documented()
+ anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
+ anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[TYPE])
+intf java.lang.annotation.Annotation
+
+CLSS public java.lang.Object
+cons public init()
+meth protected java.lang.Object clone() throws java.lang.CloneNotSupportedException
+meth protected void finalize() throws java.lang.Throwable
+meth public boolean equals(java.lang.Object)
+meth public final java.lang.Class> getClass()
+meth public final void notify()
+meth public final void notifyAll()
+meth public final void wait() throws java.lang.InterruptedException
+meth public final void wait(long) throws java.lang.InterruptedException
+meth public final void wait(long,int) throws java.lang.InterruptedException
+meth public int hashCode()
+meth public java.lang.String toString()
+
+CLSS public abstract interface java.lang.Runnable
+ anno 0 java.lang.FunctionalInterface()
+meth public abstract void run()
+
+CLSS public java.lang.RuntimeException
+cons protected init(java.lang.String,java.lang.Throwable,boolean,boolean)
+cons public init()
+cons public init(java.lang.String)
+cons public init(java.lang.String,java.lang.Throwable)
+cons public init(java.lang.Throwable)
+supr java.lang.Exception
+hfds serialVersionUID
+
+CLSS public java.lang.Throwable
+cons protected init(java.lang.String,java.lang.Throwable,boolean,boolean)
+cons public init()
+cons public init(java.lang.String)
+cons public init(java.lang.String,java.lang.Throwable)
+cons public init(java.lang.Throwable)
+intf java.io.Serializable
+meth public final java.lang.Throwable[] getSuppressed()
+meth public final void addSuppressed(java.lang.Throwable)
+meth public java.lang.StackTraceElement[] getStackTrace()
+meth public java.lang.String getLocalizedMessage()
+meth public java.lang.String getMessage()
+meth public java.lang.String toString()
+meth public java.lang.Throwable fillInStackTrace()
+meth public java.lang.Throwable getCause()
+meth public java.lang.Throwable initCause(java.lang.Throwable)
+meth public void printStackTrace()
+meth public void printStackTrace(java.io.PrintStream)
+meth public void printStackTrace(java.io.PrintWriter)
+meth public void setStackTrace(java.lang.StackTraceElement[])
+supr java.lang.Object
+hfds CAUSE_CAPTION,EMPTY_THROWABLE_ARRAY,NULL_CAUSE_MESSAGE,SELF_SUPPRESSION_MESSAGE,SUPPRESSED_CAPTION,SUPPRESSED_SENTINEL,UNASSIGNED_STACK,backtrace,cause,detailMessage,serialVersionUID,stackTrace,suppressedExceptions
+hcls PrintStreamOrWriter,SentinelHolder,WrappedPrintStream,WrappedPrintWriter
+
+CLSS public abstract interface java.lang.annotation.Annotation
+meth public abstract boolean equals(java.lang.Object)
+meth public abstract int hashCode()
+meth public abstract java.lang.Class extends java.lang.annotation.Annotation> annotationType()
+meth public abstract java.lang.String toString()
+
+CLSS public abstract interface !annotation java.lang.annotation.Documented
+ anno 0 java.lang.annotation.Documented()
+ anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
+ anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[ANNOTATION_TYPE])
+intf java.lang.annotation.Annotation
+
+CLSS public abstract interface !annotation java.lang.annotation.Retention
+ anno 0 java.lang.annotation.Documented()
+ anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
+ anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[ANNOTATION_TYPE])
+intf java.lang.annotation.Annotation
+meth public abstract java.lang.annotation.RetentionPolicy value()
+
+CLSS public abstract interface !annotation java.lang.annotation.Target
+ anno 0 java.lang.annotation.Documented()
+ anno 0 java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy value=RUNTIME)
+ anno 0 java.lang.annotation.Target(java.lang.annotation.ElementType[] value=[ANNOTATION_TYPE])
+intf java.lang.annotation.Annotation
+meth public abstract java.lang.annotation.ElementType[] value()
+
+CLSS public abstract interface java.util.concurrent.Callable<%0 extends java.lang.Object>
+ anno 0 java.lang.FunctionalInterface()
+meth public abstract {java.util.concurrent.Callable%0} call() throws java.lang.Exception
+
+CLSS public abstract interface java.util.function.BiConsumer<%0 extends java.lang.Object, %1 extends java.lang.Object>
+ anno 0 java.lang.FunctionalInterface()
+meth public abstract void accept({java.util.function.BiConsumer%0},{java.util.function.BiConsumer%1})
+meth public java.util.function.BiConsumer<{java.util.function.BiConsumer%0},{java.util.function.BiConsumer%1}> andThen(java.util.function.BiConsumer super {java.util.function.BiConsumer%0},? super {java.util.function.BiConsumer%1}>)
+
+CLSS public abstract interface java.util.function.BiFunction<%0 extends java.lang.Object, %1 extends java.lang.Object, %2 extends java.lang.Object>
+ anno 0 java.lang.FunctionalInterface()
+meth public <%0 extends java.lang.Object> java.util.function.BiFunction<{java.util.function.BiFunction%0},{java.util.function.BiFunction%1},{%%0}> andThen(java.util.function.Function super {java.util.function.BiFunction%2},? extends {%%0}>)
+meth public abstract {java.util.function.BiFunction%2} apply({java.util.function.BiFunction%0},{java.util.function.BiFunction%1})
+
+CLSS public abstract interface java.util.function.Consumer<%0 extends java.lang.Object>
+ anno 0 java.lang.FunctionalInterface()
+meth public abstract void accept({java.util.function.Consumer%0})
+meth public java.util.function.Consumer<{java.util.function.Consumer%0}> andThen(java.util.function.Consumer super {java.util.function.Consumer%0}>)
+
+CLSS public abstract interface java.util.function.Function<%0 extends java.lang.Object, %1 extends java.lang.Object>
+ anno 0 java.lang.FunctionalInterface()
+meth public <%0 extends java.lang.Object> java.util.function.Function<{%%0},{java.util.function.Function%1}> compose(java.util.function.Function super {%%0},? extends {java.util.function.Function%0}>)
+meth public <%0 extends java.lang.Object> java.util.function.Function<{java.util.function.Function%0},{%%0}> andThen(java.util.function.Function super {java.util.function.Function%1},? extends {%%0}>)
+meth public abstract {java.util.function.Function%1} apply({java.util.function.Function%0})
+meth public static <%0 extends java.lang.Object> java.util.function.Function<{%%0},{%%0}> identity()
+
+CLSS public abstract interface java.util.function.Supplier<%0 extends java.lang.Object>
+ anno 0 java.lang.FunctionalInterface()
+meth public abstract {java.util.function.Supplier%0} get()
+
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/Flow.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/Flow.java
similarity index 95%
rename from api/src/main/java/com/fnproject/fn/api/flow/Flow.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/Flow.java
index 22ab2ed8..b366e361 100644
--- a/api/src/main/java/com/fnproject/fn/api/flow/Flow.java
+++ b/flow-api/src/main/java/com/fnproject/fn/api/flow/Flow.java
@@ -33,7 +33,7 @@ public interface Flow extends Serializable {
*
* Function IDs should be of the form "APPID/path/in/app" (without leading slash) where APPID may either be a named application or ".", indicating the appID of the current (calling) function.
*
- * @param functionId Function ID of function to invoke - this should have the form APPNAME/FUNCTION_PATH (e.g. "myapp/path/to/function" or "./path/to/function").
+ * @param functionId Function ID of function to invoke - this should be the function ID returned by `fn inspect function appName fnName`
* @param method HTTP method to invoke function
* @param headers Headers to add to the HTTP request representing the function invocation
* @param data input data to function as a byte array -
@@ -45,7 +45,7 @@ public interface Flow extends Serializable {
* Invoke a function by ID with headers and an empty body
*
*
- * @param functionId Function ID of function to invoke - this should have the form APPNAME/FUNCTION_PATH (e.g. "myapp/path/to/function" or "./path/to/function").
+ * @param functionId Function ID of function to invoke - this should be the function ID returned by `fn inspect function appName fnName`
* @param method HTTP method to invoke function
* @param headers Headers to add to the HTTP request representing the function invocation
* @return a future which completes normally if the function succeeded and fails if it fails
@@ -60,7 +60,7 @@ default FlowFuture invokeFunction(String functionId, HttpMethod me
*
* This currently only maps to JSON via the default JSON mapper in the FDK
*
- * @param functionId Function ID of function to invoke - this should have the form APPNAME/FUNCTION_PATH (e.g. "myapp/path/to/function" or "./path/to/function").
+ * @param functionId Function ID of function to invoke - this should be the function ID returned by `fn inspect function appName fnName`
* @param input The input object to send to the function input
* @param responseType The expected response type of the target function
* @param The Response type
@@ -77,7 +77,7 @@ default FlowFuture invokeFunction(String function
*
* This currently only maps to JSON via the default JSON mapper in the FDK
*
- * @param functionId Function ID of function to invoke - this should have the form APPNAME/FUNCTION_PATH (e.g. "myapp/path/to/function" or "./path/to/function").
+ * @param functionId Function ID of function to invoke - this should be the function ID returned by `fn inspect function appName fnName`
* @param method the HTTP method to use for this call
* @param headers additional HTTP headers to pass to this function -
* @param input The input object to send to the function input
@@ -98,7 +98,7 @@ default FlowFuture invokeFunction(String function
*
* This currently only maps to JSON via the default JSON mapper in the FDK
*
- * @param functionId Function ID of function to invoke - this should have the form APPNAME/FUNCTION_PATH (e.g. "myapp/path/to/function" or "./path/to/function").
+ * @param functionId Function ID of function to invoke - this should be the function ID returned by `fn inspect function appName fnName`
* @param input The input object to send to the function input
* @param The Input type of the function
* @return a flow future that completes with the result of the function, or an error if the function invocation failed
@@ -116,7 +116,7 @@ default FlowFuture invokeFunction(String functionId, U input)
*
* This currently only maps to JSON via the default JSON mapper in the FDK
*
- * @param functionId Function ID of function to invoke - this should have the form APPNAME/FUNCTION_PATH (e.g. "myapp/path/to/function" or "./path/to/function").
+ * @param functionId Function ID of function to invoke - this should be the function ID returned by `fn inspect function appName fnName`
* @param method the HTTP method to use for this call
* @param headers additional HTTP headers to pass to this function -
* @param input The input object to send to the function input
@@ -130,7 +130,7 @@ default FlowFuture invokeFunction(String functionId, U input)
* Invoke a function by ID with no headers
*
*
- * @param functionId Function ID of function to invoke - this should have the form APPNAME/FUNCTION_PATH (e.g. "myapp/path/to/function" or "./path/to/function").
+ * @param functionId Function ID of function to invoke - this should be the function ID returned by `fn inspect function appName fnName`
* @param method HTTP method to invoke function
* @return a future which completes normally if the function succeeded and fails if it fails
* @see #invokeFunction(String, HttpMethod, Headers, byte[])
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/FlowCompletionException.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/FlowCompletionException.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/FlowCompletionException.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/FlowCompletionException.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/FlowFuture.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/FlowFuture.java
similarity index 99%
rename from api/src/main/java/com/fnproject/fn/api/flow/FlowFuture.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/FlowFuture.java
index da3d5e60..6dd82fdb 100644
--- a/api/src/main/java/com/fnproject/fn/api/flow/FlowFuture.java
+++ b/flow-api/src/main/java/com/fnproject/fn/api/flow/FlowFuture.java
@@ -1,7 +1,6 @@
package com.fnproject.fn.api.flow;
import java.io.Serializable;
-import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/Flows.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/Flows.java
similarity index 94%
rename from api/src/main/java/com/fnproject/fn/api/flow/Flows.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/Flows.java
index c71b58d4..5845671b 100644
--- a/api/src/main/java/com/fnproject/fn/api/flow/Flows.java
+++ b/flow-api/src/main/java/com/fnproject/fn/api/flow/Flows.java
@@ -1,5 +1,6 @@
package com.fnproject.fn.api.flow;
+
import java.io.Serializable;
import java.util.Objects;
import java.util.concurrent.Callable;
@@ -19,7 +20,7 @@ private Flows() {
*
* @return the current supplier of the flow runtime
*/
- public static FlowSource getCurrentFlowSource() {
+ public static synchronized FlowSource getCurrentFlowSource() {
return flowSource;
}
@@ -37,7 +38,7 @@ public interface FlowSource {
* @return the current flow runtime
*/
public synchronized static Flow currentFlow() {
- Objects.requireNonNull(flowSource, "Flows.flowSource is not set - Flows.currentFlow() should only be called from within a FaaS function invocation");
+ Objects.requireNonNull(flowSource, "Flows.flowSource is not set - Flows.currentFlow() is the @FnFeature(FlowFeature.class) annotation set on your function?");
return flowSource.currentFlow();
}
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/FunctionInvocationException.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/FunctionInvocationException.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/FunctionInvocationException.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/FunctionInvocationException.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/FunctionInvokeFailedException.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/FunctionInvokeFailedException.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/FunctionInvokeFailedException.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/FunctionInvokeFailedException.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/FunctionTimeoutException.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/FunctionTimeoutException.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/FunctionTimeoutException.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/FunctionTimeoutException.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/HttpMethod.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/HttpMethod.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/HttpMethod.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/HttpMethod.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/HttpRequest.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/HttpRequest.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/HttpRequest.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/HttpRequest.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/HttpResponse.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/HttpResponse.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/HttpResponse.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/HttpResponse.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/InvalidStageResponseException.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/InvalidStageResponseException.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/InvalidStageResponseException.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/InvalidStageResponseException.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/LambdaSerializationException.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/LambdaSerializationException.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/LambdaSerializationException.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/LambdaSerializationException.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/PlatformException.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/PlatformException.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/PlatformException.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/PlatformException.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/ResultSerializationException.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/ResultSerializationException.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/ResultSerializationException.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/ResultSerializationException.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/StageInvokeFailedException.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/StageInvokeFailedException.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/StageInvokeFailedException.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/StageInvokeFailedException.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/StageLostException.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/StageLostException.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/StageLostException.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/StageLostException.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/StageTimeoutException.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/StageTimeoutException.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/StageTimeoutException.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/StageTimeoutException.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/WrappedFunctionException.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/WrappedFunctionException.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/WrappedFunctionException.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/WrappedFunctionException.java
diff --git a/api/src/main/java/com/fnproject/fn/api/flow/package-info.java b/flow-api/src/main/java/com/fnproject/fn/api/flow/package-info.java
similarity index 100%
rename from api/src/main/java/com/fnproject/fn/api/flow/package-info.java
rename to flow-api/src/main/java/com/fnproject/fn/api/flow/package-info.java
diff --git a/api/src/test/java/com/fnproject/fn/api/flow/FlowsTest.java b/flow-api/src/test/java/com/fnproject/fn/api/flow/FlowsTest.java
similarity index 99%
rename from api/src/test/java/com/fnproject/fn/api/flow/FlowsTest.java
rename to flow-api/src/test/java/com/fnproject/fn/api/flow/FlowsTest.java
index d8d51d5e..aed5b7eb 100644
--- a/api/src/test/java/com/fnproject/fn/api/flow/FlowsTest.java
+++ b/flow-api/src/test/java/com/fnproject/fn/api/flow/FlowsTest.java
@@ -1,9 +1,11 @@
package com.fnproject.fn.api.flow;
+import org.junit.Test;
+
import java.lang.reflect.Modifier;
+
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertTrue;
-import org.junit.Test;
public class FlowsTest {
public FlowsTest() {
diff --git a/flow-runtime/pom.xml b/flow-runtime/pom.xml
new file mode 100644
index 00000000..532686fe
--- /dev/null
+++ b/flow-runtime/pom.xml
@@ -0,0 +1,76 @@
+
+
+
+ fdk
+ com.fnproject.fn
+ 1.0.0-SNAPSHOT
+
+ 4.0.0
+
+ flow-runtime
+
+
+
+ com.fnproject.fn
+ api
+
+
+ com.fnproject.fn
+ flow-api
+
+
+
+ com.fnproject.fn
+ runtime
+
+
+ com.fnproject.fn
+ testing-junit4
+ test
+
+
+ org.mockito
+ mockito-core
+ test
+
+
+ junit
+ junit
+
+
+ org.assertj
+ assertj-core
+ test
+
+
+ org.apache.httpcomponents
+ httpmime
+ test
+
+
+
+
+
+
+
+ maven-dependency-plugin
+
+
+ copy-dependencies
+ package
+
+ copy-dependencies
+
+
+ ${project.build.directory}/dependency
+ runtime
+ true
+
+
+
+
+
+
+
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/APIModel.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/APIModel.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/APIModel.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/APIModel.java
index eea809a9..c5cb2b1b 100644
--- a/runtime/src/main/java/com/fnproject/fn/runtime/flow/APIModel.java
+++ b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/APIModel.java
@@ -5,8 +5,8 @@
import com.fasterxml.jackson.annotation.JsonTypeInfo;
import com.fasterxml.jackson.annotation.JsonUnwrapped;
import com.fnproject.fn.api.Headers;
-import com.fnproject.fn.api.flow.*;
import com.fnproject.fn.api.exception.FunctionInputHandlingException;
+import com.fnproject.fn.api.flow.*;
import com.fnproject.fn.runtime.exception.PlatformCommunicationException;
import org.apache.commons.io.IOUtils;
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/BlobResponse.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/BlobResponse.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/BlobResponse.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/BlobResponse.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/BlobStoreClient.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/BlobStoreClient.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/BlobStoreClient.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/BlobStoreClient.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/CodeLocation.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/CodeLocation.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/CodeLocation.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/CodeLocation.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/CompleterClient.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/CompleterClient.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/CompleterClient.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/CompleterClient.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/CompleterClientFactory.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/CompleterClientFactory.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/CompleterClientFactory.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/CompleterClientFactory.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/CompletionId.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/CompletionId.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/CompletionId.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/CompletionId.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/DefaultHttpResponse.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/DefaultHttpResponse.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/DefaultHttpResponse.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/DefaultHttpResponse.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/EntityReader.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/EntityReader.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/EntityReader.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/EntityReader.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowContinuationInvoker.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowContinuationInvoker.java
similarity index 93%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowContinuationInvoker.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowContinuationInvoker.java
index 81d12f1a..177b2e89 100644
--- a/runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowContinuationInvoker.java
+++ b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowContinuationInvoker.java
@@ -1,12 +1,11 @@
package com.fnproject.fn.runtime.flow;
import com.fasterxml.jackson.core.JsonProcessingException;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.fnproject.fn.api.*;
+import com.fnproject.fn.api.exception.FunctionInputHandlingException;
import com.fnproject.fn.api.flow.Flow;
import com.fnproject.fn.api.flow.Flows;
import com.fnproject.fn.api.flow.PlatformException;
-import com.fnproject.fn.api.exception.FunctionInputHandlingException;
import com.fnproject.fn.runtime.exception.InternalFunctionInvocationException;
import com.fnproject.fn.runtime.exception.PlatformCommunicationException;
@@ -32,6 +31,10 @@ public final class FlowContinuationInvoker implements FunctionInvoker {
public static final String FLOW_ID_HEADER = "Fnproject-FlowId";
+ FlowContinuationInvoker() {
+
+ }
+
private static class URLCompleterClientFactory implements CompleterClientFactory {
private final String completerBaseUrl;
private transient CompleterClient completerClient;
@@ -45,7 +48,7 @@ private static class URLCompleterClientFactory implements CompleterClientFactory
public synchronized CompleterClient getCompleterClient() {
if (this.completerClient == null) {
this.completerClient = new RemoteFlowApiClient(completerBaseUrl + "/v1",
- getBlobStoreClient(), new HttpClient());
+ getBlobStoreClient(), new HttpClient());
}
return this.completerClient;
}
@@ -135,7 +138,7 @@ public synchronized Flow currentFlow() {
if (matchingDispatchPattern != null) {
if (matchingDispatchPattern.numArguments() != invokeStageRequest.args.size()) {
- throw new FunctionInputHandlingException("Number of arguments provided (" + invokeStageRequest.args.size() + ") in .InvokeStageRequest does not match the number required by the function type (" + matchingDispatchPattern.numArguments() + ")");
+ throw new FunctionInputHandlingException("Number of arguments provided (" + invokeStageRequest.args.size() + ") in .InvokeStageRequest does not match the number required by the function type (" + matchingDispatchPattern.numArguments() + ")");
}
} else {
throw new FunctionInputHandlingException("No functional interface type matches the supplied continuation class");
@@ -168,7 +171,7 @@ public synchronized Flow currentFlow() {
@Override
public synchronized Flow currentFlow() {
if (runtime == null) {
- String functionId = evt.getAppName() + evt.getRoute();
+ String functionId = ctx.getRuntimeContext().getFunctionID();
CompleterClientFactory factory = getOrCreateCompleterClientFactory(completerBaseUrl);
final FlowId flowId = factory.getCompleterClient().createFlow(functionId);
runtime = new RemoteFlow(flowId);
@@ -204,9 +207,9 @@ private OutputEvent invokeContinuation(BlobStoreClient blobStoreClient, FlowId f
APIModel.Datum datum = APIModel.datumFromJava(flowId, ite.getCause(), blobStoreClient);
throw new InternalFunctionInvocationException(
- "Error invoking flows lambda",
- ite.getCause(),
- constructOutputEvent(datum, false)
+ "Error invoking flows lambda",
+ ite.getCause(),
+ constructOutputEvent(datum, false)
);
} catch (Exception ex) {
throw new PlatformException(ex);
@@ -223,27 +226,18 @@ private OutputEvent invokeContinuation(BlobStoreClient blobStoreClient, FlowId f
*/
final static class ContinuationOutputEvent implements OutputEvent {
private final byte[] body;
+ private static final Headers headers = Headers.emptyHeaders().setHeader(OutputEvent.CONTENT_TYPE_HEADER, "application/json");
private ContinuationOutputEvent(boolean success, byte[] body) {
this.body = body;
}
- /**
- * The completer expects a 200 on the output event.
- *
- * @return
- */
@Override
- public int getStatusCode() {
- return OutputEvent.SUCCESS;
+ public Status getStatus() {
+ return Status.Success;
}
- @Override
- public Optional getContentType() {
- return Optional.of("application/json");
- }
-
@Override
public void writeToOutput(OutputStream out) throws IOException {
out.write(body);
@@ -251,7 +245,7 @@ public void writeToOutput(OutputStream out) throws IOException {
@Override
public Headers getHeaders() {
- return Headers.emptyHeaders();
+ return headers;
}
}
diff --git a/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowFeature.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowFeature.java
new file mode 100644
index 00000000..27a79f0b
--- /dev/null
+++ b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowFeature.java
@@ -0,0 +1,38 @@
+package com.fnproject.fn.runtime.flow;
+
+import com.fnproject.fn.api.FunctionInvoker;
+import com.fnproject.fn.api.RuntimeContext;
+import com.fnproject.fn.api.RuntimeFeature;
+
+/**
+ *
+ * The flow feature enables the Flow Client SDK and runtime behaviour in a Java function in order to use Flow in a function you must add the following to the function class:
+ *
+ *
+ *
+ * import com.fnproject.fn.api.FnFeature;
+ * import com.fnproject.fn.runtime.flow.FlowFeature;
+ *
+ * @FnFeature(FlowFeature.class)
+ * public class MyFunction {
+ *
+ *
+ * public void myFunction(String input){
+ * Flows.currentFlow()....
+ *
+ * }
+ * }
+ *
+ *
+ *
+ * Created on 10/09/2018.
+ *
+ * (c) 2018 Oracle Corporation
+ */
+public class FlowFeature implements RuntimeFeature {
+ @Override
+ public void initialize(RuntimeContext context){
+ FunctionInvoker invoker = new FlowContinuationInvoker();
+ context.addInvoker(invoker,FunctionInvoker.Phase.PreCall);
+ }
+}
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowFutureSource.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowFutureSource.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowFutureSource.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowFutureSource.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowId.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowId.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowId.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowId.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowRuntimeGlobals.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowRuntimeGlobals.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowRuntimeGlobals.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/FlowRuntimeGlobals.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/HttpClient.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/HttpClient.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/HttpClient.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/HttpClient.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/JsonInvoke.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/JsonInvoke.java
similarity index 97%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/JsonInvoke.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/JsonInvoke.java
index cfb21953..d6e20272 100644
--- a/runtime/src/main/java/com/fnproject/fn/runtime/flow/JsonInvoke.java
+++ b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/JsonInvoke.java
@@ -73,7 +73,7 @@ public static FlowFuture invokeFunction(Flow flow, String func
String inputString = getObjectMapper().writeValueAsString(input);
Headers newHeaders;
if (!headers.get("Content-type").isPresent()) {
- newHeaders = headers.withHeader("Content-type", "application/json");
+ newHeaders = headers.addHeader("Content-type", "application/json");
} else {
newHeaders = headers;
}
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteBlobStoreClient.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteBlobStoreClient.java
similarity index 97%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteBlobStoreClient.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteBlobStoreClient.java
index 18f0d644..b4274d93 100644
--- a/runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteBlobStoreClient.java
+++ b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteBlobStoreClient.java
@@ -1,6 +1,5 @@
package com.fnproject.fn.runtime.flow;
-import com.fasterxml.jackson.databind.ObjectMapper;
import com.fnproject.fn.runtime.exception.PlatformCommunicationException;
import java.io.IOException;
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteFlow.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteFlow.java
similarity index 100%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteFlow.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteFlow.java
diff --git a/runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteFlowApiClient.java b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteFlowApiClient.java
similarity index 95%
rename from runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteFlowApiClient.java
rename to flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteFlowApiClient.java
index 60c96a09..a60bf452 100644
--- a/runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteFlowApiClient.java
+++ b/flow-runtime/src/main/java/com/fnproject/fn/runtime/flow/RemoteFlowApiClient.java
@@ -120,12 +120,10 @@ public CompletionId invokeFunction(FlowId flowId, String functionId, byte[] data
httpReq.headers = new ArrayList<>();
- headers.getAll().forEach((k, v) -> {
- APIModel.HTTPHeader h = new APIModel.HTTPHeader();
- h.key = k;
- h.value = v;
- httpReq.headers.add(h);
- });
+ headers.asMap().forEach((k, vs) -> vs.forEach(v -> httpReq.headers.add(APIModel.HTTPHeader.create(k, v))));
+
+ Map> headersMap = headers.asMap();
+ headersMap.forEach((key, values) -> values.forEach(value -> httpReq.headers.add(APIModel.HTTPHeader.create(key, value))));
}
httpReq.method = APIModel.HTTPMethod.fromFlow(method);
@@ -153,9 +151,9 @@ public CompletionId completedValue(FlowId flowId, boolean success, Object value,
APIModel.CompletionResult completionResult = new APIModel.CompletionResult();
completionResult.successful = success;
- if(value instanceof RemoteFlow.RemoteFlowFuture) {
+ if (value instanceof RemoteFlow.RemoteFlowFuture) {
APIModel.StageRefDatum stageRefDatum = new APIModel.StageRefDatum();
- stageRefDatum.stageId = ((RemoteFlow.RemoteFlowFuture)value).id();
+ stageRefDatum.stageId = ((RemoteFlow.RemoteFlowFuture) value).id();
completionResult.result = stageRefDatum;
} else {
APIModel.Datum blobDatum = APIModel.datumFromJava(flowId, value, blobStoreClient);
@@ -251,14 +249,14 @@ public Object waitForCompletion(FlowId flowId, CompletionId id, ClassLoader igno
long remainingTimeout = Math.max(1, start + msTimeout - lastStart);
try (HttpClient.HttpResponse response =
- httpClient.execute(prepareGet(apiUrlBase + "/flows/" + flowId.getId() + "/stages/" + id.getId() + "/await?timeout_ms=" + remainingTimeout))) {
+ httpClient.execute(prepareGet(apiUrlBase + "/flows/" + flowId.getId() + "/stages/" + id.getId() + "/await?timeout_ms=" + remainingTimeout))) {
if (response.getStatusCode() == 200) {
APIModel.AwaitStageResponse resp = FlowRuntimeGlobals.getObjectMapper().readValue(response.getContentStream(), APIModel.AwaitStageResponse.class);
if (resp.result.successful) {
return resp.result.toJava(flowId, blobStoreClient, getClass().getClassLoader());
} else {
- throw new FlowCompletionException((Throwable)resp.result.toJava(flowId, blobStoreClient, getClass().getClassLoader()));
+ throw new FlowCompletionException((Throwable) resp.result.toJava(flowId, blobStoreClient, getClass().getClassLoader()));
}
} else if (response.getStatusCode() == 408) {
// do nothing go round again
@@ -313,10 +311,10 @@ private static PlatformCommunicationException asError(HttpClient.HttpResponse re
try {
String body = response.entityAsString();
return new PlatformCommunicationException(String.format("Received unexpected response (%d) from " +
- "Flow service: %s", response.getStatusCode(), body == null ? "Empty body" : body));
+ "Flow service: %s", response.getStatusCode(), body == null ? "Empty body" : body));
} catch (IOException e) {
return new PlatformCommunicationException(String.format("Received unexpected response (%d) from " +
- "Flow service. Could not read body.", response.getStatusCode()), e);
+ "Flow service. Could not read body.", response.getStatusCode()), e);
}
}
@@ -342,7 +340,7 @@ private static byte[] serializeClosure(Object data) {
private CompletionId addStageWithClosure(APIModel.CompletionOperation operation, FlowId flowId, Serializable supplier, CodeLocation codeLocation, List deps) {
byte[] serialized = serializeClosure(supplier);
- BlobResponse blobResponse = blobStoreClient.writeBlob(flowId.getId(), serialized, CONTENT_TYPE_JAVA_OBJECT);
+ BlobResponse blobResponse = blobStoreClient.writeBlob(flowId.getId(), serialized, CONTENT_TYPE_JAVA_OBJECT);
return addStage(operation, APIModel.Blob.fromBlobResponse(blobResponse), deps, flowId, codeLocation);
diff --git a/runtime/src/test/java/com/fnproject/fn/runtime/flow/FlowsContinuationInvokerTest.java b/flow-runtime/src/test/java/com/fnproject/fn/runtime/flow/FlowsContinuationInvokerTest.java
similarity index 76%
rename from runtime/src/test/java/com/fnproject/fn/runtime/flow/FlowsContinuationInvokerTest.java
rename to flow-runtime/src/test/java/com/fnproject/fn/runtime/flow/FlowsContinuationInvokerTest.java
index 389d4e34..e38dfe45 100644
--- a/runtime/src/test/java/com/fnproject/fn/runtime/flow/FlowsContinuationInvokerTest.java
+++ b/flow-runtime/src/test/java/com/fnproject/fn/runtime/flow/FlowsContinuationInvokerTest.java
@@ -3,10 +3,9 @@
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fnproject.fn.api.*;
+import com.fnproject.fn.api.exception.FunctionInputHandlingException;
import com.fnproject.fn.api.flow.*;
-import com.fnproject.fn.runtime.QueryParametersImpl;
import com.fnproject.fn.runtime.ReadOnceInputEvent;
-import com.fnproject.fn.api.exception.FunctionInputHandlingException;
import com.fnproject.fn.runtime.exception.InternalFunctionInvocationException;
import org.junit.After;
import org.junit.Before;
@@ -15,10 +14,8 @@
import org.junit.rules.ExpectedException;
import java.io.*;
-import java.io.IOException;
-import java.io.InputStream;
-import java.io.Serializable;
import java.lang.reflect.Method;
+import java.time.Instant;
import java.util.*;
import static com.fnproject.fn.runtime.flow.FlowContinuationInvoker.FLOW_ID_HEADER;
@@ -62,9 +59,9 @@ public void continuationInvokedWhenGraphHeaderPresent() throws IOException, Clas
// Given
InputEvent event = newRequest()
- .withClosure((Flows.SerFunction) (x) -> x * 2)
- .withJavaObjectArgs(10)
- .asEvent();
+ .withClosure((Flows.SerFunction) (x) -> x * 2)
+ .withJavaObjectArgs(10)
+ .asEvent();
// When
Optional result = invoker.tryInvoke(new EmptyInvocationContext(), event);
@@ -84,8 +81,8 @@ public void continuationNotInvokedWhenHeaderMissing() throws IOException, ClassN
// Given
InputEvent event = new InputEventBuilder()
- .withBody("")
- .build();
+ .withBody("")
+ .build();
// When
FlowContinuationInvoker invoker = new FlowContinuationInvoker();
@@ -102,8 +99,8 @@ public void failsIfArgMissing() throws IOException, ClassNotFoundException {
// Given
InputEvent event = newRequest()
- .withClosure((Flows.SerFunction) (x) -> x * 2)
- .asEvent();
+ .withClosure((Flows.SerFunction) (x) -> x * 2)
+ .asEvent();
invoker.tryInvoke(new EmptyInvocationContext(), event);
@@ -121,8 +118,8 @@ public void failsIfUnknownClosureType() {
thrown.expect(FunctionInputHandlingException.class);
// Given
InputEvent event = newRequest()
- .withClosure(new TestIf())
- .asEvent();
+ .withClosure(new TestIf())
+ .asEvent();
invoker.tryInvoke(new EmptyInvocationContext(), event);
}
@@ -143,25 +140,25 @@ private Tc(Serializable closure, Object result, Object... args) {
}
Tc[] cases = new Tc[]{
- new Tc((Flows.SerConsumer) (v) -> {
- }, null, "hello"),
- new Tc((Flows.SerBiFunction) (String::concat), "hello bob", "hello ", "bob"),
- new Tc((Flows.SerBiConsumer) (a, b) -> {
- }, null, "hello ", "bob"),
- new Tc((Flows.SerFunction) (String::toUpperCase), "HELLO BOB", "hello bob"),
- new Tc((Flows.SerRunnable) () -> {
- }, null),
- new Tc((Flows.SerCallable) () -> "hello", "hello"),
- new Tc((Flows.SerSupplier) () -> "hello", "hello"),
+ new Tc((Flows.SerConsumer) (v) -> {
+ }, null, "hello"),
+ new Tc((Flows.SerBiFunction) (String::concat), "hello bob", "hello ", "bob"),
+ new Tc((Flows.SerBiConsumer) (a, b) -> {
+ }, null, "hello ", "bob"),
+ new Tc((Flows.SerFunction) (String::toUpperCase), "HELLO BOB", "hello bob"),
+ new Tc((Flows.SerRunnable) () -> {
+ }, null),
+ new Tc((Flows.SerCallable) () -> "hello", "hello"),
+ new Tc((Flows.SerSupplier) () -> "hello", "hello"),
};
for (Tc tc : cases) {
InputEvent event = newRequest()
- .withClosure(tc.closure)
- .withJavaObjectArgs(tc.args)
- .asEvent();
+ .withClosure(tc.closure)
+ .withJavaObjectArgs(tc.args)
+ .asEvent();
Optional result = invoker.tryInvoke(new EmptyInvocationContext(), event);
assertThat(result).isPresent();
@@ -180,13 +177,13 @@ private Tc(Serializable closure, Object result, Object... args) {
public void emptyValueCorrectlySerialized() throws IOException, ClassNotFoundException {
// Given
InputEvent event = newRequest()
- .withClosure((Flows.SerConsumer