diff --git a/docs/src/main/asciidoc/adapters/azure-intro.adoc b/docs/src/main/asciidoc/adapters/azure-intro.adoc index 230e1f6b7..db68aca0a 100644 --- a/docs/src/main/asciidoc/adapters/azure-intro.adoc +++ b/docs/src/main/asciidoc/adapters/azure-intro.adoc @@ -1,18 +1,31 @@ :branch: master -=== Microsoft Azure +=== Microsoft Azure Functions -The https://azure.microsoft.com[Azure] adapter bootstraps a Spring Cloud Function context and channels function calls from the Azure framework into the user functions, using Spring Boot configuration where necessary. -Azure Functions has quite a unique and invasive programming model, involving annotations in user code that are specific to the Azure platform. -However, it is important to understand that because of the style of integration provided by Spring Cloud Function, this annotation-based programming model is simply a type-safe way to configure your simple java function (function that has no awareness of Azure) to be recognized as Azure function. +The https://azure.microsoft.com[Azure] adapter, that allows to deploy and run Spring Cloud Functions as native Azure Java Functions. -All you need to annotate the your class with `@Component` or `@Service` annotations, auto-wire the required Spring beans (or the `FunctionCatalog` when using Spring Cloud Function), define and configure your Azure function handler. This Azure handler method provides input and output types as annotated method parameters (enabling Azure to inspect the class and create JSON bindings). +The Azure impose an annotation-based https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java[programming model] for defining the function's handler methods and their input and output types. +The Azure's maven or gradle plugins are used to inspects the annotated class definitions to generate the necessary Azure Function binding files (such as function.json). +The annotations are just a type-safe way to configure your simple java function (function that has no awareness of Azure) to be recognized as Azure function. + +The https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-adapters/spring-cloud-function-adapter-azure[spring-cloud-function-adapter-azure] extends the basic programming model and provides fully fledged Spring and Spring Cloud Function programming model support. +With the adapter you can build your, usual, Spring Cloud Function application and then auto-wire and use the required services from within your Azure handler methods. + +TIP: For pure web based function applications, the https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web[spring-cloud-function-adapter-azure-web] adapter allows replacing the Azure programming model completely with the familiar Spring Web programming model. You just build your Spring Web app, add the azure-web adapter dependency and the necessarily azure layout packaging. You can find more about the azure-web adapter [here] + +==== Using the Azure Adapter + +All you need to annotate the your class with `@Component` or `@Service` annotations, auto-wire the required Spring beans (or the https://docs.spring.io/spring-cloud-function/docs/current/reference/html/spring-cloud-function.html#_function_catalog_and_flexible_function_signatures[FunctionCatalog] when using Spring Cloud Function), define and configure your Azure function handler. [source,java] ---- -@Component +@SpringBootApplication public class MyAzureFunction { + public static void main(String[] args) { + SpringApplication.run(MyAzureFunction.class, args); + } + /** * Plain Spring bean (not Spring Cloud Functions!) */ @@ -25,8 +38,8 @@ public class MyAzureFunction { @Autowired private FunctionCatalog functionCatalog; - @FunctionName("bean") - public String plainBean( + @FunctionName("spring") + public String plainBean( // <1> @HttpTrigger(name = "req", methods = { HttpMethod.GET, HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, ExecutionContext context) { @@ -35,7 +48,7 @@ public class MyAzureFunction { } @FunctionName("scf") - public String springCloudFunction( + public String springCloudFunction( // <2> @HttpTrigger(name = "req", methods = { HttpMethod.GET, HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, ExecutionContext context) { @@ -47,10 +60,15 @@ public class MyAzureFunction { } } ---- +Azure's programming-model uses the https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#java-function-basics[@FunctionName] method annotation to identify the designated function handlers. +When invoked by a trigger (such as `@HttpTrigger`), functions process that trigger, and any other inputs, to produce one or more outputs. -The `plainBean` method will be mapped to the `bean` Azure function and when executed this method uses of the plain `uppercase` bean to compute the result. +TIP: Use the Java annotations included in the https://learn.microsoft.com/en-us/java/api/com.microsoft.azure.functions.annotation?view=azure-java-stable[com.microsoft.azure.functions.annotation.*] package to bind input and outputs to your methods. -The `springCloudFunction` method, mapped to the `scf` Azure function shows how to use the Spring Cloud Function composition. + +<1> The `plainBean` method handler is mapped to an Azure function, called `spring`, and when executed is uses of the auto-wired `uppercase` spring bean to compute the result. +This demonstrates how to use "plain" Spring components in your Azure handlers. +<2> The `springCloudFunction` method handler is mapped to the `scf` Azure function and shows how to use Spring Cloud Function composition in your handles. The actual Spring defined functions you're delegating to looks like this: @@ -67,22 +85,35 @@ public Function reverse() { } ---- -In order to enable the Azure Function integration add the azure adapter dependency to your `pom.xml` -file: +In order to enable the Azure Function integration add the azure adapter dependency to your `pom.xml` or `build.gradle` +files: -[source,xml] +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven ---- org.springframework.cloud spring-cloud-function-adapter-azure + 4.0.3 ---- -Note: version `4.0.0+` is requried. Having the adapter on the classpath activates the Azure Java Worker integration. +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +dependencies { + implementation "org.springframework.cloud:spring-cloud-function-adapter-azure:4.0.3" +} -==== Accessing Azure ExecutionContext +---- +==== + +NOTE: version `4.0.0+` is required. Having the adapter on the classpath activates the Azure Java Worker integration. + +===== Accessing Azure ExecutionContext Some time there is a need to access the target execution context provided by the Azure runtime in the form of `com.microsoft.azure.functions.ExecutionContext`. For example one of such needs is logging, so it can appear in the Azure console. @@ -97,7 +128,7 @@ public String execute( HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, ExecutionContext context) { - Message message = AzureFunctionUtil.enhanceInputIfNecessary(request.getBody().get(), context); + Message message = (Message) AzureFunctionUtil.enhanceInputIfNecessary(request.getBody().get(), context); return this.uppercase.apply(message); } @@ -117,21 +148,27 @@ public Function, String> uppercase(JsonMapper mapper) { } ---- -==== Notes on JAR Layout +==== Azure JAR Layout You don't need the Spring Cloud Function Web at runtime in Azure, so you can exclude this before you create the JAR you deploy to Azure, but it won't be used if you include it, so it doesn't hurt to leave it in. -A function application on Azure is an archive generated by the `azure-functions-maven-plugin` Maven plugin. +A function application on Azure is an archive generated either by the Maven (`azure-functions-maven-plugin`) or the Gradle(`azure-functions-gradle-plugin`) plugins. The function lives in the JAR file generated by this project. + The sample creates it as an executable jar, using the thin layout, so that Azure can find the handler classes. If you prefer you can just use a regular flat JAR file. The dependencies should *not* be included. ==== Build file setup -In order to run Spring Cloud Function applications on Microsoft Azure, you can leverage the `azure-functions-maven-plugin` Maven plugin offered by the cloud platform provider. +In order to run Spring Cloud Function applications on Microsoft Azure, you have to use Maven or Gradle plugins offered by Azure. -You will need to provide Azure-specific configuration for your application, specifying the `resourceGroup`, `appName` and other optional properties, and add the `package` goal execution so that the `function.json` file required by Azure is generated for you. Full plugin documentation can be found in the https://github.com/microsoft/azure-maven-plugins[plugin repository]. +Provide the Azure-specific configuration for your application, specifying the `resourceGroup`, `appName` and other optional properties. +More information about the runtime configurations: https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#java-versions[Java Versions], https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#specify-the-deployment-os[Deployment OS]. -[source,xml] +Sample Azure Function (Maven/Gradle) configuration would like like: + +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven ---- com.microsoft.azure @@ -172,22 +209,69 @@ You will need to provide Azure-specific configuration for your application, spec ---- -Runtime configurations: https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#java-versions[Java Versions], https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#specify-the-deployment-os[Deployment OS] +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +plugins { + id "com.microsoft.azure.azurefunctions" version "1.11.0" + // ... +} -Add the `start-class` POM property to point to your main (e.g. SpringApplication) class. +apply plugin: "com.microsoft.azure.azurefunctions" -[source,xml] +azurefunctions { + appName = 'YOUR-AZURE-FUNCTION-APP-NAME' + resourceGroup = 'YOUR-AZURE-FUNCTION-RESOURCE-GROUP' + region = 'YOUR-AZURE-FUNCTION-APP-REGION' + appServicePlanName = 'YOUR-AZURE-FUNCTION-APP-SERVICE-PLANE-NAME' + pricingTier = 'YOUR-AZURE-FUNCTION-APP-SERVICE-PLANE-NAME' + runtime { + os = 'linux' + javaVersion = '11' + } + auth { + type = 'azure_cli' + } + appSettings { + FUNCTIONS_EXTENSION_VERSION = '~4' + } +} +---- +==== + +The complete plugin documentation is available at the https://github.com/microsoft/azure-maven-plugins/tree/develop/azure-functions-maven-plugin[Azure Maven] and https://github.com/microsoft/azure-gradle-plugins/tree/master/azure-functions-gradle-plugin[Azure Gradle] repositories. + +Next you must specify the `Start-Class` or `Main-Class` to point to your application main class. + +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven ---- - 17 - YOUR MAIN CLASS + YOUR APP MAIN CLASS ... ---- +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +jar { + manifest { + attributes( + "Main-Class": "YOUR APP MAIN CLASS" + ) + } +} +---- +==== + +IMPORTANT: The main class provided must be annotated by `SpringBootApplication` or `SpringBootConfiguration` annotation. + + You will also have to ensure that the files to be scanned by the plugin can be found in the Azure functions staging directory (see the https://github.com/microsoft/azure-maven-plugins[plugin repository] for more details on the staging directory and it's default location). -Add the `host.json` configuration under the `src/main/resources` folder: +Add the `host.json` configuration file: [source,json] ---- @@ -200,17 +284,33 @@ Add the `host.json` configuration under the `src/main/resources` folder: } ---- -You can find the entire sample `pom.xml` file for deploying Spring Cloud Function applications to Microsoft Azure with Maven https://github.com/spring-cloud/spring-cloud-function/blob/{branch}/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/pom.xml[here]. +TIP: If the file is not in the project top folder you need to configure your plugins accordingly (like `hostJson` maven attribute). -NOTE: As of yet, only Maven plugin is available. Gradle plugin has not been created by -the cloud platform provider. + +Here is a list of various Spring Cloud Function Azure Adapter samples you can explore: + +- https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-http-trigger[HTTP Trigger (Maven)] +- https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle[ HTTP Trigger (Gradle)] +- https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-blob-trigger[Blob Trigger (Maven)] +- https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-timer-trigger[Timer Trigger (Maven)] +- https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-kafka-trigger[ Kafka Trigger & Output Binding (Maven)]. ==== Build +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven ---- ./mvnw -U clean package ---- +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +./gradlew azureFunctionsPackage +---- +==== + ==== Running locally To run locally on top of `Azure Functions`, and to deploy to your live Azure environment, you will need `Azure Functions Core Tools` installed along with the Azure CLI (see https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-java?tabs=bash%2Cazure-cli%2Cbrowser#configure-your-local-environment[here]). @@ -218,10 +318,20 @@ For some configuration you would need the https://learn.microsoft.com/en-us/azur Then run the sample: +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven ---- ./mvnw azure-functions:run ---- +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +./gradlew azureFunctionsRun +---- +==== + ==== Running on Azure Make sure you are logged in your Azure account. @@ -232,10 +342,20 @@ az login and deploy +==== +[source,xml,indent=0,subs="verbatim,attributes",role="primary"] +.Maven ---- ./mvnw azure-functions:deploy ---- +[source,groovy,indent=0,subs="verbatim,attributes",role="secondary"] +.Gradle +---- +./gradlew azureFunctionsDeploy +---- +==== + ==== Debug locally Run the function in debug mode. @@ -273,172 +393,19 @@ VS Code remote debug configuration: "hostName": "localhost", "port": "5005" }, + ... +] } ---- -==== (Legacy) FunctionInvoker integration option +=== Microsoft Azure Functions Web -The https://azure.microsoft.com[Azure] adapter bootstraps a Spring Cloud Function context and channels function calls from the Azure -framework into the user functions, using Spring Boot configuration where necessary. Azure Functions has quite a unique and -invasive programming model, involving annotations in user code that are specific to the Azure platform. -However, it is important to understand that because of the style of integration provided by Spring Cloud Function, specifically `org.springframework.cloud.function.adapter.azure.FunctionInvoker`, this annotation-based programming model is simply a type-safe way to configure -your simple java function (function that has no awareness of Azure) to be recognized as Azure function. -All you need to do is create a handler that extends `FunctionInvoker`, define and configure your function handler method and -make a callback to `handleRequest(..)` method. This handler method provides input and output types as annotated method parameters -(enabling Azure to inspect the class and create JSON bindings). +For pure web based function applications, the https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web[spring-cloud-function-adapter-azure-web] adapter allows replacing the Azure programming model completely with the familiar Spring Web programming model. You just build your Spring Web app, add the azure-web adapter dependency and the necessarily azure layout packaging. +The `spring-cloud-function-adapter-azure-web` requires the same package layout and build/deployment steps as the `spring-cloud-function-adapter-azure`. -```java -public class UppercaseHandler extends FunctionInvoker, String> { +=== (Legacy) FunctionInvoker integration - @FunctionName("uppercase") - public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET, - HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, - ExecutionContext context) { - Message message = MessageBuilder.withPayload(request.getBody().get()).copyHeaders(request.getHeaders()).build(); - return handleRequest(message, context); - } -} -``` +WARNING: The legacy `FunctionInvoker` programming model is deprecated and will not be supported going forward. -Note that aside form providing configuration via Azure annotation we create an instance of `Message` inside the body of this handler method and make a callback to `handleRequest(..)` method returning its result. - -The actual user function you're delagating to looks like this - -```java -@Bean -public Function uppercase() { - return payload -> payload.toUpperCase(); -} - -OR - -@Bean -public Function, String> uppercase() { - return message -> message.getPayload().toUpperCase(); -} - -``` - -Note that when creating a Message you can copy HTTP headers effectively making them available to you if necessary. - -The `org.springframework.cloud.function.adapter.azure.FunctionInvoker` class has two useful -methods (`handleRequest` and `handleOutput`) to which you can delegate the actual function call, so mostly the function will only ever have one line. - -The function name (definition) will be retrieved from Azure's `ExecutionContext.getFunctionName()` method, effectively supporting multiple function in the application context. - -==== Accessing Azure ExecutionContext - -Some time there is a need to access the target execution context provided by the Azure runtime in the form of `com.microsoft.azure.functions.ExecutionContext`. -For example one of such needs is logging, so it can appear in the Azure console. - -For that purpose the FunctionInvoker will add an instance of the `ExecutionContext` as a Message header so you can retrieve it via `executionContext` key. - -``` -@Bean -public Function, String> uppercase(JsonMapper mapper) { - return message -> { - String value = message.getPayload(); - ExecutionContext context = (ExecutionContext) message.getHeaders().get("executionContext"); - . . . - } -} -``` - -==== Notes on JAR Layout - -You don't need the Spring Cloud Function Web at runtime in Azure, so you can exclude this -before you create the JAR you deploy to Azure, but it won't be used if you include it, so -it doesn't hurt to leave it in. A function application on Azure is an archive generated by - the Maven plugin. The function lives in the JAR file generated by this project. - The sample creates it as an executable jar, using the thin layout, so that Azure can find - the handler classes. If you prefer you can just use a regular flat JAR file. - The dependencies should *not* be included. - -==== Build file setup - -In order to run Spring Cloud Function applications on Microsoft Azure, you can leverage the Maven -plugin offered by the cloud platform provider. - -In order to use the adapter plugin for Maven, add the plugin dependency to your `pom.xml` -file: - -[source,xml] ----- - - - org.springframework.cloud - spring-cloud-function-adapter-azure - - ----- - -Then, configure the plugin. You will need to provide Azure-specific configuration for your -application, specifying the `resourceGroup`, `appName` and other optional properties, and - add the `package` goal execution so that the `function.json` file required by Azure is - generated for you. Full plugin documentation can be found in the https://github.com/microsoft/azure-maven-plugins[plugin repository]. - -[source,xml] ----- - - com.microsoft.azure - azure-functions-maven-plugin - - ${functionResourceGroup} - ${functionAppName} - - - - package-functions - - package - - - - ----- - -You will also have to ensure that the files to be scanned by the plugin can be found in the -Azure functions staging directory (see the https://github.com/microsoft/azure-maven-plugins[plugin repository] - for more details on the staging directory and it's default location). - -You can find the entire sample `pom.xml` file for deploying Spring Cloud Function -applications to Microsoft Azure with Maven https://github.com/spring-cloud/spring-cloud-function/blob/{branch}/spring-cloud-function-samples/function-sample-azure/pom.xml[here]. - -NOTE: As of yet, only Maven plugin is available. Gradle plugin has not been created by -the cloud platform provider. - -==== Build - ----- -./mvnw -U clean package ----- - -==== Running the sample - -You can run the sample locally, just like the other Spring Cloud Function samples: - ---- -./mvnw spring-boot:run ---- - -and `curl -H "Content-Type: text/plain" localhost:8080/api/uppercase -d '{"value": "hello foobar"}'`. - -You will need the `az` CLI app (see https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-java-maven for more detail). To deploy the function on Azure runtime: - ----- -$ az login -$ mvn azure-functions:deploy ----- - -On another terminal try this: `curl https:///api/uppercase -d '{"value": "hello foobar!"}'`. Please ensure that you use the right URL for the function above. Alternatively you can test the function in the Azure Dashboard UI (click on the function name, go to the right hand side and click "Test" and to the bottom right, "Run"). - -The input type for the function in the Azure sample is a Foo with a single property called "value". So you need this to test it with something like below: - ----- -{ - "value": "foobar" -} ----- - -NOTE: The Azure sample app is written in the "non-functional" style (using `@Bean`). The functional style (with just `Function` or `ApplicationContextInitializer`) is much faster on startup in Azure than the traditional `@Bean` style, so if you don't need `@Beans` (or `@EnableAutoConfiguration`) it's a good choice. Warm starts are not affected. +For additional documentation and samples about the Function Integration approach follow the https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure/[azure-sample] README and code. diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/README.adoc b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/README.adoc new file mode 100644 index 000000000..87dee4f61 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/README.adoc @@ -0,0 +1,11 @@ +=== Introduction + +Light weight Azure Function forwarding proxy which can deploy any existing Spring Boot web application as Azure Functions. +Infernally uses the Azure Http Trigger mapping. + +This module is identified as the only additional dependency to the existing web-app. + +A sample is provided in https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-web[azure-web sample] + + + diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/README.md b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/README.md deleted file mode 100644 index 19552843c..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/README.md +++ /dev/null @@ -1,12 +0,0 @@ -#### Introduction - -This module represents a concept of a light weight AWS forwarding proxy which deploys and interacts with existing -Spring Boot web application deployed as AWS Lambda. - -A sample is provided in [sample](https://github.com/spring-cloud/spring-cloud-function/tree/serverless-web/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store) directory. It contain README and SAM template file to simplify the deployment. This module is identified as the only additional dependnecy to the existing web-app. - -_NOTE: Although this module is AWS specific, this dependency is protocol only (not binary), therefore there is no AWS dependnecies._ - -The aformentioned proxy is identified as AWS Lambda [handler](https://github.com/spring-cloud/spring-cloud-function/blob/serverless-web/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/template.yml#L14) - -The main Spring Boot configuration file is identified as [MAIN_CLASS](https://github.com/spring-cloud/spring-cloud-function/blob/serverless-web/spring-cloud-function-adapters/spring-cloud-function-adapter-aws-web/sample/pet-store/template.yml#L22) diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/pom.xml b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/pom.xml index bbc7a8816..f65f14857 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/pom.xml +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/pom.xml @@ -24,10 +24,18 @@ annotations 3.0.1 + + org.springframework.cloud + spring-cloud-function-context + org.springframework.cloud spring-cloud-function-serverless-web + + org.springframework.boot + spring-boot-autoconfigure + com.microsoft.azure.functions azure-functions-java-library diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/AzureWebProxyInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/AzureWebProxyInvoker.java index ecbaf0118..08c501152 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/AzureWebProxyInvoker.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/AzureWebProxyInvoker.java @@ -38,6 +38,7 @@ import org.apache.commons.logging.LogFactory; import org.springframework.cloud.function.serverless.web.ProxyHttpServletRequest; import org.springframework.cloud.function.serverless.web.ProxyHttpServletResponse; import org.springframework.cloud.function.serverless.web.ProxyMvc; +import org.springframework.cloud.function.utils.FunctionClassUtils; import org.springframework.util.CollectionUtils; import org.springframework.util.StringUtils; @@ -61,7 +62,6 @@ public class AzureWebProxyInvoker implements FunctionInstanceInjector { @Override public T getInstance(Class functionClass) throws Exception { - // System.setProperty("MAIN_CLASS", "oz.spring.petstore.PetStoreSpringAppConfig"); this.initialize(); return (T) this; } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/FunctionClassUtils.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/FunctionClassUtils.java deleted file mode 100644 index af6c32802..000000000 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/java/org/springframework/cloud/function/adapter/azure/web/FunctionClassUtils.java +++ /dev/null @@ -1,153 +0,0 @@ -/* - * Copyright 2019-2021 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package org.springframework.cloud.function.adapter.azure.web; - -import java.io.InputStream; -import java.net.URL; -import java.util.Collections; -import java.util.List; -import java.util.jar.JarFile; -import java.util.jar.Manifest; - -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; - -//import org.springframework.boot.SpringBootConfiguration; -//import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.core.KotlinDetector; -import org.springframework.core.io.Resource; -import org.springframework.core.io.support.PathMatchingResourcePatternResolver; -import org.springframework.util.Assert; -import org.springframework.util.ClassUtils; -import org.springframework.util.StringUtils; - -/** - * General utility class which aggregates various class-level utility functions - * used by the framework. - * - * @author Oleg Zhurakousky - * @since 3.0.1 - */ -public final class FunctionClassUtils { - - private static Log logger = LogFactory.getLog(FunctionClassUtils.class); - - private FunctionClassUtils() { - - } - - /** - * Discovers the start class in the currently running application. - * The discover search order is 'MAIN_CLASS' environment property, - * 'MAIN_CLASS' system property, META-INF/MANIFEST.MF:'Start-Class' attribute, - * meta-inf/manifest.mf:'Start-Class' attribute. - * - * @return instance of Class which represent the start class of the application. - */ - public static Class getStartClass() { - ClassLoader classLoader = FunctionClassUtils.class.getClassLoader(); - return getStartClass(classLoader); - } - - static Class getStartClass(ClassLoader classLoader) { - Class mainClass = null; - if (System.getenv("MAIN_CLASS") != null) { - mainClass = ClassUtils.resolveClassName(System.getenv("MAIN_CLASS"), classLoader); - } - else if (System.getProperty("MAIN_CLASS") != null) { - mainClass = ClassUtils.resolveClassName(System.getProperty("MAIN_CLASS"), classLoader); - } - else { - try { - Class result = getStartClass( - Collections.list(classLoader.getResources(JarFile.MANIFEST_NAME)), classLoader); - if (result == null) { - result = getStartClass(Collections - .list(classLoader.getResources("meta-inf/manifest.mf")), classLoader); - } - Assert.notNull(result, "Failed to locate main class"); - mainClass = result; - } - catch (Exception ex) { - throw new IllegalStateException("Failed to discover main class. An attempt was made to discover " - + "main class as 'MAIN_CLASS' environment variable, system property as well as " - + "entry in META-INF/MANIFEST.MF (in that order).", ex); - } - } - logger.info("Main class: " + mainClass); - return mainClass; - } - - private static Class getStartClass(List list, ClassLoader classLoader) { - if (logger.isTraceEnabled()) { - logger.trace("Searching manifests: " + list); - } - for (URL url : list) { - try { - InputStream inputStream = null; - Manifest manifest = new Manifest(url.openStream()); - logger.info("Searching for start class in manifest: " + url); - if (logger.isDebugEnabled()) { - manifest.write(System.out); - } - try { - String startClassName = manifest.getMainAttributes().getValue("Start-Class"); - if (!StringUtils.hasText(startClassName)) { - startClassName = manifest.getMainAttributes().getValue("Main-Class"); - } - - if (StringUtils.hasText(startClassName)) { - Class startClass = ClassUtils.forName(startClassName, classLoader); - - if (KotlinDetector.isKotlinType(startClass)) { - PathMatchingResourcePatternResolver r = new PathMatchingResourcePatternResolver(classLoader); - String packageName = startClass.getPackage().getName(); - Resource[] resources = r.getResources("classpath:" + packageName.replace(".", "/") + "/*.class"); - for (int i = 0; i < resources.length; i++) { - Resource resource = resources[i]; - String className = packageName + "." + (resource.getFilename().replace("/", ".")).replace(".class", ""); - startClass = ClassUtils.forName(className, classLoader); -// if (isSpringBootApplication(startClass)) { -// logger.info("Loaded Main Kotlin Class: " + startClass); -// return startClass; -// } - } - } -// else if (isSpringBootApplication(startClass)) { -// logger.info("Loaded Start Class: " + startClass); -// return startClass; -// } - } - } - finally { - if (inputStream != null) { - inputStream.close(); - } - } - } - catch (Exception ex) { - logger.debug("Failed to determine Start-Class in manifest file of " + url, ex); - } - } - return null; - } - -// private static boolean isSpringBootApplication(Class startClass) { -// return startClass.getDeclaredAnnotation(SpringBootApplication.class) != null -// || startClass.getDeclaredAnnotation(SpringBootConfiguration.class) != null; -// } -} diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/resources/application.properties b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/resources/application.properties new file mode 100644 index 000000000..d6705c881 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.banner.location=classpath:/spring-azure-function-banner.txt diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/resources/spring-azure-function-banner.txt b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/resources/spring-azure-function-banner.txt new file mode 100644 index 000000000..32225b776 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web/src/main/resources/spring-azure-function-banner.txt @@ -0,0 +1,8 @@ + ____ _ _ _____ _ _ + / ___| _ __ _ __(_)_ __ __ _ / \ _____ _ _ __ ___ | ___| _ _ __ ___| |_(_) ___ _ __ ___ + \___ \| '_ \| '__| | '_ \ / _` | / _ \ |_ / | | | '__/ _ \ | |_ | | | | '_ \ / __| __| |/ _ \| '_ \/ __| + ___) | |_) | | | | | | | (_| | / ___ \ / /| |_| | | | __/ | _|| |_| | | | | (__| |_| | (_) | | | \__ \ + |____/| .__/|_| |_|_| |_|\__, | /_/ \_\/___|\__,_|_| \___| |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/ + |_| |___/ +${application.title} ${application.version} +Powered by Spring Boot ${spring-boot.version} and Azure Functions \ No newline at end of file diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/README.adoc b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/README.adoc index 7a09ab922..74fbb3535 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/README.adoc +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/README.adoc @@ -7,6 +7,11 @@ Edit the files in the src/main/asciidoc/ directory instead. This project provides an adapter layer for a Spring Cloud Function application onto Azure. You can write an app with a single `@Bean` of type `Function` and it will be deployable in Azure if you get the JAR file laid out right. -== Sample Function +== Sample Functions -Go to the link:../../spring-cloud-function-samples/function-sample-azure/[function-sample-azure] to learn about how the sample works, and how to run and test it. \ No newline at end of file +- ../../spring-cloud-function-samples/function-sample-azure-http-trigger[Azure HTTP Trigger (Maven)] +- ../../spring-cloud-function-samples/function-sample-azure-http-trigger-gradle[Azure HTTP Trigger (Gradle)] +- ../../spring-cloud-function-samples/function-sample-azure-blob-trigger[Azure Blob Trigger (Maven)] +- ../../spring-cloud-function-samples/function-sample-azure-timer-trigger[Azure Timer Trigger (Maven)] +- ../../spring-cloud-function-samples/function-sample-azure-kafka-trigger[Azure Kafka Trigger & Output Binding (Maven)] +- ../../spring-cloud-function-samples/function-sample-azure/[(Legacy - FunctionInvoker) Azure HTTP Trigger (Maven)] diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureFunctionInstanceInjector.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureFunctionInstanceInjector.java index b6a450a16..999d24c01 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureFunctionInstanceInjector.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureFunctionInstanceInjector.java @@ -22,10 +22,12 @@ import com.microsoft.azure.functions.spi.inject.FunctionInstanceInjector; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; +import org.springframework.boot.ResourceBanner; import org.springframework.boot.SpringApplication; import org.springframework.boot.WebApplicationType; import org.springframework.cloud.function.utils.FunctionClassUtils; import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.core.io.DefaultResourceLoader; import org.springframework.util.ClassUtils; import org.springframework.util.CollectionUtils; @@ -96,6 +98,8 @@ public class AzureFunctionInstanceInjector implements FunctionInstanceInjector { SpringApplication application = new org.springframework.cloud.function.context.FunctionalSpringApplication( configurationClass); application.setWebApplicationType(WebApplicationType.NONE); + application.setBanner(new ResourceBanner( + new DefaultResourceLoader().getResource("classpath:/spring-azure-function-banner.txt"))); return application; } } diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java index 88b50cb5a..decdd24c2 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/FunctionInvoker.java @@ -65,8 +65,14 @@ import org.springframework.util.StringUtils; * @param result type * @author Oleg Zhurakousky * @author Chris Bono + * @author Christian Tzolov + * * @since 3.2 + * + * @deprecated since 4.0.0 in favor of the dependency injection implementation {@link AzureFunctionInstanceInjector}. + * Follow the official documentation for further information. */ +@Deprecated public class FunctionInvoker { private static Log logger = LogFactory.getLog(FunctionInvoker.class); @@ -109,11 +115,13 @@ public class FunctionInvoker { private FunctionInvocationWrapper discoverFunction(String functionDefinition) { FunctionInvocationWrapper function = FUNCTION_CATALOG.lookup(functionDefinition); - if (function != null && StringUtils.hasText(functionDefinition) && !function.getFunctionDefinition().equals(functionDefinition)) { + if (function != null && StringUtils.hasText(functionDefinition) + && !function.getFunctionDefinition().equals(functionDefinition)) { this.registerFunction(functionDefinition); function = FUNCTION_CATALOG.lookup(functionDefinition); } - else if (function == null && StringUtils.hasText(functionDefinition) && APPLICATION_CONTEXT.containsBean(functionDefinition)) { + else if (function == null && StringUtils.hasText(functionDefinition) + && APPLICATION_CONTEXT.containsBean(functionDefinition)) { this.registerFunction(functionDefinition); function = FUNCTION_CATALOG.lookup(functionDefinition); } @@ -128,20 +136,22 @@ public class FunctionInvoker { Object functionResult = function.apply(enhancedInput); if (functionResult instanceof Publisher) { - return postProcessReactiveFunctionResult(input, enhancedInput, (Publisher) functionResult, function, executionContext); + return postProcessReactiveFunctionResult(input, enhancedInput, (Publisher) functionResult, function, + executionContext); } return postProcessImperativeFunctionResult(input, enhancedInput, functionResult, function, executionContext); } /** - * Post-processes the result from a non-reactive function invocation before returning it to the Azure - * runtime. The default behavior is to {@link #convertOutputIfNecessary possibly convert} the result. + * Post-processes the result from a non-reactive function invocation before returning it to the Azure runtime. The + * default behavior is to {@link #convertOutputIfNecessary possibly convert} the result. * - *

Provides a hook for custom function invokers to extend/modify the function results handling. + *

+ * Provides a hook for custom function invokers to extend/modify the function results handling. * * @param rawInputs the inputs passed in from the Azure runtime - * @param functionInputs the actual inputs used for the function invocation; may be - * {@link #enhanceInputIfNecessary different} from the {@literal rawInputs} + * @param functionInputs the actual inputs used for the function invocation; may be {@link #enhanceInputIfNecessary + * different} from the {@literal rawInputs} * @param functionResult the result from the function invocation * @param function the invoked function * @param executionContext the Azure execution context @@ -149,44 +159,47 @@ public class FunctionInvoker { */ @SuppressWarnings("unchecked") protected O postProcessImperativeFunctionResult(I rawInputs, Object functionInputs, Object functionResult, - FunctionInvocationWrapper function, ExecutionContext executionContext - ) { + FunctionInvocationWrapper function, ExecutionContext executionContext) { return (O) this.convertOutputIfNecessary(rawInputs, functionResult); } /** - * Post-processes the result from a reactive function invocation before returning it to the Azure - * runtime. The default behavior is to delegate to {@link #postProcessMonoFunctionResult} or + * Post-processes the result from a reactive function invocation before returning it to the Azure runtime. The + * default behavior is to delegate to {@link #postProcessMonoFunctionResult} or * {@link #postProcessFluxFunctionResult} based on the result type. * - *

Provides a hook for custom function invokers to extend/modify the function results handling. + *

+ * Provides a hook for custom function invokers to extend/modify the function results handling. * * @param rawInputs the inputs passed in from the Azure runtime - * @param functionInputs the actual inputs used for the function invocation; may be - * {@link #enhanceInputIfNecessary different} from the {@literal rawInputs} + * @param functionInputs the actual inputs used for the function invocation; may be {@link #enhanceInputIfNecessary + * different} from the {@literal rawInputs} * @param functionResult the result from the function invocation * @param function the invoked function * @param executionContext the Azure execution context * @return the possibly modified function results */ protected O postProcessReactiveFunctionResult(I rawInputs, Object functionInputs, Publisher functionResult, - FunctionInvocationWrapper function, ExecutionContext executionContext - ) { + FunctionInvocationWrapper function, ExecutionContext executionContext) { if (FunctionTypeUtils.isMono(function.getOutputType())) { - return postProcessMonoFunctionResult(rawInputs, functionInputs, Mono.from(functionResult), function, executionContext); + return postProcessMonoFunctionResult(rawInputs, functionInputs, Mono.from(functionResult), function, + executionContext); } - return postProcessFluxFunctionResult(rawInputs, functionInputs, Flux.from(functionResult), function, executionContext); + return postProcessFluxFunctionResult(rawInputs, functionInputs, Flux.from(functionResult), function, + executionContext); } /** * Post-processes the {@code Mono} result from a reactive function invocation before returning it to the Azure - * runtime. The default behavior is to {@link Mono#blockOptional()} and {@link #convertOutputIfNecessary possibly convert} the result. + * runtime. The default behavior is to {@link Mono#blockOptional()} and {@link #convertOutputIfNecessary possibly + * convert} the result. * - *

Provides a hook for custom function invokers to extend/modify the function results handling. + *

+ * Provides a hook for custom function invokers to extend/modify the function results handling. * * @param rawInputs the inputs passed in from the Azure runtime - * @param functionInputs the actual inputs used for the function invocation; may be - * {@link #enhanceInputIfNecessary different} from the {@literal rawInputs} + * @param functionInputs the actual inputs used for the function invocation; may be {@link #enhanceInputIfNecessary + * different} from the {@literal rawInputs} * @param functionResult the Mono result from the function invocation * @param function the invoked function * @param executionContext the Azure execution context @@ -194,20 +207,21 @@ public class FunctionInvoker { */ @SuppressWarnings("unchecked") protected O postProcessMonoFunctionResult(I rawInputs, Object functionInputs, Mono functionResult, - FunctionInvocationWrapper function, ExecutionContext executionContext - ) { + FunctionInvocationWrapper function, ExecutionContext executionContext) { return (O) this.convertOutputIfNecessary(rawInputs, functionResult.blockOptional().get()); } /** * Post-processes the {@code Flux} result from a reactive function invocation before returning it to the Azure - * runtime. The default behavior is to {@link Flux#toIterable() block} and {@link #convertOutputIfNecessary possibly convert} the results. + * runtime. The default behavior is to {@link Flux#toIterable() block} and {@link #convertOutputIfNecessary possibly + * convert} the results. * - *

Provides a hook for custom function invokers to extend/modify the function results handling. + *

+ * Provides a hook for custom function invokers to extend/modify the function results handling. * * @param rawInputs the inputs passed in from the Azure runtime - * @param functionInputs the actual inputs used for the function invocation; may be - * {@link #enhanceInputIfNecessary different} from the {@literal rawInputs} + * @param functionInputs the actual inputs used for the function invocation; may be {@link #enhanceInputIfNecessary + * different} from the {@literal rawInputs} * @param functionResult the Mono result from the function invocation * @param function the invoked function * @param executionContext the Azure execution context @@ -215,16 +229,16 @@ public class FunctionInvoker { */ @SuppressWarnings({ "rawtypes", "unchecked" }) protected O postProcessFluxFunctionResult(I rawInputs, Object functionInputs, Flux functionResult, - FunctionInvocationWrapper function, ExecutionContext executionContext - ) { + FunctionInvocationWrapper function, ExecutionContext executionContext) { List resultList = new ArrayList<>(); for (Object resultItem : functionResult.toIterable()) { if (resultItem instanceof Collection) { resultList.addAll((Collection) resultItem); } else { - if (!function.isSupplier() && Collection.class.isAssignableFrom(FunctionTypeUtils.getRawType(function.getInputType())) - && !Collection.class.isAssignableFrom(FunctionTypeUtils.getRawType(function.getOutputType()))) { + if (!function.isSupplier() + && Collection.class.isAssignableFrom(FunctionTypeUtils.getRawType(function.getInputType())) + && !Collection.class.isAssignableFrom(FunctionTypeUtils.getRawType(function.getOutputType()))) { return (O) this.convertOutputIfNecessary(rawInputs, resultItem); } else { @@ -238,11 +252,10 @@ public class FunctionInvoker { @SuppressWarnings({ "unchecked", "rawtypes" }) private void registerFunction(String functionDefinition) { if (APPLICATION_CONTEXT.containsBean(functionDefinition)) { - FunctionRegistration functionRegistration = - new FunctionRegistration(APPLICATION_CONTEXT.getBean(functionDefinition), functionDefinition); + FunctionRegistration functionRegistration = new FunctionRegistration( + APPLICATION_CONTEXT.getBean(functionDefinition), functionDefinition); - Type type = FunctionContextUtils. - findType(functionDefinition, APPLICATION_CONTEXT.getBeanFactory()); + Type type = FunctionContextUtils.findType(functionDefinition, APPLICATION_CONTEXT.getBeanFactory()); functionRegistration = functionRegistration.type(type); @@ -305,8 +318,9 @@ public class FunctionInvoker { MessageBuilder messageBuilder = null; if (input instanceof HttpRequestMessage) { HttpRequestMessage requestMessage = (HttpRequestMessage) input; - Object payload = requestMessage.getHttpMethod() != null && requestMessage.getHttpMethod().equals(HttpMethod.GET) - ? requestMessage.getQueryParameters() + Object payload = requestMessage.getHttpMethod() != null + && requestMessage.getHttpMethod().equals(HttpMethod.GET) + ? requestMessage.getQueryParameters() : requestMessage.getBody(); if (payload == null) { @@ -352,8 +366,10 @@ public class FunctionInvoker { if (CollectionUtils.isEmpty(mf)) { OBJECT_MAPPER = new JacksonMapper(new ObjectMapper()); JsonMessageConverter jsonConverter = new JsonMessageConverter(OBJECT_MAPPER); - SmartCompositeMessageConverter messageConverter = new SmartCompositeMessageConverter(Collections.singletonList(jsonConverter)); - FUNCTION_CATALOG = new SimpleFunctionRegistry(APPLICATION_CONTEXT.getBeanFactory().getConversionService(), + SmartCompositeMessageConverter messageConverter = new SmartCompositeMessageConverter( + Collections.singletonList(jsonConverter)); + FUNCTION_CATALOG = new SimpleFunctionRegistry( + APPLICATION_CONTEXT.getBeanFactory().getConversionService(), messageConverter, OBJECT_MAPPER); } else { diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/HttpFunctionInvoker.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/HttpFunctionInvoker.java index 00047a8d2..c2af61fb3 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/HttpFunctionInvoker.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/HttpFunctionInvoker.java @@ -29,7 +29,11 @@ import com.microsoft.azure.functions.HttpResponseMessage; * @author Oleg Zhurakousky * * @since 3.2 + * + * @deprecated since 4.0.0 in favor of the dependency injection implementation {@link AzureFunctionInstanceInjector}. + * Follow the official documentation for further information. */ +@Deprecated public class HttpFunctionInvoker extends FunctionInvoker, HttpResponseMessage> { diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/resources/spring-azure-function-banner.txt b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/resources/spring-azure-function-banner.txt new file mode 100644 index 000000000..32225b776 --- /dev/null +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/resources/spring-azure-function-banner.txt @@ -0,0 +1,8 @@ + ____ _ _ _____ _ _ + / ___| _ __ _ __(_)_ __ __ _ / \ _____ _ _ __ ___ | ___| _ _ __ ___| |_(_) ___ _ __ ___ + \___ \| '_ \| '__| | '_ \ / _` | / _ \ |_ / | | | '__/ _ \ | |_ | | | | '_ \ / __| __| |/ _ \| '_ \/ __| + ___) | |_) | | | | | | | (_| | / ___ \ / /| |_| | | | __/ | _|| |_| | | | | (__| |_| | (_) | | | \__ \ + |____/| .__/|_| |_|_| |_|\__, | /_/ \_\/___|\__,_|_| \___| |_| \__,_|_| |_|\___|\__|_|\___/|_| |_|___/ + |_| |___/ +${application.title} ${application.version} +Powered by Spring Boot ${spring-boot.version} and Azure Functions \ No newline at end of file diff --git a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletResponse.java b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletResponse.java index b15eb6ef0..12575af1d 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletResponse.java +++ b/spring-cloud-function-adapters/spring-cloud-function-serverless-web/src/main/java/org/springframework/cloud/function/serverless/web/ProxyHttpServletResponse.java @@ -136,7 +136,7 @@ public class ProxyHttpServletResponse implements HttpServletResponse { @Override public void setContentLengthLong(long len) { - throw new UnsupportedOperationException(); + // Ignore } @Override diff --git a/spring-cloud-function-samples/function-azure-di-samples/README.md b/spring-cloud-function-samples/function-azure-di-samples/README.md deleted file mode 100644 index 459ec4e74..000000000 --- a/spring-cloud-function-samples/function-azure-di-samples/README.md +++ /dev/null @@ -1,209 +0,0 @@ -# Azure Functions with DI adapter - -## Common instructions to integrate Azure Functions with Spring Framework - -* Use the [Spring Initializer](https://start.spring.io/) to generate a pain, java Spring Boot project without additional dependencies. Set the boot version to `3.0.x`, the build to `Maven` and the packaging to `Jar`. - -* Add the `spring-cloud-function-adapter-azure` POM dependency: - - ```xml - - org.springframework.cloud - spring-cloud-function-adapter-azure - 4.0.0 - - ``` - Having the adapter on the classpath activates the Azure Java Worker integration. - -* Implement the [Azure Java Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#java-function-basics) as `@FunctionName` annotated methods: - - ```java - import java.util.Optional; - import java.util.function.Function; - - import com.microsoft.azure.functions.ExecutionContext; - import com.microsoft.azure.functions.HttpMethod; - import com.microsoft.azure.functions.HttpRequestMessage; - import com.microsoft.azure.functions.annotation.AuthorizationLevel; - import com.microsoft.azure.functions.annotation.FunctionName; - import com.microsoft.azure.functions.annotation.HttpTrigger; - - import org.springframework.beans.factory.annotation.Autowired; - import org.springframework.stereotype.Component; - - @Component - public class MyAzureFunction { - - @Autowired - private Function uppercase; - - @FunctionName("ditest") - public String execute( - @HttpTrigger(name = "req", methods = { HttpMethod.GET, - HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, - ExecutionContext context) { - - return this.uppercase.apply(request.getBody().get()); - } - } - ``` - - The `@FunctionName` annotated methods represent the Azure Function implementations. - - The class must be marked with the Spring `@Component` annotation. - - You can use any Spring mechanism to auto-wire the Spring beans used for the function implementation. - -* Add the `host.json` configuration under the `src/main/resources` folder: - - ```json - { - "version": "2.0", - "extensionBundle": { - "id": "Microsoft.Azure.Functions.ExtensionBundle", - "version": "[3.*, 4.0.0)" - } - } - ``` - -* When bootstrapped as Spring Boot project make sure to either disable the `spring-boot-maven-plugin` plugin or cover it into `thin-layout`: - - ```xml - - org.springframework.boot - spring-boot-maven-plugin - - - org.springframework.boot.experimental - spring-boot-thin-layout - ${spring-boot-thin-layout.version} - - - - ``` - Since Azure Functions requires a specific, custom, Jar packaging we have to disable SpringBoot one. - -* Add the `start-class` POM property to point to your main (e.g. SpringApplication) class. - ```xml - - 17 - YOUR MAIN CLASS - ... - - ``` - -* Add the `azure-functions-maven-plugin` to your POM configuration. A sample configuration would look like this. - - ```xml - - com.microsoft.azure - azure-functions-maven-plugin - 1.22.0 or higher - - - YOUR-AZURE-FUNCTION-APP-NAME - YOUR-AZURE-FUNCTION-RESOURCE-GROUP - YOUR-AZURE-FUNCTION-APP-REGION - YOUR-AZURE-FUNCTION-APP-SERVICE-PLANE-NAME - YOUR-AZURE-FUNCTION-PRICING-TIER - - ${project.basedir}/src/main/resources/host.json - - - linux - 11 - - - 7072 - - - - FUNCTIONS_EXTENSION_VERSION - ~4 - - - - - - package-functions - - package - - - - - ``` - - Set the AZURE subscription configuration such as app name, resource group, region, service plan, pricing Tier - - Runtime configuration: - - [Java Versions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#java-versions) - - Specify [Deployment OS](https://learn.microsoft.com/en-us/azure/azure-functions/functions-reference-java?tabs=bash%2Cconsumption#specify-the-deployment-os) - -* Build the project: - - ``` - ./mvnw clean package - ``` - -## Running Locally - -NOTE: To run locally on top of `Azure Functions`, and to deploy to your live Azure environment, you will need `Azure Functions Core Tools` installed along with the Azure CLI (see [here](https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-java?tabs=bash%2Cazure-cli%2Cbrowser#configure-your-local-environment)). - -NOTE: [Azure Functions Core Tools](https://github.com/Azure/azure-functions-core-tools) version `4.0.5030` or newer is required! - -For some configuration you would need the [Azurite emulator](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-emulator) as well. - -Then build and run the sample: - -``` -./mvnw clean package -./mvnw azure-functions:run -``` - -## Running on Azure - -Make sure you are logged in your Azure account. -``` -az login -``` - -Build and deploy - -``` -./mvnw clean package -./mvnw azure-functions:deploy -``` - -## Debug locally - -Run the function in debug mode. -``` -./mvnw azure-functions:run -DenableDebug -``` - -Alternatively and the `JAVA_OPTS` value to your `local.settings.json` like this: - -```json -{ - "IsEncrypted": false, - "Values": { - ... - "FUNCTIONS_WORKER_RUNTIME": "java", - "JAVA_OPTS": "-Djava.net.preferIPv4Stack=true -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=127.0.0.1:5005" - } -} -``` - - -VS Code remote debug configuration: - - ```json - { - "version": "0.2.0", - "configurations": [ - { - "type": "java", - "name": "Attach to Remote Program", - "request": "attach", - "hostName": "localhost", - "port": "5005" - }, - } - - ``` \ No newline at end of file diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/README.md b/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/README.md deleted file mode 100644 index 37bb86dc6..000000000 --- a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/README.md +++ /dev/null @@ -1,6 +0,0 @@ - - -The Blob storage binding is part of an [extension bundle](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-register#extension-bundles), which is specified in your [host.json](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob?tabs=in-process%2Cextensionv5%2Cextensionv3&pivots=programming-language-java#install-bundle) project file. - - -The Blob storage trigger starts a function when a new or updated blob is detected. The blob contents are provided as [input](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob-input?tabs=in-process%2Cextensionv5&pivots=programming-language-java) to the function. \ No newline at end of file diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/.gitignore b/spring-cloud-function-samples/function-sample-azure-blob-trigger/.gitignore similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/.gitignore rename to spring-cloud-function-samples/function-sample-azure-blob-trigger/.gitignore diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-function-samples/function-sample-azure-blob-trigger/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/.mvn/wrapper/maven-wrapper.jar rename to spring-cloud-function-samples/function-sample-azure-blob-trigger/.mvn/wrapper/maven-wrapper.jar diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/.mvn/wrapper/maven-wrapper.properties b/spring-cloud-function-samples/function-sample-azure-blob-trigger/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/.mvn/wrapper/maven-wrapper.properties rename to spring-cloud-function-samples/function-sample-azure-blob-trigger/.mvn/wrapper/maven-wrapper.properties diff --git a/spring-cloud-function-samples/function-sample-azure-blob-trigger/README.adoc b/spring-cloud-function-samples/function-sample-azure-blob-trigger/README.adoc new file mode 100644 index 000000000..d6230713d --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-blob-trigger/README.adoc @@ -0,0 +1,96 @@ +== Azure Functions with Blob Trigger + +IMPORTANT: For a general information about building and deploying `Azure Functions` with Spring Cloud Function, consult the https://docs.spring.io/spring-cloud-function/docs/current/reference/html/azure.html[Azure Adapter] documentation. + +The Blob storage trigger starts a function when a new or updated blob is detected. The blob contents are provided as https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob-input?tabs=in-process%2Cextensionv5&pivots=programming-language-java[input] to the function. + +The Blob storage binding is part of an https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-register#extension-bundles[extension bundle], specified in your https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob?tabs=in-process%2Cextensionv5%2Cextensionv3&pivots=programming-language-java#install-bundle[host.json] file. + + +=== Usage + +==== Package Staging folder + +Use the script below to package your staging folder: + +[source,shell] +---- +./mvnw clean package +---- + +==== Run Azure Functions locally + +Use the script below to run the function locally. + +[source,shell] +---- +./mvnw azure-functions:run +---- + +NOTE: To run locally on top of `Azure Functions`, and to deploy to your live Azure environment, you will need `Azure Functions Core Tools` installed along with the Azure CLI (see https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-java?tabs=bash%2Cazure-cli%2Cbrowser#configure-your-local-environment[here]). + +NOTE: https://github.com/Azure/azure-functions-core-tools[Azure Functions Core Tools] version `4.0.5030` or newer is required! + +For some configuration you would need the https://learn.microsoft.com/en-us/azure/storage/common/storage-use-emulator[Azurite emulator] as well. + + +==== Deploy Azure Functions to Azure Cloud + +Make sure you are logged in your Azure account. + +[source,shell] +---- +az login +---- + +then build and deploy + +[source,shell] +---- +./mvnw clean package +./mvnw azure-functions:deploy +---- + + +==== Debug locally + +Run the function in debug mode. + +[source,shell] +---- +./mvnw azure-functions:run -DenableDebug +---- + +Alternatively and the `JAVA_OPTS` value to your `local.settings.json` like this: + +[source,json] +---- +{ + "IsEncrypted": false, + "Values": { + ... + "FUNCTIONS_WORKER_RUNTIME": "java", + "JAVA_OPTS": "-Djava.net.preferIPv4Stack=true -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=127.0.0.1:5005" + } +} +---- + + +For VSCode remote debug use configuration like this: + +[source,json] +---- +{ + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Attach to Remote Program", + "request": "attach", + "hostName": "localhost", + "port": "5005" + }, + ... + ] +} +---- \ No newline at end of file diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/mvnw b/spring-cloud-function-samples/function-sample-azure-blob-trigger/mvnw similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/mvnw rename to spring-cloud-function-samples/function-sample-azure-blob-trigger/mvnw diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/mvnw.cmd b/spring-cloud-function-samples/function-sample-azure-blob-trigger/mvnw.cmd similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/mvnw.cmd rename to spring-cloud-function-samples/function-sample-azure-blob-trigger/mvnw.cmd diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/pom.xml b/spring-cloud-function-samples/function-sample-azure-blob-trigger/pom.xml similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/pom.xml rename to spring-cloud-function-samples/function-sample-azure-blob-trigger/pom.xml diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/src/main/java/com/example/azure/di/azureblobtriggerdemo/AzureBlobTriggerDemoApplication.java b/spring-cloud-function-samples/function-sample-azure-blob-trigger/src/main/java/com/example/azure/di/azureblobtriggerdemo/AzureBlobTriggerDemoApplication.java similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/src/main/java/com/example/azure/di/azureblobtriggerdemo/AzureBlobTriggerDemoApplication.java rename to spring-cloud-function-samples/function-sample-azure-blob-trigger/src/main/java/com/example/azure/di/azureblobtriggerdemo/AzureBlobTriggerDemoApplication.java diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/src/main/java/com/example/azure/di/azureblobtriggerdemo/MyBlobFunction.java b/spring-cloud-function-samples/function-sample-azure-blob-trigger/src/main/java/com/example/azure/di/azureblobtriggerdemo/MyBlobFunction.java similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/src/main/java/com/example/azure/di/azureblobtriggerdemo/MyBlobFunction.java rename to spring-cloud-function-samples/function-sample-azure-blob-trigger/src/main/java/com/example/azure/di/azureblobtriggerdemo/MyBlobFunction.java diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/src/main/resources/application.properties b/spring-cloud-function-samples/function-sample-azure-blob-trigger/src/main/resources/application.properties similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/src/main/resources/application.properties rename to spring-cloud-function-samples/function-sample-azure-blob-trigger/src/main/resources/application.properties diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/src/main/resources/host.json b/spring-cloud-function-samples/function-sample-azure-blob-trigger/src/main/resources/host.json similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/src/main/resources/host.json rename to spring-cloud-function-samples/function-sample-azure-blob-trigger/src/main/resources/host.json diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/src/main/resources/local.settings.json b/spring-cloud-function-samples/function-sample-azure-blob-trigger/src/main/resources/local.settings.json similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/src/main/resources/local.settings.json rename to spring-cloud-function-samples/function-sample-azure-blob-trigger/src/main/resources/local.settings.json diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/src/test/java/com/example/azure/di/azureblobtriggerdemo/AzureBlobTriggerDemoApplicationTests.java b/spring-cloud-function-samples/function-sample-azure-blob-trigger/src/test/java/com/example/azure/di/azureblobtriggerdemo/AzureBlobTriggerDemoApplicationTests.java similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-blob-trigger-demo/src/test/java/com/example/azure/di/azureblobtriggerdemo/AzureBlobTriggerDemoApplicationTests.java rename to spring-cloud-function-samples/function-sample-azure-blob-trigger/src/test/java/com/example/azure/di/azureblobtriggerdemo/AzureBlobTriggerDemoApplicationTests.java diff --git a/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/.gitignore b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/.gitignore new file mode 100644 index 000000000..c2065bc26 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/.gitignore @@ -0,0 +1,37 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/**/build/ +!**/src/test/**/build/ + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache +bin/ +!**/src/main/**/bin/ +!**/src/test/**/bin/ + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ +!**/src/main/**/out/ +!**/src/test/**/out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/README.adoc b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/README.adoc new file mode 100644 index 000000000..c6f4dd39b --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/README.adoc @@ -0,0 +1,49 @@ +== Spring Cloud Function on Azure - Gradle Example + +Show how to build SCF/Azure application with the Azure Function Gradle Plugin. + +IMPORTANT: For a general information about building and deploying `Azure Functions` with Spring Cloud Function, consult the https://docs.spring.io/spring-cloud-function/docs/current/reference/html/azure.html[Azure Adapter] documentation. + +=== Usage + +==== Package Staging folder + +Use the script below to package your staging folder: + +[source,shell] +---- +./gradlew azureFunctionsPackage +---- + +==== Run Azure Functions locally + +Use the script below to run the function locally. + +[source,shell] +---- +./gradlew azureFunctionsRun +---- + +Once up and running test with: + +[source,shell] +---- +curl -X POST http://localhost:7071/api/bean -d 'low case test' +---- + +should trigger an output like: `LOW CASE TEST%` + +TIP: To debug your functions, please add `localDebug = "transport=dt_socket,server=y,suspend=n,address=5005"` to the `azurefunctions` section of your `build.gradle`. + +==== Deploy Azure Functions to Azure Cloud + +[source,shell] +---- +./gradlew azureFunctionsDeploy +---- + +=== References + +- https://github.com/microsoft/azure-gradle-plugins/tree/master/azure-functions-gradle-plugin[Azure Functions Gradle Plugin] +- https://learn.microsoft.com/en-us/azure/azure-functions/functions-create-first-java-gradle[Use Java and Gradle to create and publish a function to Azure] +- https://docs.spring.io/spring-cloud-function/docs/current/reference/html/azure.html[Spring Cloud Function - Microsoft Azure Adapter] \ No newline at end of file diff --git a/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/build.gradle b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/build.gradle new file mode 100644 index 000000000..7e2b83995 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/build.gradle @@ -0,0 +1,61 @@ +plugins { + id 'java' + id 'io.spring.dependency-management' version '1.1.0' + id "com.microsoft.azure.azurefunctions" version "1.11.0" +} + +apply plugin: 'java' +apply plugin: "com.microsoft.azure.azurefunctions" + +group = 'org.scf.azure' +version = '0.0.1-SNAPSHOT' + +java { + sourceCompatibility = '17' + targetCompatibility = '17' + toolchain { + languageVersion = JavaLanguageVersion.of(17) + } +} + +jar { + manifest { + attributes( + // The main class is compulsory. Set it to point your SpringBootApplication. + "Main-Class": "org.scf.azure.gradle.GradleDemoApplication" + ) + } +} + +repositories { + mavenCentral() +} + +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + implementation "org.springframework.cloud:spring-cloud-function-adapter-azure:4.0.3" +} + +tasks.named('test') { + useJUnitPlatform() +} + +// Configuration options: https://github.com/microsoft/azure-gradle-plugins/wiki/Configuration +azurefunctions { + resourceGroup = 'java-functions-group' + appName = 'scff-azure-gradle-sample' + region = 'westus' + appServicePlanName = 'java-functions-app-service-plan' + pricingTier = 'EP1' + runtime { + os = 'linux' + javaVersion = '11' + } + auth { + type = 'azure_cli' + } + appSettings { + FUNCTIONS_EXTENSION_VERSION = '~4' + } + localDebug = "transport=dt_socket,server=y,suspend=n,address=5005" +} \ No newline at end of file diff --git a/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/gradle/wrapper/gradle-wrapper.jar b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 000000000..c1962a79e Binary files /dev/null and b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/gradle/wrapper/gradle-wrapper.jar differ diff --git a/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/gradle/wrapper/gradle-wrapper.properties b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000..37aef8d3f --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +networkTimeout=10000 +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/gradlew b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/gradlew new file mode 100755 index 000000000..aeb74cbb4 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/gradlew @@ -0,0 +1,245 @@ +#!/bin/sh + +# +# Copyright © 2015-2021 the original authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# +############################################################################## + +# Attempt to set APP_HOME + +# Resolve links: $0 may be a link +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac +done + +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD=maximum + +warn () { + echo "$*" +} >&2 + +die () { + echo + echo "$*" + echo + exit 1 +} >&2 + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD=$JAVA_HOME/jre/sh/java + else + JAVACMD=$JAVA_HOME/bin/java + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD=java + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac +fi + +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. + +# For Cygwin or MSYS, switch paths to Windows format before running java +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + + # Now convert the arguments - kludge to limit ourselves to /bin/sh + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) + fi + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg + done +fi + + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command; +# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of +# shell script including quotes and variable substitutions, so put them in +# double quotes to make sure that they get re-expanded; and +# * put everything else in single quotes, so that it's not re-expanded. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' + +exec "$JAVACMD" "$@" diff --git a/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/gradlew.bat b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/gradlew.bat new file mode 100644 index 000000000..6689b85be --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/gradlew.bat @@ -0,0 +1,92 @@ +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/host.json b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/host.json new file mode 100644 index 000000000..48be97cf3 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/host.json @@ -0,0 +1,7 @@ +{ + "version": "2.0", + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[3.*, 4.0.0)" + } +} \ No newline at end of file diff --git a/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/settings.gradle b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/settings.gradle new file mode 100644 index 000000000..69c977517 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'gradle-demo' diff --git a/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/src/main/java/org/scf/azure/gradle/GradleDemoApplication.java b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/src/main/java/org/scf/azure/gradle/GradleDemoApplication.java new file mode 100644 index 000000000..3d5902df8 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/src/main/java/org/scf/azure/gradle/GradleDemoApplication.java @@ -0,0 +1,60 @@ +package org.scf.azure.gradle; + +import java.util.Optional; +import java.util.function.Function; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.cloud.function.adapter.azure.AzureFunctionUtil; +import org.springframework.context.annotation.Bean; +import org.springframework.messaging.Message; + +@SpringBootApplication +public class GradleDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(GradleDemoApplication.class, args); + } + + /** + * Plain Spring bean (not Spring Cloud Functions!) + */ + @Autowired + private Function, String> uppercase; + + @FunctionName("bean") + public String plainBean( + @HttpTrigger(name = "req", methods = { HttpMethod.GET, + HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + ExecutionContext context) { + + // Inject the ExecutionContext as Message header + Message enhancedRequest = (Message) AzureFunctionUtil.enhanceInputIfNecessary( + request.getBody().get(), + context); + + return this.uppercase.apply(enhancedRequest); + } + + @Bean + public Function, String> uppercase() { + return message -> { + ExecutionContext context = (ExecutionContext) message.getHeaders().get(AzureFunctionUtil.EXECUTION_CONTEXT); + + String updatedPayload = message.getPayload().toUpperCase(); + + context.getLogger().info("Azure Test: " + updatedPayload); + + return message.getPayload().toUpperCase(); + }; + } + +} diff --git a/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/src/main/resources/application.properties b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/src/main/resources/application.properties new file mode 100644 index 000000000..1b45c68af --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle/src/main/resources/application.properties @@ -0,0 +1 @@ +spring.main.allow-circular-references=true \ No newline at end of file diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/.gitignore b/spring-cloud-function-samples/function-sample-azure-http-trigger/.gitignore similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/.gitignore rename to spring-cloud-function-samples/function-sample-azure-http-trigger/.gitignore diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-function-samples/function-sample-azure-http-trigger/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/.mvn/wrapper/maven-wrapper.jar rename to spring-cloud-function-samples/function-sample-azure-http-trigger/.mvn/wrapper/maven-wrapper.jar diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/.mvn/wrapper/maven-wrapper.properties b/spring-cloud-function-samples/function-sample-azure-http-trigger/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/.mvn/wrapper/maven-wrapper.properties rename to spring-cloud-function-samples/function-sample-azure-http-trigger/.mvn/wrapper/maven-wrapper.properties diff --git a/spring-cloud-function-samples/function-sample-azure-http-trigger/README.adoc b/spring-cloud-function-samples/function-sample-azure-http-trigger/README.adoc new file mode 100644 index 000000000..d5cbed220 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-http-trigger/README.adoc @@ -0,0 +1,94 @@ +== Azure Functions HTTP triggers sample + +IMPORTANT: For a general information about building and deploying `Azure Functions` with Spring Cloud Function, consult the https://docs.spring.io/spring-cloud-function/docs/current/reference/html/azure.html[Azure Adapter] documentation. + +Azure Functions may be invoked via HTTP requests to build serverless APIs. +Find more about the https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-http-webhook?tabs=in-process%2Cfunctionsv2&pivots=programming-language-java[HTTP triggers]. + +=== Usage + +==== Package Staging folder + +Use the script below to package your staging folder: + +[source,shell] +---- +./mvnw clean package +---- + +==== Run Azure Functions locally + +Use the script below to run the function locally. + +[source,shell] +---- +./mvnw azure-functions:run +---- + +NOTE: To run locally on top of `Azure Functions`, and to deploy to your live Azure environment, you will need `Azure Functions Core Tools` installed along with the Azure CLI (see https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-java?tabs=bash%2Cazure-cli%2Cbrowser#configure-your-local-environment[here]). + +NOTE: https://github.com/Azure/azure-functions-core-tools[Azure Functions Core Tools] version `4.0.5030` or newer is required! + +For some configuration you would need the https://learn.microsoft.com/en-us/azure/storage/common/storage-use-emulator[Azurite emulator] as well. + + +==== Deploy Azure Functions to Azure Cloud + +Make sure you are logged in your Azure account. + +[source,shell] +---- +az login +---- + +then build and deploy + +[source,shell] +---- +./mvnw clean package +./mvnw azure-functions:deploy +---- + + +==== Debug locally + +Run the function in debug mode. + +[source,shell] +---- +./mvnw azure-functions:run -DenableDebug +---- + +Alternatively and the `JAVA_OPTS` value to your `local.settings.json` like this: + +[source,json] +---- +{ + "IsEncrypted": false, + "Values": { + ... + "FUNCTIONS_WORKER_RUNTIME": "java", + "JAVA_OPTS": "-Djava.net.preferIPv4Stack=true -Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=127.0.0.1:5005" + } +} +---- + + +For VSCode remote debug use configuration like this: + +[source,json] +---- +{ + "version": "0.2.0", + "configurations": [ + { + "type": "java", + "name": "Attach to Remote Program", + "request": "attach", + "hostName": "localhost", + "port": "5005" + }, + ... + ] +} +---- \ No newline at end of file diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/mvnw b/spring-cloud-function-samples/function-sample-azure-http-trigger/mvnw similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/mvnw rename to spring-cloud-function-samples/function-sample-azure-http-trigger/mvnw diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/mvnw.cmd b/spring-cloud-function-samples/function-sample-azure-http-trigger/mvnw.cmd similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/mvnw.cmd rename to spring-cloud-function-samples/function-sample-azure-http-trigger/mvnw.cmd diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/pom.xml b/spring-cloud-function-samples/function-sample-azure-http-trigger/pom.xml similarity index 98% rename from spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/pom.xml rename to spring-cloud-function-samples/function-sample-azure-http-trigger/pom.xml index 950edd688..83d1997e2 100644 --- a/spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/pom.xml +++ b/spring-cloud-function-samples/function-sample-azure-http-trigger/pom.xml @@ -13,13 +13,13 @@ azure-httptrigger-demo 0.0.1-SNAPSHOT azure-httptrigger-demo - + Demo Spring Boot, Azure Function - HttpTrigger (DI adapter) 17 1.0.28.RELEASE - + com.example.azure.di.httptriggerdemo.HttpTriggerDemoApplication @@ -36,7 +36,7 @@ org.springframework.cloud spring-cloud-function-adapter-azure - 4.0.0 + 4.1.0-SNAPSHOT diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/src/main/java/com/example/azure/di/httptriggerdemo/HttpTriggerDemoApplication.java b/spring-cloud-function-samples/function-sample-azure-http-trigger/src/main/java/com/example/azure/di/httptriggerdemo/HttpTriggerDemoApplication.java similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/src/main/java/com/example/azure/di/httptriggerdemo/HttpTriggerDemoApplication.java rename to spring-cloud-function-samples/function-sample-azure-http-trigger/src/main/java/com/example/azure/di/httptriggerdemo/HttpTriggerDemoApplication.java diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/src/main/java/com/example/azure/di/httptriggerdemo/MyAzureFunction.java b/spring-cloud-function-samples/function-sample-azure-http-trigger/src/main/java/com/example/azure/di/httptriggerdemo/MyAzureFunction.java similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/src/main/java/com/example/azure/di/httptriggerdemo/MyAzureFunction.java rename to spring-cloud-function-samples/function-sample-azure-http-trigger/src/main/java/com/example/azure/di/httptriggerdemo/MyAzureFunction.java diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/src/main/resources/application.properties b/spring-cloud-function-samples/function-sample-azure-http-trigger/src/main/resources/application.properties similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/src/main/resources/application.properties rename to spring-cloud-function-samples/function-sample-azure-http-trigger/src/main/resources/application.properties diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/src/main/resources/host.json b/spring-cloud-function-samples/function-sample-azure-http-trigger/src/main/resources/host.json similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/src/main/resources/host.json rename to spring-cloud-function-samples/function-sample-azure-http-trigger/src/main/resources/host.json diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/src/test/java/com/example/azure/di/httptriggerdemo/HttptriggerDemoApplicationTests.java b/spring-cloud-function-samples/function-sample-azure-http-trigger/src/test/java/com/example/azure/di/httptriggerdemo/HttptriggerDemoApplicationTests.java similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-httptrigger-demo/src/test/java/com/example/azure/di/httptriggerdemo/HttptriggerDemoApplicationTests.java rename to spring-cloud-function-samples/function-sample-azure-http-trigger/src/test/java/com/example/azure/di/httptriggerdemo/HttptriggerDemoApplicationTests.java diff --git a/spring-cloud-function-samples/function-sample-azure-kafka-trigger/pom.xml b/spring-cloud-function-samples/function-sample-azure-kafka-trigger/pom.xml index f56c601ed..7ff36d5ce 100644 --- a/spring-cloud-function-samples/function-sample-azure-kafka-trigger/pom.xml +++ b/spring-cloud-function-samples/function-sample-azure-kafka-trigger/pom.xml @@ -16,8 +16,8 @@ Demo project for Spring Boot 17 - 2023.0.0-SNAPSHOT - 2.1.0 + 4.0.4 + 3.0.0 example.KafkaTriggerDemoApplication @@ -26,8 +26,9 @@ westeurope ${project.build.directory}/azure-functions/${functionAppName} java-functions-app-service-plan + 1.22.0 - + org.springframework.cloud @@ -37,19 +38,13 @@ spring-cloud-function-adapter-azure org.springframework.cloud - - - org.springframework.boot - spring-boot-starter-test - test - org.springframework.cloud spring-cloud-function-dependencies - ${spring-cloud-dependencies.version} + ${spring-cloud-function-dependencies.version} pom import @@ -63,16 +58,6 @@ - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${java.version} - ${java.version} - ${project.build.sourceEncoding} - - org.apache.maven.plugins maven-deploy-plugin @@ -80,36 +65,19 @@ true - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-dependencies - prepare-package - - copy-dependencies - - - ${stagingDirectory}/lib - false - false - true - runtime - azure-functions-java-library - - - - com.microsoft.azure azure-functions-maven-plugin - + ${azure.functions.maven.plugin.version} ${functionResourceGroup} ${functionAppName} ${functionAppRegion} ${functionAppServicePlanName} + + ${project.basedir}/src/main/azure/host.json + ${project.basedir}/src/main/azure/local.settings.json + linux 17 @@ -124,8 +92,8 @@ FUNCTIONS_WORKER_RUNTIME java - - + + package-functions @@ -135,45 +103,6 @@ - - maven-resources-plugin - - - copy-resources - package - - copy-resources - - - true - - ${stagingDirectory} - - - - ${project.basedir}/src/main/azure - - - ** - - - - - - - - - - maven-clean-plugin - 3.1.0 - - - - obj - - - - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - false - - - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - false - - - - diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/.gitignore b/spring-cloud-function-samples/function-sample-azure-time-trigger/.gitignore similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/.gitignore rename to spring-cloud-function-samples/function-sample-azure-time-trigger/.gitignore diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-function-samples/function-sample-azure-time-trigger/.mvn/wrapper/maven-wrapper.jar similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/.mvn/wrapper/maven-wrapper.jar rename to spring-cloud-function-samples/function-sample-azure-time-trigger/.mvn/wrapper/maven-wrapper.jar diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/.mvn/wrapper/maven-wrapper.properties b/spring-cloud-function-samples/function-sample-azure-time-trigger/.mvn/wrapper/maven-wrapper.properties similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/.mvn/wrapper/maven-wrapper.properties rename to spring-cloud-function-samples/function-sample-azure-time-trigger/.mvn/wrapper/maven-wrapper.properties diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/README.md b/spring-cloud-function-samples/function-sample-azure-time-trigger/README.adoc similarity index 60% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/README.md rename to spring-cloud-function-samples/function-sample-azure-time-trigger/README.adoc index 5b459e447..d8bc693d5 100644 --- a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/README.md +++ b/spring-cloud-function-samples/function-sample-azure-time-trigger/README.adoc @@ -1,64 +1,77 @@ -# Azure TimerTrigger Function +== Azure TimerTrigger Function -Spring Cloud Function example for implementing [Timer trigger for Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=in-process&pivots=programming-language-java). +IMPORTANT: For a general information about building and deploying `Azure Functions` with Spring Cloud Function, consult the https://docs.spring.io/spring-cloud-function/docs/current/reference/html/azure.html[Azure Adapter] documentation. -## Running Locally +Spring Cloud Function example for implementing https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=in-process&pivots=programming-language-java[Timer trigger for Azure Functions]. -NOTE: To run locally on top of `Azure Functions`, and to deploy to your live Azure environment, you will need `Azure Functions Core Tools` installed along with the Azure CLI (see [here](https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-java?tabs=bash%2Cazure-cli%2Cbrowser#configure-your-local-environment)) as well as the Use [Azurite emulator](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-emulator) for local Azure Storage development. For the emulator you can run a docker container (see below) or use the [Visual-Studio-Code extension](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio-code). +=== Running Locally + +NOTE: To run locally on top of `Azure Functions`, and to deploy to your live Azure environment, you will need `Azure Functions Core Tools` installed along with the Azure CLI (see https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-java?tabs=bash%2Cazure-cli%2Cbrowser#configure-your-local-environment[here]) as well as the Use https://learn.microsoft.com/en-us/azure/storage/common/storage-use-emulator[Azurite emulator] for local Azure Storage development. For the emulator you can run a docker container (see below) or use the https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio-code[Visual-Studio-Code extension]. Here is how ot start the `Azure emulator` as docker container: -``` +[source,shell] +---- + docker run --name azurite --rm -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite -``` +---- Then build and run the sample: -``` +[source,shell] +---- ./mvnw clean package ./mvnw azure-functions:run -``` +---- -The timer triggers the function every minute. +The timer triggers the function every minute. In result the the `uppercase` Spring Cloud Function is called and uppercase the timeInfo and logs it into the context. ``` [2022-10-11T08:53:00.011Z] Execution Context Log - TimeInfo: {"Schedule":{"AdjustForDST":true},"ScheduleStatus":{"Last":"2022-10-11T10:52:00.003967+02:00","Next":"2022-10-11T10:53:00+02:00","LastUpdated":"2022-10-11T10:52:00.003967+02:00"},"IsPastDue":false} ``` -## Running on Azure +The `executeExpRetry` handler demonstrates how to handle errors using the https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-error-pages?tabs=exponential-backoff%2Cin-process&pivots=programming-language-java#retry-policies[Retry policies]. +Sample emulates 3 errors on the first 3 executions and then continues as expected. + +=== Running on Azure Make sure you are logged in your Azure account. -``` + +[source,shell] +---- az login -``` +---- Build and deploy -``` +[source,shell] +---- ./mvnw clean package ./mvnw azure-functions:deploy -``` +---- -## Implementation details +=== Implementation details The `spring-cloud-function-adapter-azure` dependency activates the AzureFunctionInstanceInjector: -```xml +[source,xml] +---- org.springframework.cloud spring-cloud-function-adapter-azure 3.2.9-SNAPSHOT -``` +---- (Version 3.2.9 or higher) -The `uppercase` function with signature `Function, Void> uppercase()` is defined as `@Bean` in the TimeTriggerDemoApplication context. +The `uppercase` function with signature `Function, Void> uppercase()` is defined as `@Bean` in the TimeTriggerDemoApplication context. -```java +[source,java] +---- @Bean public Consumer> uppercase() { return message -> { @@ -74,13 +87,14 @@ The `uppercase` function with signature `Function, Void> upperca // No response. }; } -``` +---- TIP: The uppercase function does not return value (e.g. Void output type) and is backed by `java.util.Consumer`. The `UppercaseHandler` (marked as Spring `@Component`) implements the Azure function using the Azure Function Java API. Furthermore as Spring component the UppercaseHandler leverages the Spring configuration and programming model to inject the necessary services required by the functions. -```java +[source,java] +---- @Component public class UppercaseHandler { @@ -101,10 +115,9 @@ public class UppercaseHandler { this.uppercase.accept(message); } } -``` +---- +=== Notes -## Notes - -* Change the `spring-boot-maven-plugin` to `tiny` in favor of the `azure-functions-maven-plugin` jar packaging. -* Add `"AzureWebJobsStorage": "UseDevelopmentStorage=true"` to the `local.settings.json`. +- Change the `spring-boot-maven-plugin` to `tiny` in favor of the `azure-functions-maven-plugin` jar packaging. +- Add `"AzureWebJobsStorage": "UseDevelopmentStorage=true"` to the `local.settings.json`. diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/mvnw b/spring-cloud-function-samples/function-sample-azure-time-trigger/mvnw similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/mvnw rename to spring-cloud-function-samples/function-sample-azure-time-trigger/mvnw diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/mvnw.cmd b/spring-cloud-function-samples/function-sample-azure-time-trigger/mvnw.cmd similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/mvnw.cmd rename to spring-cloud-function-samples/function-sample-azure-time-trigger/mvnw.cmd diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/pom.xml b/spring-cloud-function-samples/function-sample-azure-time-trigger/pom.xml similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/pom.xml rename to spring-cloud-function-samples/function-sample-azure-time-trigger/pom.xml diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/main/java/com/example/azure/di/timetriggerdemo/TimeTriggerDemoApplication.java b/spring-cloud-function-samples/function-sample-azure-time-trigger/src/main/java/com/example/azure/di/timetriggerdemo/TimeTriggerDemoApplication.java similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/main/java/com/example/azure/di/timetriggerdemo/TimeTriggerDemoApplication.java rename to spring-cloud-function-samples/function-sample-azure-time-trigger/src/main/java/com/example/azure/di/timetriggerdemo/TimeTriggerDemoApplication.java diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/main/java/com/example/azure/di/timetriggerdemo/UppercaseHandler.java b/spring-cloud-function-samples/function-sample-azure-time-trigger/src/main/java/com/example/azure/di/timetriggerdemo/UppercaseHandler.java similarity index 60% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/main/java/com/example/azure/di/timetriggerdemo/UppercaseHandler.java rename to spring-cloud-function-samples/function-sample-azure-time-trigger/src/main/java/com/example/azure/di/timetriggerdemo/UppercaseHandler.java index 4e217edf6..ca3890206 100644 --- a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/main/java/com/example/azure/di/timetriggerdemo/UppercaseHandler.java +++ b/spring-cloud-function-samples/function-sample-azure-time-trigger/src/main/java/com/example/azure/di/timetriggerdemo/UppercaseHandler.java @@ -16,9 +16,12 @@ package com.example.azure.di.timetriggerdemo; +import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.annotation.ExponentialBackoffRetry; +import com.microsoft.azure.functions.annotation.FixedDelayRetry; import com.microsoft.azure.functions.annotation.FunctionName; import com.microsoft.azure.functions.annotation.TimerTrigger; @@ -32,10 +35,13 @@ public class UppercaseHandler { public static String EXECUTION_CONTEXT = "executionContext"; + private static AtomicInteger count = new AtomicInteger(); + @Autowired private Consumer> uppercase; @FunctionName("uppercase") + @FixedDelayRetry(maxRetryCount = 4, delayInterval = "00:00:10") public void execute(@TimerTrigger(name = "keepAliveTrigger", schedule = "0 */1 * * * *") String timerInfo, ExecutionContext context) { @@ -46,4 +52,24 @@ public class UppercaseHandler { this.uppercase.accept(message); } + + @FunctionName("uppercaseExpRetry") + @ExponentialBackoffRetry(maxRetryCount = 4, maximumInterval = "00:15:00", minimumInterval = "00:00:03") + public void executeExpRetry(@TimerTrigger(name = "keepAliveTrigger", schedule = "*/10 * * * * *") String timerInfo, + ExecutionContext context) { + + if (count.incrementAndGet() < 3) { + context.getLogger().info("EMULATE ERROR# " + count.get()); + throw new IllegalStateException("Emulated ERROR# " + count.get()); + } + + context.getLogger().info("ERRORLESS EXECUTION"); + + Message message = MessageBuilder + .withPayload(timerInfo) + .setHeader(EXECUTION_CONTEXT, context) + .build(); + + this.uppercase.accept(message); + } } diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/main/resources/application.properties b/spring-cloud-function-samples/function-sample-azure-time-trigger/src/main/resources/application.properties similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/main/resources/application.properties rename to spring-cloud-function-samples/function-sample-azure-time-trigger/src/main/resources/application.properties diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/main/resources/host.json b/spring-cloud-function-samples/function-sample-azure-time-trigger/src/main/resources/host.json similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/main/resources/host.json rename to spring-cloud-function-samples/function-sample-azure-time-trigger/src/main/resources/host.json diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/main/resources/local.settings.json b/spring-cloud-function-samples/function-sample-azure-time-trigger/src/main/resources/local.settings.json similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/main/resources/local.settings.json rename to spring-cloud-function-samples/function-sample-azure-time-trigger/src/main/resources/local.settings.json diff --git a/spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/test/java/com/example/azure/di/timetriggerdemo/TimetriggerDemoApplicationTests.java b/spring-cloud-function-samples/function-sample-azure-time-trigger/src/test/java/com/example/azure/di/timetriggerdemo/TimetriggerDemoApplicationTests.java similarity index 100% rename from spring-cloud-function-samples/function-azure-di-samples/azure-timetrigger-demo/src/test/java/com/example/azure/di/timetriggerdemo/TimetriggerDemoApplicationTests.java rename to spring-cloud-function-samples/function-sample-azure-time-trigger/src/test/java/com/example/azure/di/timetriggerdemo/TimetriggerDemoApplicationTests.java diff --git a/spring-cloud-function-samples/function-sample-azure-timer-trigger/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-function-samples/function-sample-azure-timer-trigger/.mvn/wrapper/maven-wrapper.jar deleted file mode 100644 index c1dd12f17..000000000 Binary files a/spring-cloud-function-samples/function-sample-azure-timer-trigger/.mvn/wrapper/maven-wrapper.jar and /dev/null differ diff --git a/spring-cloud-function-samples/function-sample-azure-timer-trigger/README.md b/spring-cloud-function-samples/function-sample-azure-timer-trigger/README.md deleted file mode 100644 index af856c669..000000000 --- a/spring-cloud-function-samples/function-sample-azure-timer-trigger/README.md +++ /dev/null @@ -1,93 +0,0 @@ -# Azure TimerTrigger Function - -Spring Cloud Function example for implementing [Timer trigger for Azure Functions](https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=in-process&pivots=programming-language-java). - -NOTE: JVM '17' is required. - -https://learn.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=in-process&pivots=programming-language-java - -## Running Locally - -NOTE: To run locally on top of Azure Functions, and to deploy to your live Azure environment, you will need Azure Functions Core Tools installed along with the Azure CLI (see [here](https://docs.microsoft.com/en-us/azure/azure-functions/create-first-function-cli-java?tabs=bash%2Cazure-cli%2Cbrowser#configure-your-local-environment) for details) as well as the Use [Azurite emulator](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-emulator) for local Azure Storage development. For the emulator you can run a docker container (see below) or use the [Visual-Studio-Code extension](https://learn.microsoft.com/en-us/azure/storage/common/storage-use-azurite?tabs=visual-studio-code). - -``` -docker run --name azurite --rm -p 10000:10000 -p 10001:10001 -p 10002:10002 mcr.microsoft.com/azure-storage/azurite -``` - -``` -./mvnw clean package -./mvnw azure-functions:run -``` - -The timer triggers the function every minute. -In result the the `uppercase` Spring Cloud Function is called and uppercase the timeInfo and logs it into the context. - -``` -[2022-10-11T08:53:00.011Z] Timer is triggered: {"Schedule":{"AdjustForDST":true},"ScheduleStatus":{"Last":"2022-10-11T10:52:00.003967+02:00","Next":"2022-10-11T10:53:00+02:00","LastUpdated":"2022-10-11T10:52:00.003967+02:00"},"IsPastDue":false} -``` - -## Running on Azure - -Make sure you are logged in your Azure account. -``` -az login -``` - -Build and deploy - -``` -./mvnw clean package -./mvnw azure-functions:deploy -``` - -## Implementation details - -The `uppercase` function signature is `Function, Void> uppercase()`. The implementation of `UppercaseHandler` (which extends `FunctionInvoker`) provides access to the Azure Function context via the _MessageHeaders_. - -NOTE: Implementation of `FunctionInvoker` (your handler), should contain the least amount of code. It is really a type-safe way to define -and configure function to be recognized as Azure Function. -Everything else should be delegated to the base `FunctionInvoker` via `handleRequest(..)` callback which will invoke your function, taking care of -necessary type conversion, transformation etc. One exception to this rule is when custom result handling is required. In that case, the proper post-process method can be overridden as well in order to take control of the results processing. - -`UppercaseHandler.java`: - -```java -public class UppercaseHandler extends FunctionInvoker, Void> { - - @FunctionName("uppercase") - public void execute(@TimerTrigger(name = "keepAliveTrigger", schedule = "0 */1 * * * *") String timerInfo, - ExecutionContext context) { - - Message message = MessageBuilder.withPayload(timerInfo).build(); - - handleRequest(message, context); - } -} -``` - -Note that this function does not return value (e.g. Void output type) and is backed by `java.util.Consumer` SCF implementation: - -```java - @Bean - public Consumer> uppercase() { - return message -> { - // /timeInfo is a JSON string, you can deserialize it to an object using your favorite JSON library - String timeInfo = message.getPayload(); - - // Business logic -> convert the timeInfo to uppercase. - String value = timeInfo.toUpperCase(); - - // (Optionally) access and use the Azure function context. - ExecutionContext context = (ExecutionContext) message.getHeaders().get("executionContext"); - context.getLogger().info("Timer is triggered with TimeInfo: " + value); - - // No response. - }; - } -``` - -## Notes - -* Disable the `spring-boot-maven-plugin` in favor of the `azure-functions-maven-plugin`. -* Exclude the `org.springframework.boot:spring-boot-starter-logging` dependency from the `org.springframework.cloud:spring-cloud-function-adapter-azure`. -* Add `"AzureWebJobsStorage": "UseDevelopmentStorage=true"` to the `local.settings.json`. diff --git a/spring-cloud-function-samples/function-sample-azure-timer-trigger/pom.xml b/spring-cloud-function-samples/function-sample-azure-timer-trigger/pom.xml deleted file mode 100644 index 0be0b96ea..000000000 --- a/spring-cloud-function-samples/function-sample-azure-timer-trigger/pom.xml +++ /dev/null @@ -1,221 +0,0 @@ - - - 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.2.0-SNAPSHOT - - - - example.scf.azure - timer-trigger-azure-spring-function - 0.0.1-SNAPSHOT - timer-trigger-demo - Demo project for Spring Boot - - 17 - 2023.0.0-SNAPSHOT - 2.1.0 - - example.TimerTriggerDemoApplication - - example-spring-function-resource-group - timer-trigger-azure-spring-function - westeurope - ${project.build.directory}/azure-functions/${functionAppName} - java-functions-app-service-plan - - - - - org.springframework.cloud - spring-cloud-function-context - - - spring-cloud-function-adapter-azure - org.springframework.cloud - - - - org.springframework.boot - spring-boot-starter-test - test - - - - - - org.springframework.cloud - spring-cloud-dependencies - ${spring-cloud.version} - pom - import - - - com.microsoft.azure.functions - azure-functions-java-library - ${azure.functions.java.core.version} - - - - - - - - org.apache.maven.plugins - maven-compiler-plugin - 3.8.1 - - ${java.version} - ${java.version} - ${project.build.sourceEncoding} - - - - org.apache.maven.plugins - maven-deploy-plugin - - true - - - - org.apache.maven.plugins - maven-dependency-plugin - - - copy-dependencies - prepare-package - - copy-dependencies - - - ${stagingDirectory}/lib - false - false - true - runtime - azure-functions-java-library - - - - - - com.microsoft.azure - azure-functions-maven-plugin - - - ${functionResourceGroup} - ${functionAppName} - ${functionAppRegion} - ${functionAppServicePlanName} - - linux - 17 - - - - - FUNCTIONS_EXTENSION_VERSION - ~4 - - - FUNCTIONS_WORKER_RUNTIME - java - - - - - - package-functions - - package - - - - - - maven-resources-plugin - - - copy-resources - package - - copy-resources - - - true - - ${stagingDirectory} - - - - ${project.basedir}/src/main/azure - - - ** - - - - - - - - - - maven-clean-plugin - 3.1.0 - - - - obj - - - - - - - - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - false - - - - - - spring-milestones - Spring Milestones - https://repo.spring.io/milestone - - false - - - - spring-snapshots - Spring Snapshots - https://repo.spring.io/snapshot - - false - - - - - diff --git a/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/azure/host.json b/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/azure/host.json deleted file mode 100644 index a1e0497a7..000000000 --- a/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/azure/host.json +++ /dev/null @@ -1,4 +0,0 @@ -{ - "functionTimeout": "00:05:00", - "version": "2.0" -} \ No newline at end of file diff --git a/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/azure/local.settings.json b/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/azure/local.settings.json deleted file mode 100644 index adce8b884..000000000 --- a/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/azure/local.settings.json +++ /dev/null @@ -1,8 +0,0 @@ -{ - "IsEncrypted": false, - "Values": { - "AzureWebJobsStorage": "UseDevelopmentStorage=true", - "AzureWebJobsDashboard": "", - "FUNCTIONS_WORKER_RUNTIME": "java" - } -} \ No newline at end of file diff --git a/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/java/example/TimerTriggerDemoApplication.java b/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/java/example/TimerTriggerDemoApplication.java deleted file mode 100644 index 0e5273cf1..000000000 --- a/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/java/example/TimerTriggerDemoApplication.java +++ /dev/null @@ -1,50 +0,0 @@ -/* - * Copyright 2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package example; - -import java.util.function.Consumer; - -import com.microsoft.azure.functions.ExecutionContext; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; -import org.springframework.context.annotation.Bean; -import org.springframework.messaging.Message; - -@SpringBootApplication -public class TimerTriggerDemoApplication { - - public static void main(String[] args) { - SpringApplication.run(TimerTriggerDemoApplication.class, args); - } - - @Bean - public Consumer> uppercase() { - return message -> { - // /timeInfo is a JSON string, you can deserialize it to an object using your favorite JSON library - String timeInfo = message.getPayload(); - - // Business logic -> convert the timeInfo to uppercase. - String value = timeInfo.toUpperCase(); - - // (Optionally) access and use the Azure function context. - ExecutionContext context = (ExecutionContext) message.getHeaders().get("executionContext"); - context.getLogger().info("Timer is triggered with TimeInfo: " + value); - - // No response. - }; - } -} diff --git a/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/java/example/UppercaseHandler.java b/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/java/example/UppercaseHandler.java deleted file mode 100644 index 3c8bb7246..000000000 --- a/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/java/example/UppercaseHandler.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright 2022 the original author or authors. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * https://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package example; - -import com.microsoft.azure.functions.ExecutionContext; -import com.microsoft.azure.functions.annotation.FunctionName; -import com.microsoft.azure.functions.annotation.TimerTrigger; - -import org.springframework.cloud.function.adapter.azure.FunctionInvoker; -import org.springframework.messaging.Message; -import org.springframework.messaging.support.MessageBuilder; - -public class UppercaseHandler extends FunctionInvoker, Void> { - - @FunctionName("uppercase") - public void execute(@TimerTrigger(name = "keepAliveTrigger", schedule = "0 */1 * * * *") String timerInfo, - ExecutionContext context) { - - Message message = MessageBuilder.withPayload(timerInfo).build(); - - handleRequest(message, context); - } -} diff --git a/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/resources/application.properties b/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/resources/application.properties deleted file mode 100644 index 8b1378917..000000000 --- a/spring-cloud-function-samples/function-sample-azure-timer-trigger/src/main/resources/application.properties +++ /dev/null @@ -1 +0,0 @@ - diff --git a/spring-cloud-function-samples/function-sample-azure-timer-trigger/.gitignore b/spring-cloud-function-samples/function-sample-azure-web/.gitignore similarity index 97% rename from spring-cloud-function-samples/function-sample-azure-timer-trigger/.gitignore rename to spring-cloud-function-samples/function-sample-azure-web/.gitignore index 7ed0d6b67..549e00a2a 100644 --- a/spring-cloud-function-samples/function-sample-azure-timer-trigger/.gitignore +++ b/spring-cloud-function-samples/function-sample-azure-web/.gitignore @@ -1,3 +1,4 @@ +HELP.md target/ !.mvn/wrapper/maven-wrapper.jar !**/src/main/**/target/ diff --git a/spring-cloud-function-samples/function-sample-azure-web/.mvn/wrapper/maven-wrapper.jar b/spring-cloud-function-samples/function-sample-azure-web/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 000000000..cb28b0e37 Binary files /dev/null and b/spring-cloud-function-samples/function-sample-azure-web/.mvn/wrapper/maven-wrapper.jar differ diff --git a/spring-cloud-function-samples/function-sample-azure-timer-trigger/.mvn/wrapper/maven-wrapper.properties b/spring-cloud-function-samples/function-sample-azure-web/.mvn/wrapper/maven-wrapper.properties similarity index 54% rename from spring-cloud-function-samples/function-sample-azure-timer-trigger/.mvn/wrapper/maven-wrapper.properties rename to spring-cloud-function-samples/function-sample-azure-web/.mvn/wrapper/maven-wrapper.properties index b74bf7fcd..462686e25 100644 --- a/spring-cloud-function-samples/function-sample-azure-timer-trigger/.mvn/wrapper/maven-wrapper.properties +++ b/spring-cloud-function-samples/function-sample-azure-web/.mvn/wrapper/maven-wrapper.properties @@ -1,2 +1,2 @@ -distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.6/apache-maven-3.8.6-bin.zip -wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar +distributionUrl=https://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.9.3/apache-maven-3.9.3-bin.zip +wrapperUrl=https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar diff --git a/spring-cloud-function-samples/function-sample-azure-web/README.adoc b/spring-cloud-function-samples/function-sample-azure-web/README.adoc new file mode 100644 index 000000000..a1da540de --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-web/README.adoc @@ -0,0 +1,66 @@ +== Spring Azure Web Adapter Demo + +A https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-adapters/spring-cloud-function-adapter-azure-web[spring-cloud-function-adapter-azure-web] adapter sample. + +This sample implements a standard Spring Boot Web application, with a REST API for managing a list of country entities. Later are persisted with JPA and H2 database. + +The `spring-cloud-function-adapter-azure-web` adapter provides a light-weight Azure Function forwarding proxy which allows deploying the existing Spring Boot Web application as a Azure Function. + +=== Usage + +==== Build + +[source,shell] +---- +./mvnw clean install +---- + +==== Run Locally + +[source,shell] +---- +./mvnw azure-functions:run +---- + +Then use `curl` to interact with the rest application: + +---- +curl -X GET http://localhost:7072/api/AzureWebAdapter/ +---- + +will output result like `Country Count: 0`. + +Then add few Countries: +---- +curl -X POST -H 'Content-Type:application/json' http://localhost:7072/api/AzureWebAdapter/countries -d '{"name" : "Bulgaria"}' +curl -X POST -H 'Content-Type:application/json' http://localhost:7072/api/AzureWebAdapter/countries -d '{"name" : "Netherlands"}' +curl -X POST -H 'Content-Type:application/json' http://localhost:7072/api/AzureWebAdapter/countries -d '{"name" : "Ukraine"}' +---- + +And check the count again: + +---- +curl -X GET http://localhost:7072/api/AzureWebAdapter/ +---- + +now the output is `Country Count: 3` and `curl -X GET http://localhost:7072/api/AzureWebAdapter/countries` will output: `Countries: Country{id=1, name='Bulgaria'}Country{id=2, name='Netherlands'}Country{id=3, name='Ukraine'}`. + +==== Running on Azure + +Make sure you are logged in your Azure account. + +---- +az login +---- + +and deploy + +---- +./mvnw azure-functions:deploy +---- + + + + + + diff --git a/spring-cloud-function-samples/function-sample-azure-timer-trigger/mvnw b/spring-cloud-function-samples/function-sample-azure-web/mvnw similarity index 51% rename from spring-cloud-function-samples/function-sample-azure-timer-trigger/mvnw rename to spring-cloud-function-samples/function-sample-azure-web/mvnw index 8a8fb2282..66df28542 100755 --- a/spring-cloud-function-samples/function-sample-azure-timer-trigger/mvnw +++ b/spring-cloud-function-samples/function-sample-azure-web/mvnw @@ -19,7 +19,7 @@ # ---------------------------------------------------------------------------- # ---------------------------------------------------------------------------- -# Maven Start Up Batch script +# Apache Maven Wrapper startup batch script, version 3.2.0 # # Required ENV vars: # ------------------ @@ -27,7 +27,6 @@ # # Optional ENV vars # ----------------- -# M2_HOME - location of maven2's installed home dir # MAVEN_OPTS - parameters passed to the Java VM when running Maven # e.g. to debug Maven itself, use # set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 @@ -54,7 +53,7 @@ fi cygwin=false; darwin=false; mingw=false -case "`uname`" in +case "$(uname)" in CYGWIN*) cygwin=true ;; MINGW*) mingw=true;; Darwin*) darwin=true @@ -62,9 +61,9 @@ case "`uname`" in # See https://developer.apple.com/library/mac/qa/qa1170/_index.html if [ -z "$JAVA_HOME" ]; then if [ -x "/usr/libexec/java_home" ]; then - export JAVA_HOME="`/usr/libexec/java_home`" + JAVA_HOME="$(/usr/libexec/java_home)"; export JAVA_HOME else - export JAVA_HOME="/Library/Java/Home" + JAVA_HOME="/Library/Java/Home"; export JAVA_HOME fi fi ;; @@ -72,68 +71,38 @@ esac if [ -z "$JAVA_HOME" ] ; then if [ -r /etc/gentoo-release ] ; then - JAVA_HOME=`java-config --jre-home` + JAVA_HOME=$(java-config --jre-home) fi fi -if [ -z "$M2_HOME" ] ; then - ## resolve links - $0 may be a link to maven's home - PRG="$0" - - # need this for relative symlinks - while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG="`dirname "$PRG"`/$link" - fi - done - - saveddir=`pwd` - - M2_HOME=`dirname "$PRG"`/.. - - # make it fully qualified - M2_HOME=`cd "$M2_HOME" && pwd` - - cd "$saveddir" - # echo Using m2 at $M2_HOME -fi - # For Cygwin, ensure paths are in UNIX format before anything is touched if $cygwin ; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --unix "$M2_HOME"` [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --unix "$JAVA_HOME"` + JAVA_HOME=$(cygpath --unix "$JAVA_HOME") [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --unix "$CLASSPATH"` + CLASSPATH=$(cygpath --path --unix "$CLASSPATH") fi # For Mingw, ensure paths are in UNIX format before anything is touched if $mingw ; then - [ -n "$M2_HOME" ] && - M2_HOME="`(cd "$M2_HOME"; pwd)`" - [ -n "$JAVA_HOME" ] && - JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`" + [ -n "$JAVA_HOME" ] && [ -d "$JAVA_HOME" ] && + JAVA_HOME="$(cd "$JAVA_HOME" || (echo "cannot cd into $JAVA_HOME."; exit 1); pwd)" fi if [ -z "$JAVA_HOME" ]; then - javaExecutable="`which javac`" - if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then + javaExecutable="$(which javac)" + if [ -n "$javaExecutable" ] && ! [ "$(expr "\"$javaExecutable\"" : '\([^ ]*\)')" = "no" ]; then # readlink(1) is not available as standard on Solaris 10. - readLink=`which readlink` - if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then + readLink=$(which readlink) + if [ ! "$(expr "$readLink" : '\([^ ]*\)')" = "no" ]; then if $darwin ; then - javaHome="`dirname \"$javaExecutable\"`" - javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + javaHome="$(dirname "\"$javaExecutable\"")" + javaExecutable="$(cd "\"$javaHome\"" && pwd -P)/javac" else - javaExecutable="`readlink -f \"$javaExecutable\"`" + javaExecutable="$(readlink -f "\"$javaExecutable\"")" fi - javaHome="`dirname \"$javaExecutable\"`" - javaHome=`expr "$javaHome" : '\(.*\)/bin'` + javaHome="$(dirname "\"$javaExecutable\"")" + javaHome=$(expr "$javaHome" : '\(.*\)/bin') JAVA_HOME="$javaHome" export JAVA_HOME fi @@ -149,7 +118,7 @@ if [ -z "$JAVACMD" ] ; then JAVACMD="$JAVA_HOME/bin/java" fi else - JAVACMD="`\\unset -f command; \\command -v java`" + JAVACMD="$(\unset -f command 2>/dev/null; \command -v java)" fi fi @@ -163,12 +132,9 @@ if [ -z "$JAVA_HOME" ] ; then echo "Warning: JAVA_HOME environment variable is not set." fi -CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher - # traverses directory structure from process work directory to filesystem root # first directory with .mvn subdirectory is considered project base directory find_maven_basedir() { - if [ -z "$1" ] then echo "Path not specified to find_maven_basedir" @@ -184,96 +150,99 @@ find_maven_basedir() { fi # workaround for JBEAP-8937 (on Solaris 10/Sparc) if [ -d "${wdir}" ]; then - wdir=`cd "$wdir/.."; pwd` + wdir=$(cd "$wdir/.." || exit 1; pwd) fi # end of workaround done - echo "${basedir}" + printf '%s' "$(cd "$basedir" || exit 1; pwd)" } # concatenates all lines of a file concat_lines() { if [ -f "$1" ]; then - echo "$(tr -s '\n' ' ' < "$1")" + # Remove \r in case we run on Windows within Git Bash + # and check out the repository with auto CRLF management + # enabled. Otherwise, we may read lines that are delimited with + # \r\n and produce $'-Xarg\r' rather than -Xarg due to word + # splitting rules. + tr -s '\r\n' ' ' < "$1" fi } -BASE_DIR=`find_maven_basedir "$(pwd)"` +log() { + if [ "$MVNW_VERBOSE" = true ]; then + printf '%s\n' "$1" + fi +} + +BASE_DIR=$(find_maven_basedir "$(dirname "$0")") if [ -z "$BASE_DIR" ]; then exit 1; fi +MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}; export MAVEN_PROJECTBASEDIR +log "$MAVEN_PROJECTBASEDIR" + ########################################################################################## # Extension to allow automatically downloading the maven-wrapper.jar from Maven-central # This allows using the maven wrapper in projects that prohibit checking in binary data. ########################################################################################## -if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found .mvn/wrapper/maven-wrapper.jar" - fi +wrapperJarPath="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" +if [ -r "$wrapperJarPath" ]; then + log "Found $wrapperJarPath" else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..." - fi + log "Couldn't find $wrapperJarPath, downloading it ..." + if [ -n "$MVNW_REPOURL" ]; then - jarUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + wrapperUrl="$MVNW_REPOURL/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" else - jarUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + wrapperUrl="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" fi - while IFS="=" read key value; do - case "$key" in (wrapperUrl) jarUrl="$value"; break ;; + while IFS="=" read -r key value; do + # Remove '\r' from value to allow usage on windows as IFS does not consider '\r' as a separator ( considers space, tab, new line ('\n'), and custom '=' ) + safeValue=$(echo "$value" | tr -d '\r') + case "$key" in (wrapperUrl) wrapperUrl="$safeValue"; break ;; esac - done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties" - if [ "$MVNW_VERBOSE" = true ]; then - echo "Downloading from: $jarUrl" - fi - wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" + done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" + log "Downloading from: $wrapperUrl" + if $cygwin; then - wrapperJarPath=`cygpath --path --windows "$wrapperJarPath"` + wrapperJarPath=$(cygpath --path --windows "$wrapperJarPath") fi if command -v wget > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found wget ... using wget" - fi + log "Found wget ... using wget" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--quiet" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - wget "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + wget $QUIET "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" else - wget --http-user=$MVNW_USERNAME --http-password=$MVNW_PASSWORD "$jarUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" + wget $QUIET --http-user="$MVNW_USERNAME" --http-password="$MVNW_PASSWORD" "$wrapperUrl" -O "$wrapperJarPath" || rm -f "$wrapperJarPath" fi elif command -v curl > /dev/null; then - if [ "$MVNW_VERBOSE" = true ]; then - echo "Found curl ... using curl" - fi + log "Found curl ... using curl" + [ "$MVNW_VERBOSE" = true ] && QUIET="" || QUIET="--silent" if [ -z "$MVNW_USERNAME" ] || [ -z "$MVNW_PASSWORD" ]; then - curl -o "$wrapperJarPath" "$jarUrl" -f + curl $QUIET -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" else - curl --user $MVNW_USERNAME:$MVNW_PASSWORD -o "$wrapperJarPath" "$jarUrl" -f + curl $QUIET --user "$MVNW_USERNAME:$MVNW_PASSWORD" -o "$wrapperJarPath" "$wrapperUrl" -f -L || rm -f "$wrapperJarPath" fi - else - if [ "$MVNW_VERBOSE" = true ]; then - echo "Falling back to using Java to download" - fi - javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java" + log "Falling back to using Java to download" + javaSource="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.java" + javaClass="$MAVEN_PROJECTBASEDIR/.mvn/wrapper/MavenWrapperDownloader.class" # For Cygwin, switch paths to Windows format before running javac if $cygwin; then - javaClass=`cygpath --path --windows "$javaClass"` + javaSource=$(cygpath --path --windows "$javaSource") + javaClass=$(cygpath --path --windows "$javaClass") fi - if [ -e "$javaClass" ]; then - if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Compiling MavenWrapperDownloader.java ..." - fi - # Compiling the Java class - ("$JAVA_HOME/bin/javac" "$javaClass") + if [ -e "$javaSource" ]; then + if [ ! -e "$javaClass" ]; then + log " - Compiling MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/javac" "$javaSource") fi - if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then - # Running the downloader - if [ "$MVNW_VERBOSE" = true ]; then - echo " - Running MavenWrapperDownloader.java ..." - fi - ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR") + if [ -e "$javaClass" ]; then + log " - Running MavenWrapperDownloader.java ..." + ("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$wrapperUrl" "$wrapperJarPath") || rm -f "$wrapperJarPath" fi fi fi @@ -282,35 +251,58 @@ fi # End of extension ########################################################################################## -export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} -if [ "$MVNW_VERBOSE" = true ]; then - echo $MAVEN_PROJECTBASEDIR +# If specified, validate the SHA-256 sum of the Maven wrapper jar file +wrapperSha256Sum="" +while IFS="=" read -r key value; do + case "$key" in (wrapperSha256Sum) wrapperSha256Sum=$value; break ;; + esac +done < "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.properties" +if [ -n "$wrapperSha256Sum" ]; then + wrapperSha256Result=false + if command -v sha256sum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | sha256sum -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + elif command -v shasum > /dev/null; then + if echo "$wrapperSha256Sum $wrapperJarPath" | shasum -a 256 -c > /dev/null 2>&1; then + wrapperSha256Result=true + fi + else + echo "Checksum validation was requested but neither 'sha256sum' or 'shasum' are available." + echo "Please install either command, or disable validation by removing 'wrapperSha256Sum' from your maven-wrapper.properties." + exit 1 + fi + if [ $wrapperSha256Result = false ]; then + echo "Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised." >&2 + echo "Investigate or delete $wrapperJarPath to attempt a clean download." >&2 + echo "If you updated your Maven version, you need to update the specified wrapperSha256Sum property." >&2 + exit 1 + fi fi + MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS" # For Cygwin, switch paths to Windows format before running java if $cygwin; then - [ -n "$M2_HOME" ] && - M2_HOME=`cygpath --path --windows "$M2_HOME"` [ -n "$JAVA_HOME" ] && - JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"` + JAVA_HOME=$(cygpath --path --windows "$JAVA_HOME") [ -n "$CLASSPATH" ] && - CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + CLASSPATH=$(cygpath --path --windows "$CLASSPATH") [ -n "$MAVEN_PROJECTBASEDIR" ] && - MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` + MAVEN_PROJECTBASEDIR=$(cygpath --path --windows "$MAVEN_PROJECTBASEDIR") fi # Provide a "standardized" way to retrieve the CLI args that will # work with both Windows and non-Windows executions. -MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $@" +MAVEN_CMD_LINE_ARGS="$MAVEN_CONFIG $*" export MAVEN_CMD_LINE_ARGS WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain +# shellcheck disable=SC2086 # safe args exec "$JAVACMD" \ $MAVEN_OPTS \ $MAVEN_DEBUG_OPTS \ -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ - "-Dmaven.home=${M2_HOME}" \ "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/spring-cloud-function-samples/function-sample-azure-timer-trigger/mvnw.cmd b/spring-cloud-function-samples/function-sample-azure-web/mvnw.cmd similarity index 82% rename from spring-cloud-function-samples/function-sample-azure-timer-trigger/mvnw.cmd rename to spring-cloud-function-samples/function-sample-azure-web/mvnw.cmd index 1d8ab018e..95ba6f54a 100644 --- a/spring-cloud-function-samples/function-sample-azure-timer-trigger/mvnw.cmd +++ b/spring-cloud-function-samples/function-sample-azure-web/mvnw.cmd @@ -18,13 +18,12 @@ @REM ---------------------------------------------------------------------------- @REM ---------------------------------------------------------------------------- -@REM Maven Start Up Batch script +@REM Apache Maven Wrapper startup batch script, version 3.2.0 @REM @REM Required ENV vars: @REM JAVA_HOME - location of a JDK home dir @REM @REM Optional ENV vars -@REM M2_HOME - location of maven2's installed home dir @REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands @REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a keystroke before ending @REM MAVEN_OPTS - parameters passed to the Java VM when running Maven @@ -120,10 +119,10 @@ SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe" set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar" set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain -set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" +set WRAPPER_URL="https://repo.maven.apache.org/maven2/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( - IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B + IF "%%A"=="wrapperUrl" SET WRAPPER_URL=%%B ) @REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central @@ -134,11 +133,11 @@ if exist %WRAPPER_JAR% ( ) ) else ( if not "%MVNW_REPOURL%" == "" ( - SET DOWNLOAD_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.1.0/maven-wrapper-3.1.0.jar" + SET WRAPPER_URL="%MVNW_REPOURL%/org/apache/maven/wrapper/maven-wrapper/3.2.0/maven-wrapper-3.2.0.jar" ) if "%MVNW_VERBOSE%" == "true" ( echo Couldn't find %WRAPPER_JAR%, downloading it ... - echo Downloading from: %DOWNLOAD_URL% + echo Downloading from: %WRAPPER_URL% ) powershell -Command "&{"^ @@ -146,7 +145,7 @@ if exist %WRAPPER_JAR% ( "if (-not ([string]::IsNullOrEmpty('%MVNW_USERNAME%') -and [string]::IsNullOrEmpty('%MVNW_PASSWORD%'))) {"^ "$webclient.Credentials = new-object System.Net.NetworkCredential('%MVNW_USERNAME%', '%MVNW_PASSWORD%');"^ "}"^ - "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"^ + "[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; $webclient.DownloadFile('%WRAPPER_URL%', '%WRAPPER_JAR%')"^ "}" if "%MVNW_VERBOSE%" == "true" ( echo Finished downloading %WRAPPER_JAR% @@ -154,6 +153,24 @@ if exist %WRAPPER_JAR% ( ) @REM End of extension +@REM If specified, validate the SHA-256 sum of the Maven wrapper jar file +SET WRAPPER_SHA_256_SUM="" +FOR /F "usebackq tokens=1,2 delims==" %%A IN ("%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties") DO ( + IF "%%A"=="wrapperSha256Sum" SET WRAPPER_SHA_256_SUM=%%B +) +IF NOT %WRAPPER_SHA_256_SUM%=="" ( + powershell -Command "&{"^ + "$hash = (Get-FileHash \"%WRAPPER_JAR%\" -Algorithm SHA256).Hash.ToLower();"^ + "If('%WRAPPER_SHA_256_SUM%' -ne $hash){"^ + " Write-Output 'Error: Failed to validate Maven wrapper SHA-256, your Maven wrapper might be compromised.';"^ + " Write-Output 'Investigate or delete %WRAPPER_JAR% to attempt a clean download.';"^ + " Write-Output 'If you updated your Maven version, you need to update the specified wrapperSha256Sum property.';"^ + " exit 1;"^ + "}"^ + "}" + if ERRORLEVEL 1 goto error +) + @REM Provide a "standardized" way to retrieve the CLI args that will @REM work with both Windows and non-Windows executions. set MAVEN_CMD_LINE_ARGS=%* diff --git a/spring-cloud-function-samples/function-sample-azure-web/pom.xml b/spring-cloud-function-samples/function-sample-azure-web/pom.xml new file mode 100644 index 000000000..b24acfd90 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-web/pom.xml @@ -0,0 +1,156 @@ + + + 4.0.0 + + org.springframework.boot + spring-boot-starter-parent + 3.2.0-SNAPSHOT + + + + com.example + azure-web-demo + 0.0.1-SNAPSHOT + azure-web-demo + Spring Cloud Function - Azure Function Web Adapter Demo + + + + 17 + + 1.0.28.RELEASE + 4.1.0-SNAPSHOT + + + com.example.azure.web.AzureWebDemoApplication + + + 1.22.0 + scf-azure-web-sample + westus + java-functions-group + java-functions-app-service-plan + EP1 + + + + + + + + org.springframework.cloud + spring-cloud-function-adapter-azure-web + ${spring-cloud-function-adapter-azure-web.version} + + + + + org.springframework.boot + spring-boot-starter-web + + + + org.springframework.boot + spring-boot-starter-data-jpa + + + + org.springframework.boot + spring-boot-starter-validation + + + + com.h2database + h2 + runtime + + + + org.springframework.boot + spring-boot-starter-actuator + + + + + + + + + org.apache.maven.plugins + maven-deploy-plugin + + true + + + + com.microsoft.azure + azure-functions-maven-plugin + ${azure.functions.maven.plugin.version} + + + ${functionAppName} + ${functionResourceGroup} + ${functionAppRegion} + ${functionAppServicePlanName} + ${functionPricingTier} + + ${project.basedir}/src/main/resources/host.json + + + linux + 17 + + + 7072 + + + + FUNCTIONS_EXTENSION_VERSION + ~4 + + + + + + package-functions + + package + + + + + + + org.springframework.boot + spring-boot-maven-plugin + + + org.springframework.boot.experimental + spring-boot-thin-layout + ${spring-boot-thin-layout.version} + + + + + + + + + + spring-snapshot + https://repo.spring.io/snapshot + + true + + + false + + + + spring-milestone + https://repo.spring.io/milestone + + + + diff --git a/spring-cloud-function-samples/function-sample-azure-web/src/main/java/com/example/azure/web/AzureWebDemoApplication.java b/spring-cloud-function-samples/function-sample-azure-web/src/main/java/com/example/azure/web/AzureWebDemoApplication.java new file mode 100644 index 000000000..c2e09a03b --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-web/src/main/java/com/example/azure/web/AzureWebDemoApplication.java @@ -0,0 +1,15 @@ +package com.example.azure.web; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaRepositories; + +@SpringBootApplication +@EnableJpaRepositories +public class AzureWebDemoApplication { + + public static void main(String[] args) { + SpringApplication.run(AzureWebDemoApplication.class, args); + } + +} diff --git a/spring-cloud-function-samples/function-sample-azure-web/src/main/java/com/example/azure/web/Country.java b/spring-cloud-function-samples/function-sample-azure-web/src/main/java/com/example/azure/web/Country.java new file mode 100644 index 000000000..2fbecb4bd --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-web/src/main/java/com/example/azure/web/Country.java @@ -0,0 +1,79 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.azure.web; + +import java.util.Objects; + +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Table; + +/** + * + * @author Christian Tzolov + */ + +@Table(name = "COUNTRIES") +@Entity +public class Country { + + @Id + @GeneratedValue + private Long id; + + private String name; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + @Override + public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + Country country = (Country) o; + return id == country.id && name.equals(country.name); + } + + @Override + public int hashCode() { + return Objects.hash(id, name); + } + + @Override + public String toString() { + return "Country{" + + "id=" + id + + ", name='" + name + '\'' + + '}'; + } +} \ No newline at end of file diff --git a/spring-cloud-function-samples/function-sample-azure-web/src/main/java/com/example/azure/web/CountryController.java b/spring-cloud-function-samples/function-sample-azure-web/src/main/java/com/example/azure/web/CountryController.java new file mode 100644 index 000000000..f6d16f918 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-web/src/main/java/com/example/azure/web/CountryController.java @@ -0,0 +1,65 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.azure.web; + +import java.util.stream.Collectors; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.util.StringUtils; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +/** + * + * @author Christian Tzolov + */ +@RestController +public class CountryController { + + @Autowired + private CountryRepository countryRepository; + + @GetMapping("/") + public String index() { + return "Country Count: " + countryRepository.count(); + } + + @GetMapping("/countries") + public String allCountries() { + String countries = this.countryRepository.findAll().stream() + .map(country -> country.toString()) + .collect(Collectors.joining()); + + return "Countries: " + countries; + } + + @PostMapping(path = "/countries") + public Country addCountry(@RequestBody Country country) { + if (!StringUtils.hasText(country.getName())) { + return null; + } + return this.countryRepository.save(country); + } + + @GetMapping("/countries/{id}") + public Country countryById(@PathVariable Integer id) { + return this.countryRepository.findById(id).get(); + } +} diff --git a/spring-cloud-function-samples/function-sample-azure-web/src/main/java/com/example/azure/web/CountryRepository.java b/spring-cloud-function-samples/function-sample-azure-web/src/main/java/com/example/azure/web/CountryRepository.java new file mode 100644 index 000000000..3f08e010b --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-web/src/main/java/com/example/azure/web/CountryRepository.java @@ -0,0 +1,26 @@ +/* + * Copyright 2023-2023 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.example.azure.web; + +import org.springframework.data.jpa.repository.JpaRepository; + +/** + * + * @author Christian Tzolov + */ +public interface CountryRepository extends JpaRepository { +} \ No newline at end of file diff --git a/spring-cloud-function-samples/function-sample-azure-web/src/main/resources/application.properties b/spring-cloud-function-samples/function-sample-azure-web/src/main/resources/application.properties new file mode 100644 index 000000000..2226f0b85 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-web/src/main/resources/application.properties @@ -0,0 +1,6 @@ +spring.datasource.url=jdbc:h2:mem:testdb +spring.datasource.driverClassName=org.h2.Driver +spring.datasource.username=sa +spring.datasource.password=password +spring.jpa.database-platform=org.hibernate.dialect.H2Dialect +spring.jpa.defer-datasource-initialization=true \ No newline at end of file diff --git a/spring-cloud-function-samples/function-sample-azure-web/src/main/resources/host.json b/spring-cloud-function-samples/function-sample-azure-web/src/main/resources/host.json new file mode 100644 index 000000000..10d0c0748 --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure-web/src/main/resources/host.json @@ -0,0 +1,7 @@ +{ + "version": "2.0", + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[3.*, 4.0.0)" + } +} \ No newline at end of file diff --git a/spring-cloud-function-samples/function-sample-azure/README.adoc b/spring-cloud-function-samples/function-sample-azure/README.adoc index 65501dd64..f9812e70d 100644 --- a/spring-cloud-function-samples/function-sample-azure/README.adoc +++ b/spring-cloud-function-samples/function-sample-azure/README.adoc @@ -1,4 +1,178 @@ -== Running Locally ` +== (Legacy) FunctionInvoker Integration + +WARNING: The legacy `FunctionInvoker` programming model is deprecated and will not be supported going forward. +For up to date samples, aligned with the dependency-injections https://docs.spring.io/spring-cloud-function/docs/current/reference/html/azure.html#_microsoft_azure[Azure Adapter], check the: https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-blob-trigger[Blob Trigger], https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-http-trigger[HTTP Trigger], https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-timer-trigger[Timer Trigger], https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-kafka-trigger[ Kafka Trigger & Output Binding]. +For a Gradle project example check the https://github.com/spring-cloud/spring-cloud-function/tree/main/spring-cloud-function-samples/function-sample-azure-http-trigger-gradle[ HTTP Trigger with Gradle]. + +=== Overview +The https://azure.microsoft.com[Azure] adapter bootstraps a Spring Cloud Function context and channels function calls from the Azure +framework into the user functions, using Spring Boot configuration where necessary. Azure Functions has quite a unique and +invasive programming model, involving annotations in user code that are specific to the Azure platform. +However, it is important to understand that because of the style of integration provided by Spring Cloud Function, specifically `org.springframework.cloud.function.adapter.azure.FunctionInvoker`, this annotation-based programming model is simply a type-safe way to configure +your simple java function (function that has no awareness of Azure) to be recognized as Azure function. +All you need to do is create a handler that extends `FunctionInvoker`, define and configure your function handler method and +make a callback to `handleRequest(..)` method. This handler method provides input and output types as annotated method parameters +(enabling Azure to inspect the class and create JSON bindings). + + +[source,java] +---- +public class UppercaseHandler extends FunctionInvoker, String> { + + @FunctionName("uppercase") + public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET, + HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + ExecutionContext context) { + Message message = MessageBuilder.withPayload(request.getBody().get()).copyHeaders(request.getHeaders()).build(); + return handleRequest(message, context); + } +} +---- + +Note that aside form providing configuration via Azure annotation we create an instance of `Message` inside the body of this handler method and make a callback to `handleRequest(..)` method returning its result. + +The actual user function you're delegating to looks like this + +[source,java] +---- +@Bean +public Function uppercase() { + return payload -> payload.toUpperCase(); +} + +OR + +@Bean +public Function, String> uppercase() { + return message -> message.getPayload().toUpperCase(); +} +---- + +Note that when creating a Message you can copy HTTP headers effectively making them available to you if necessary. + +The `org.springframework.cloud.function.adapter.azure.FunctionInvoker` class has two useful +methods (`handleRequest` and `handleOutput`) to which you can delegate the actual function call, so mostly the function will only ever have one line. + +The function name (definition) will be retrieved from Azure's `ExecutionContext.getFunctionName()` method, effectively supporting multiple function in the application context. + +==== Accessing Azure ExecutionContext + +Some time there is a need to access the target execution context provided by the Azure runtime in the form of `com.microsoft.azure.functions.ExecutionContext`. +For example one of such needs is logging, so it can appear in the Azure console. + +For that purpose the FunctionInvoker will add an instance of the `ExecutionContext` as a Message header so you can retrieve it via `executionContext` key. + +``` +@Bean +public Function, String> uppercase(JsonMapper mapper) { + return message -> { + String value = message.getPayload(); + ExecutionContext context = (ExecutionContext) message.getHeaders().get("executionContext"); + . . . + } +} +``` + +==== Notes on JAR Layout + +You don't need the Spring Cloud Function Web at runtime in Azure, so you can exclude this +before you create the JAR you deploy to Azure, but it won't be used if you include it, so +it doesn't hurt to leave it in. A function application on Azure is an archive generated by + the Maven plugin. The function lives in the JAR file generated by this project. + The sample creates it as an executable jar, using the thin layout, so that Azure can find + the handler classes. If you prefer you can just use a regular flat JAR file. + The dependencies should *not* be included. + +==== Build file setup + +In order to run Spring Cloud Function applications on Microsoft Azure, you can leverage the Maven +plugin offered by the cloud platform provider. + +In order to use the adapter plugin for Maven, add the plugin dependency to your `pom.xml` +file: + +[source,xml] +---- + + + org.springframework.cloud + spring-cloud-function-adapter-azure + + +---- + +Then, configure the plugin. You will need to provide Azure-specific configuration for your +application, specifying the `resourceGroup`, `appName` and other optional properties, and + add the `package` goal execution so that the `function.json` file required by Azure is + generated for you. Full plugin documentation can be found in the https://github.com/microsoft/azure-maven-plugins[plugin repository]. + +[source,xml] +---- + + com.microsoft.azure + azure-functions-maven-plugin + + ${functionResourceGroup} + ${functionAppName} + + + + package-functions + + package + + + + +---- + +You will also have to ensure that the files to be scanned by the plugin can be found in the +Azure functions staging directory (see the https://github.com/microsoft/azure-maven-plugins[plugin repository] + for more details on the staging directory and it's default location). + +You can find the entire sample `pom.xml` file for deploying Spring Cloud Function +applications to Microsoft Azure with Maven https://github.com/spring-cloud/spring-cloud-function/blob/{branch}/spring-cloud-function-samples/function-sample-azure/pom.xml[here]. + +NOTE: As of yet, only Maven plugin is available. Gradle plugin has not been created by +the cloud platform provider. + +==== Build + +---- +./mvnw -U clean package +---- + +==== Running the sample + +You can run the sample locally, just like the other Spring Cloud Function samples: + +--- +./mvnw spring-boot:run +--- + +and `curl -H "Content-Type: text/plain" localhost:8080/api/uppercase -d '{"value": "hello foobar"}'`. + +You will need the `az` CLI app (see https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-java-maven for more detail). To deploy the function on Azure runtime: + +---- +$ az login +$ mvn azure-functions:deploy +---- + +On another terminal try this: `curl https:///api/uppercase -d '{"value": "hello foobar!"}'`. Please ensure that you use the right URL for the function above. Alternatively you can test the function in the Azure Dashboard UI (click on the function name, go to the right hand side and click "Test" and to the bottom right, "Run"). + +The input type for the function in the Azure sample is a Foo with a single property called "value". So you need this to test it with something like below: + +---- +{ + "value": "foobar" +} +---- + +NOTE: The Azure sample app is written in the "non-functional" style (using `@Bean`). The functional style (with just `Function` or `ApplicationContextInitializer`) is much faster on startup in Azure than the traditional `@Bean` style, so if you don't need `@Beans` (or `@EnableAutoConfiguration`) it's a good choice. Warm starts are not affected. + + +== Running Sample Locally You can run this Azure function locally, similar to other Spring Cloud Function samples, however this time by using the Azure Maven plugin, as the Microsoft Azure functions execution context must be available. @@ -60,9 +234,9 @@ Notice that the URL is of the format `/api/`). The `uppercase` function signature is `Function, String> uppercase()`. The implementation of `UppercaseHandler` (which extends `FunctionInvoker`) copies the HTTP headers of the incoming request into the input message's _MessageHeaders_ which makes them accessible to the function if needed. -NOTE: Implementation of `FunctionInvoker` (your handler), should contain the least amount of code. It is really a type-safe way to define -and configure function to be recognized as Azure Function. -Everything else should be delegated to the base `FunctionInvoker` via `handleRequest(..)` callback which will invoke your function, taking care of +NOTE: Implementation of `FunctionInvoker` (your handler), should contain the least amount of code. It is really a type-safe way to define +and configure function to be recognized as Azure Function. +Everything else should be delegated to the base `FunctionInvoker` via `handleRequest(..)` callback which will invoke your function, taking care of necessary type conversion, transformation etc. One exception to this rule is when custom result handling is required. In that case, the proper post-process method can be overridden as well in order to take control of the results processing. .UppercaseHandler.java @@ -83,7 +257,7 @@ public String execute( ---- -The `echo` function does the same as the `uppercase` less the actual uppercasing. However, the important difference to notice is that function itself +The `echo` function does the same as the `uppercase` less the actual uppercasing. However, the important difference to notice is that function itself takes primitive `String` as its input (i.e., `public Function echo()`) while the actual handler passes instance of `Message` the same way as with `uppercase`. The framework recognizes that you only care about the payload and extracts it from the `Message` before calling the function. There is also a reactive version of _uppercase_ (named _uppercaseReactive_) which will produce the same result, but @@ -94,7 +268,7 @@ demonstrates and validates the ability to use reactive functions with Azure. NOTE: The Azure Java functions runtime does not yet support Java 17 but Spring Cloud Function 4.x requires it. To get around this limitation we deploy to Azure in a custom Docker container. Once https://github.com/Azure/azure-functions-java-worker/issues/548[Azure supports] Java 17 we can move back to using non-Docker deployments. ==== Custom Docker Image -The steps below describe the process to create a custom Docker image which is suitable for deployment on Azure and contains the 4.x Azure Functions runtime, the MS Java 17 JVM, and the sample functions in this repo. +The steps below describe the process to create a custom Docker image which is suitable for deployment on Azure and contains the 4.x Azure Functions runtime, the MS Java 17 JVM, and the sample functions in this repo. ====== Image name Pick an image name for the Docker container (eg. `onobc/function-sample-azure-java17:1.0.0`) and update the _pom.xml_ `functionDockerImageName` property with the image name. @@ -103,7 +277,7 @@ TIP: By default it is expected that the image name is a publicly accessible imag .Rebuild the functions (pom.xml was updated): [source,bash] ----- +---- ../../mvnw clean package ---- .Build the Docker image: @@ -116,7 +290,7 @@ Test the Docker image locally by starting the container and issuing a request. .Start the function runtime locally in Docker: [source,bash] ----- +---- docker run -p 8080:80 ---- @@ -197,13 +371,13 @@ curl -H "Content-Type: application/json" localhost:8080/api/uppercase -d '{"gree .Push the image to Docker registry: [source,bash] ----- +---- docker push ---- At this point the custom image has been created and pushed to the configured Docker registry. ==== Deploy to Azure -To deploy the functions to your live Azure environment, including automatic provisioning of an _HTTPTrigger_ for each function, do the following. +To deploy the functions to your live Azure environment, including automatic provisioning of an _HTTPTrigger_ for each function, do the following. .Login to Azure: [source,bash]