diff --git a/samples/triggers-bindings-example/.editorconfig b/samples/triggers-bindings-example/.editorconfig new file mode 100644 index 00000000..b55a15f5 --- /dev/null +++ b/samples/triggers-bindings-example/.editorconfig @@ -0,0 +1,9 @@ +root = true + +[*] +charset = utf-8 +indent_size = 4 +indent_style = space +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true \ No newline at end of file diff --git a/samples/triggers-bindings-example/.gitignore b/samples/triggers-bindings-example/.gitignore new file mode 100644 index 00000000..2f142fa6 --- /dev/null +++ b/samples/triggers-bindings-example/.gitignore @@ -0,0 +1,61 @@ +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +# Build output +target/ + +*.factorypath + +# IDE +.idea/ +*.iml +.classpath +.project +.settings/ +.checkstyle +.vscode/ + +# macOS +.DS_Store + +# gradle-wrapper +!gradle/wrapper/gradle-wrapper.jar +!gradle/wrapper/gradle-wrapper.properties + +# integration test +*/src/it/*/bin + +jacoco.exec +bin/ + +# mvnw +!.mvn/wrapper/maven-wrapper.jar + +build/ + +.gradle/ + +# Azure Functions +local.settings.json +obj/ diff --git a/samples/triggers-bindings-example/.mvn/wrapper/maven-wrapper.jar b/samples/triggers-bindings-example/.mvn/wrapper/maven-wrapper.jar new file mode 100644 index 00000000..9cc84ea9 Binary files /dev/null and b/samples/triggers-bindings-example/.mvn/wrapper/maven-wrapper.jar differ diff --git a/samples/triggers-bindings-example/.mvn/wrapper/maven-wrapper.properties b/samples/triggers-bindings-example/.mvn/wrapper/maven-wrapper.properties new file mode 100644 index 00000000..56bb0164 --- /dev/null +++ b/samples/triggers-bindings-example/.mvn/wrapper/maven-wrapper.properties @@ -0,0 +1 @@ +distributionUrl=https://repo1.maven.org/maven2/org/apache/maven/apache-maven/3.5.0/apache-maven-3.5.0-bin.zip \ No newline at end of file diff --git a/samples/triggers-bindings-example/CODE_OF_CONDUCT.md b/samples/triggers-bindings-example/CODE_OF_CONDUCT.md new file mode 100644 index 00000000..f9ba8cf6 --- /dev/null +++ b/samples/triggers-bindings-example/CODE_OF_CONDUCT.md @@ -0,0 +1,9 @@ +# Microsoft Open Source Code of Conduct + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). + +Resources: + +- [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/) +- [Microsoft Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) +- Contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with questions or concerns diff --git a/samples/triggers-bindings-example/LICENSE.txt b/samples/triggers-bindings-example/LICENSE.txt new file mode 100644 index 00000000..9e841e7a --- /dev/null +++ b/samples/triggers-bindings-example/LICENSE.txt @@ -0,0 +1,21 @@ + MIT License + + Copyright (c) Microsoft Corporation. + + Permission is hereby granted, free of charge, to any person obtaining a copy + of this software and associated documentation files (the "Software"), to deal + in the Software without restriction, including without limitation the rights + to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + copies of the Software, and to permit persons to whom the Software is + furnished to do so, subject to the following conditions: + + The above copyright notice and this permission notice shall be included in all + copies or substantial portions of the Software. + + THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + SOFTWARE diff --git a/samples/triggers-bindings-example/README.md b/samples/triggers-bindings-example/README.md new file mode 100644 index 00000000..86589466 --- /dev/null +++ b/samples/triggers-bindings-example/README.md @@ -0,0 +1,117 @@ +--- +page_type: sample +languages: +- java +products: +- azure-functions +- azure +description: "This repository contains sample for Azure Functions in Java" +urlFragment: "azure-functions-java" +--- + +# Azure Functions samples in Java + +This repository contains samples which show the basic usage of [Azure Functions](https://docs.microsoft.com/en-us/azure/azure-functions/) in Java for the below scenarios. + +| Scenario | Description | +|-------------------|--------------------------------------------| +| [HttpTrigger](./src/main/java/com/functions/Function.java) | Basic HttpTrigger and FixedDelayRetry with HttpTrigger. | +| [BlobTrigger](./src/main/java/com/functions/BlobTriggerFunction.java) | BlobTrigger, read blob using BlobInput binding and output to blob using BlobOutput binding. | +| [CosmosDBTrigger](./src/main/java/com/functions/CosmosDBTriggerFunction.java) | CosmosDBTrigger, read cosmos DB entries with CosmosDBInput binding and output to cosmos DB CosmosDBOutput binding. | +| [TimerTrigger](./src/main/java/com/functions/TimerTriggerFunction.java) | Basic periodic TimerTrigger. | +| [EventGridTrigger](./src/main/java/com/functions/EventGridTriggerFunction.java) | EventGridTrigger and send event to Event Grid using EventGridOutput binding. | +| [EventHubTrigger](./src/main/java/com/functions/EventHubTriggerFunction.java) | EventHubTrigger for message received in event grid and output to event grid using EventHubOutput binding. | +| [KafkaTrigger](./src/main/java/com/functions/KafkaTriggerFunction.java) | KafkaTrigger with KafkaOutput and QueueOutput example. | +| [QueueTrigger](./src/main/java/com/functions/QueueTriggerFunction.java) | QueueTrigger to read content from queue and output to queue using QueueOutput binding. | +| [ServiceBusQueueTrigger](./src/main/java/com/functions/ServiceBusQueueTriggerFunction.java) | ServiceBusQueueTrigger to read message from a queue in service bus and output to service bus queue using ServiceBusQueueOutput binding. | +| [ServiceBusTopicTrigger](./src/main/java/com/functions/ServiceBusTopicTriggerFunction.java) | ServiceBusTopicTrigger to read message from a topic in service bus and output to service bus topic using ServiceBusTopicOutput binding. | +| [Table function](./src/main/java/com/functions/TableFunction.java) | Basic example to read and write to table in Azure Storage using TableInput and TableOutput binding. | +| [Durable Function](./src/main/java/com/functions/DurableFunction.java) | Durable function example to start an orchestration and follow activity chaining. | + + +## Contents + +Outline the file contents of the repository. It helps users navigate the codebase, build configuration and any related assets. + +| File/folder | Description | +|-------------------|--------------------------------------------| +| `src` | Sample source code. | +| `.gitignore` | Define what to ignore at commit time. | +| `build.gradle` | The gradle configuration to this sample. | +| `pom.xml` | The maven configuration to this sample. | +| `CHANGELOG.md` | List of changes to the sample. | +| `CONTRIBUTING.md` | Guidelines for contributing to the sample. | +| `README.md` | This README file. | +| `LICENSE.txt` | The license for the sample. | + +## Prerequisites + +- Gradle 4.10+ +- Latest [Function Core Tools](https://aka.ms/azfunc-install) +- Azure CLI. This plugin use Azure CLI for authentication, please make sure you have Azure CLI installed and logged in. + +## Setup + +- ```cmd + az login + az account set -s + ``` +- Update the Application settings in Azure portal with the required parameters as below + - AzureWebJobsStorage: Connection string to your storage account + - CosmosDBDatabaseName: Cosmos database name. Example: ItemCollectionIn + - CosmosDBCollectionName:Cosmos database collection name. Example: ItemDb + - AzureWebJobsCosmosDBConnectionString: Connection string to your Cosmos database + - AzureWebJobsEventGridOutputBindingTopicUriString: Event Grid URI + - AzureWebJobsEventGridOutputBindingTopicKeyString: Event Grid string + - AzureWebJobsEventHubSender, AzureWebJobsEventHubSender_2 : Event hub connection string + - AzureWebJobsServiceBus: Service bus connection string + - SBQueueName: Service bus queue name. Example: test-input-java + - SBTopicName: Service bus topic name. Example: javaworkercitopic2 + - SBTopicSubName: Service bus topic name. Example: javaworkercisub + - Documentation on how to [manage connection strings](https://docs.microsoft.com/en-gb/azure/storage/common/storage-account-keys-manage?tabs=azure-portal) and [access keys](https://docs.microsoft.com/en-gb/azure/storage/common/storage-configure-connection-string#create-a-connection-string-for-an-azure-storage-account) +- Update `host.json` with the right extension bundle version. `V3 - [1.*, 2.0.0) and V4 - [2.*, 3.0.0)` + + +## Running the sample + +```cmd +./mvnw clean package azure-functions:run +``` + +```cmd +./gradlew clean azureFunctionsRun +``` + +## Deploy the sample on Azure + + +```cmd +./mvnw clean package azure-functions:deploy +``` + +```cmd +./gradlew clean azureFunctionsDeploy +``` + +> NOTE: please replace '/' with '\\' when you are running on windows. + + +## Contributing + +This project welcomes contributions and suggestions. Most contributions require you to agree to a +Contributor License Agreement (CLA) declaring that you have the right to, and actually do, grant us +the rights to use your contribution. For details, visit https://cla.opensource.microsoft.com. + +When you submit a pull request, a CLA bot will automatically determine whether you need to provide +a CLA and decorate the PR appropriately (e.g., status check, comment). Simply follow the instructions +provided by the bot. You will only need to do this once across all repos using our CLA. + +This project has adopted the [Microsoft Open Source Code of Conduct](https://opensource.microsoft.com/codeofconduct/). +For more information see the [Code of Conduct FAQ](https://opensource.microsoft.com/codeofconduct/faq/) or +contact [opencode@microsoft.com](mailto:opencode@microsoft.com) with any additional questions or comments. + +## Telemetry +This project collects usage data and sends it to Microsoft to help improve our products and services. +Read Microsoft's [privacy statement](https://privacy.microsoft.com/en-us/privacystatement) to learn more. +If you would like to opt out of sending telemetry data to Microsoft, you can set `allowTelemetry` to false in the plugin configuration. +Please read our [document](https://github.com/microsoft/azure-gradle-plugins/wiki/Configuration) to find more details about *allowTelemetry*. diff --git a/samples/triggers-bindings-example/SECURITY.md b/samples/triggers-bindings-example/SECURITY.md new file mode 100644 index 00000000..e0dfff56 --- /dev/null +++ b/samples/triggers-bindings-example/SECURITY.md @@ -0,0 +1,41 @@ + + +## Security + +Microsoft takes the security of our software products and services seriously, which includes all source code repositories managed through our GitHub organizations, which include [Microsoft](https://github.com/Microsoft), [Azure](https://github.com/Azure), [DotNet](https://github.com/dotnet), [AspNet](https://github.com/aspnet), [Xamarin](https://github.com/xamarin), and [our GitHub organizations](https://opensource.microsoft.com/). + +If you believe you have found a security vulnerability in any Microsoft-owned repository that meets Microsoft's [Microsoft's definition of a security vulnerability](https://docs.microsoft.com/en-us/previous-versions/tn-archive/cc751383(v=technet.10)) of a security vulnerability, please report it to us as described below. + +## Reporting Security Issues + +**Please do not report security vulnerabilities through public GitHub issues.** + +Instead, please report them to the Microsoft Security Response Center (MSRC) at [https://msrc.microsoft.com/create-report](https://msrc.microsoft.com/create-report). + +If you prefer to submit without logging in, send email to [secure@microsoft.com](mailto:secure@microsoft.com). If possible, encrypt your message with our PGP key; please download it from the the [Microsoft Security Response Center PGP Key page](https://www.microsoft.com/en-us/msrc/pgp-key-msrc). + +You should receive a response within 24 hours. If for some reason you do not, please follow up via email to ensure we received your original message. Additional information can be found at [microsoft.com/msrc](https://www.microsoft.com/msrc). + +Please include the requested information listed below (as much as you can provide) to help us better understand the nature and scope of the possible issue: + + * Type of issue (e.g. buffer overflow, SQL injection, cross-site scripting, etc.) + * Full paths of source file(s) related to the manifestation of the issue + * The location of the affected source code (tag/branch/commit or direct URL) + * Any special configuration required to reproduce the issue + * Step-by-step instructions to reproduce the issue + * Proof-of-concept or exploit code (if possible) + * Impact of the issue, including how an attacker might exploit the issue + +This information will help us triage your report more quickly. + +If you are reporting for a bug bounty, more complete reports can contribute to a higher bounty award. Please visit our [Microsoft Bug Bounty Program](https://microsoft.com/msrc/bounty) page for more details about our active programs. + +## Preferred Languages + +We prefer all communications to be in English. + +## Policy + +Microsoft follows the principle of [Coordinated Vulnerability Disclosure](https://www.microsoft.com/en-us/msrc/cvd). + + diff --git a/samples/triggers-bindings-example/build.gradle b/samples/triggers-bindings-example/build.gradle new file mode 100644 index 00000000..fbc222a0 --- /dev/null +++ b/samples/triggers-bindings-example/build.gradle @@ -0,0 +1,39 @@ +plugins { + id "com.microsoft.azure.azurefunctions" version "1.9.0" +} +apply plugin: 'java' +apply plugin: "com.microsoft.azure.azurefunctions" + +group 'com.functions' +version '1.0-SNAPSHOT' + +dependencies { + implementation 'com.microsoft.azure.functions:azure-functions-java-library:2.0.1' + implementation 'com.microsoft:durabletask-azure-functions:1.0.0-beta.1' + testImplementation 'org.junit.jupiter:junit-jupiter:5.6.2' + testImplementation 'org.mockito:mockito-core:3.3.3' +} + +sourceCompatibility = '1.8' +targetCompatibility = '1.8' + +compileJava.options.encoding = 'UTF-8' + +repositories { + mavenCentral() +} + +azurefunctions { + resourceGroup = 'java-functions-group' + appName = 'azure-functions-sample' + pricingTier = 'Consumption' + region = 'westus' + runtime { + os = 'Windows' + javaVersion = 'Java 8' + } + auth { + type = 'azure_cli' + } + localDebug = "transport=dt_socket,server=y,suspend=n,address=5005" +} diff --git a/samples/triggers-bindings-example/gradle/wrapper/gradle-wrapper.jar b/samples/triggers-bindings-example/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 00000000..cc4fdc29 Binary files /dev/null and b/samples/triggers-bindings-example/gradle/wrapper/gradle-wrapper.jar differ diff --git a/samples/triggers-bindings-example/gradle/wrapper/gradle-wrapper.properties b/samples/triggers-bindings-example/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 00000000..94920145 --- /dev/null +++ b/samples/triggers-bindings-example/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.0.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/samples/triggers-bindings-example/gradlew b/samples/triggers-bindings-example/gradlew new file mode 100644 index 00000000..2fe81a7d --- /dev/null +++ b/samples/triggers-bindings-example/gradlew @@ -0,0 +1,183 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 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. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +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 +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# 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"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# 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 + ;; + 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" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/samples/triggers-bindings-example/gradlew.bat b/samples/triggers-bindings-example/gradlew.bat new file mode 100644 index 00000000..9618d8d9 --- /dev/null +++ b/samples/triggers-bindings-example/gradlew.bat @@ -0,0 +1,100 @@ +@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=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@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%" == "0" goto init + +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 init + +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 + +:init +@rem Get command-line arguments, handling Windows variants + +if not "%OS%" == "Windows_NT" goto win9xME_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* + +: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 %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/samples/triggers-bindings-example/host.json b/samples/triggers-bindings-example/host.json new file mode 100644 index 00000000..b7e5ad1c --- /dev/null +++ b/samples/triggers-bindings-example/host.json @@ -0,0 +1,7 @@ +{ + "version": "2.0", + "extensionBundle": { + "id": "Microsoft.Azure.Functions.ExtensionBundle", + "version": "[4.*, 5.0.0)" + } +} diff --git a/samples/triggers-bindings-example/mvnw b/samples/triggers-bindings-example/mvnw new file mode 100644 index 00000000..5bf251c0 --- /dev/null +++ b/samples/triggers-bindings-example/mvnw @@ -0,0 +1,225 @@ +#!/bin/sh +# ---------------------------------------------------------------------------- +# Licensed to the Apache Software Foundation (ASF) under one +# or more contributor license agreements. See the NOTICE file +# distributed with this work for additional information +# regarding copyright ownership. The ASF licenses this file +# to you under the Apache License, Version 2.0 (the +# "License"); you may not use this file except in compliance +# with the License. You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, +# software distributed under the License is distributed on an +# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +# KIND, either express or implied. See the License for the +# specific language governing permissions and limitations +# under the License. +# ---------------------------------------------------------------------------- + +# ---------------------------------------------------------------------------- +# Maven2 Start Up Batch script +# +# Required ENV vars: +# ------------------ +# JAVA_HOME - location of a JDK home dir +# +# 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 +# MAVEN_SKIP_RC - flag to disable loading of mavenrc files +# ---------------------------------------------------------------------------- + +if [ -z "$MAVEN_SKIP_RC" ] ; then + + if [ -f /etc/mavenrc ] ; then + . /etc/mavenrc + fi + + if [ -f "$HOME/.mavenrc" ] ; then + . "$HOME/.mavenrc" + fi + +fi + +# OS specific support. $var _must_ be set to either true or false. +cygwin=false; +darwin=false; +mingw=false +case "`uname`" in + CYGWIN*) cygwin=true ;; + MINGW*) mingw=true;; + Darwin*) darwin=true + # Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home + # 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`" + else + export JAVA_HOME="/Library/Java/Home" + fi + fi + ;; +esac + +if [ -z "$JAVA_HOME" ] ; then + if [ -r /etc/gentoo-release ] ; then + 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"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --unix "$CLASSPATH"` +fi + +# For Migwn, 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)`" + # TODO classpath? +fi + +if [ -z "$JAVA_HOME" ]; 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 + if $darwin ; then + javaHome="`dirname \"$javaExecutable\"`" + javaExecutable="`cd \"$javaHome\" && pwd -P`/javac" + else + javaExecutable="`readlink -f \"$javaExecutable\"`" + fi + javaHome="`dirname \"$javaExecutable\"`" + javaHome=`expr "$javaHome" : '\(.*\)/bin'` + JAVA_HOME="$javaHome" + export JAVA_HOME + fi + fi +fi + +if [ -z "$JAVACMD" ] ; then + 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 + else + JAVACMD="`which java`" + fi +fi + +if [ ! -x "$JAVACMD" ] ; then + echo "Error: JAVA_HOME is not defined correctly." >&2 + echo " We cannot execute $JAVACMD" >&2 + exit 1 +fi + +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" + return 1 + fi + + basedir="$1" + wdir="$1" + while [ "$wdir" != '/' ] ; do + if [ -d "$wdir"/.mvn ] ; then + basedir=$wdir + break + fi + # workaround for JBEAP-8937 (on Solaris 10/Sparc) + if [ -d "${wdir}" ]; then + wdir=`cd "$wdir/.."; pwd` + fi + # end of workaround + done + echo "${basedir}" +} + +# concatenates all lines of a file +concat_lines() { + if [ -f "$1" ]; then + echo "$(tr -s '\n' ' ' < "$1")" + fi +} + +BASE_DIR=`find_maven_basedir "$(pwd)"` +if [ -z "$BASE_DIR" ]; then + exit 1; +fi + +export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"} +echo $MAVEN_PROJECTBASEDIR +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"` + [ -n "$CLASSPATH" ] && + CLASSPATH=`cygpath --path --windows "$CLASSPATH"` + [ -n "$MAVEN_PROJECTBASEDIR" ] && + MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"` +fi + +WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain + +exec "$JAVACMD" \ + $MAVEN_OPTS \ + -classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \ + "-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \ + ${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@" diff --git a/samples/triggers-bindings-example/mvnw.cmd b/samples/triggers-bindings-example/mvnw.cmd new file mode 100644 index 00000000..019bd74d --- /dev/null +++ b/samples/triggers-bindings-example/mvnw.cmd @@ -0,0 +1,143 @@ +@REM ---------------------------------------------------------------------------- +@REM Licensed to the Apache Software Foundation (ASF) under one +@REM or more contributor license agreements. See the NOTICE file +@REM distributed with this work for additional information +@REM regarding copyright ownership. The ASF licenses this file +@REM to you under the Apache License, Version 2.0 (the +@REM "License"); you may not use this file except in compliance +@REM with the License. You may obtain a copy of the License at +@REM +@REM http://www.apache.org/licenses/LICENSE-2.0 +@REM +@REM Unless required by applicable law or agreed to in writing, +@REM software distributed under the License is distributed on an +@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +@REM KIND, either express or implied. See the License for the +@REM specific language governing permissions and limitations +@REM under the License. +@REM ---------------------------------------------------------------------------- + +@REM ---------------------------------------------------------------------------- +@REM Maven2 Start Up Batch script +@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 key stroke before ending +@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven +@REM e.g. to debug Maven itself, use +@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000 +@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files +@REM ---------------------------------------------------------------------------- + +@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on' +@echo off +@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on' +@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO% + +@REM set %HOME% to equivalent of $HOME +if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%") + +@REM Execute a user defined script before this one +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre +@REM check for pre script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat" +if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd" +:skipRcPre + +@setlocal + +set ERROR_CODE=0 + +@REM To isolate internal variables from possible post scripts, we use another setlocal +@setlocal + +@REM ==== START VALIDATION ==== +if not "%JAVA_HOME%" == "" goto OkJHome + +echo. +echo Error: JAVA_HOME not found in your environment. >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +:OkJHome +if exist "%JAVA_HOME%\bin\java.exe" goto init + +echo. +echo Error: JAVA_HOME is set to an invalid directory. >&2 +echo JAVA_HOME = "%JAVA_HOME%" >&2 +echo Please set the JAVA_HOME variable in your environment to match the >&2 +echo location of your Java installation. >&2 +echo. +goto error + +@REM ==== END VALIDATION ==== + +:init + +@REM Find the project base dir, i.e. the directory that contains the folder ".mvn". +@REM Fallback to current working directory if not found. + +set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR% +IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir + +set EXEC_DIR=%CD% +set WDIR=%EXEC_DIR% +:findBaseDir +IF EXIST "%WDIR%"\.mvn goto baseDirFound +cd .. +IF "%WDIR%"=="%CD%" goto baseDirNotFound +set WDIR=%CD% +goto findBaseDir + +:baseDirFound +set MAVEN_PROJECTBASEDIR=%WDIR% +cd "%EXEC_DIR%" +goto endDetectBaseDir + +:baseDirNotFound +set MAVEN_PROJECTBASEDIR=%EXEC_DIR% +cd "%EXEC_DIR%" + +:endDetectBaseDir + +IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig + +@setlocal EnableExtensions EnableDelayedExpansion +for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a +@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS% + +:endReadAdditionalConfig + +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 + +%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %* +if ERRORLEVEL 1 goto error +goto end + +:error +set ERROR_CODE=1 + +:end +@endlocal & set ERROR_CODE=%ERROR_CODE% + +if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost +@REM check for post script, once with legacy .bat ending and once with .cmd ending +if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat" +if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd" +:skipRcPost + +@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on' +if "%MAVEN_BATCH_PAUSE%" == "on" pause + +if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE% + +exit /B %ERROR_CODE% diff --git a/samples/triggers-bindings-example/pom.xml b/samples/triggers-bindings-example/pom.xml new file mode 100644 index 00000000..4d2ec4fc --- /dev/null +++ b/samples/triggers-bindings-example/pom.xml @@ -0,0 +1,130 @@ + + + 4.0.0 + + com.functions + azure-functions-sample + 1.0-SNAPSHOT + jar + + Azure Java Functions + + + 1.8 + UTF-8 + 1.22.0 + 3.0.0 + azure-functions-sample + westus + java-functions-resource-group + java-functions-service-plan + Y1 + + + + + com.microsoft.azure.functions + azure-functions-java-library + ${azure.functions.java.library.version} + + + + com.microsoft + durabletask-azure-functions + 1.0.0 + + + + + org.junit.jupiter + junit-jupiter + 5.6.2 + test + + + org.mockito + mockito-core + 3.3.3 + test + + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 3.8.1 + + ${java.version} + ${java.version} + ${project.build.sourceEncoding} + + + + com.microsoft.azure + azure-functions-maven-plugin + ${azure.functions.maven.plugin.version} + + + ${functionAppName} + + ${functionResourceGroup} + + ${functionServicePlan} + + + ${functionAppRegion} + + + ${functionPricingTier} + + + + + + + windows + 8 + + + + + + + + FUNCTIONS_EXTENSION_VERSION + ~4 + + + + + + package-functions + + package + + + + + + + maven-clean-plugin + 3.1.0 + + + + obj + + + + + + maven-surefire-plugin + 2.22.2 + + + + + diff --git a/samples/triggers-bindings-example/settings.gradle b/samples/triggers-bindings-example/settings.gradle new file mode 100644 index 00000000..2aa1f354 --- /dev/null +++ b/samples/triggers-bindings-example/settings.gradle @@ -0,0 +1 @@ +rootProject.name = "azure-functions-samples-java" diff --git a/samples/triggers-bindings-example/src/main/java/com/functions/BlobTriggerFunction.java b/samples/triggers-bindings-example/src/main/java/com/functions/BlobTriggerFunction.java new file mode 100644 index 00000000..af1679b2 --- /dev/null +++ b/samples/triggers-bindings-example/src/main/java/com/functions/BlobTriggerFunction.java @@ -0,0 +1,28 @@ +package com.functions; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.OutputBinding; +import com.microsoft.azure.functions.annotation.*; + +/** + * Azure Functions with Azure Storage Blob. + * https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-blob-trigger?tabs=java + */ +public class BlobTriggerFunction { + /** + * This function will be invoked when a new or updated blob is detected at the specified path. The blob contents are provided as input to this function. + * The location of the blob is provided in the path parameter. Example - test-triggerinput-java/{name} below + */ + @FunctionName("BlobTrigger") + @StorageAccount("AzureWebJobsStorage") + public void BlobTriggerToBlobTest( + @BlobTrigger(name = "triggerBlob", path = "test-triggerinput-java/{name}", dataType = "binary") byte[] triggerBlob, + @BindingName("name") String fileName, + @BlobInput(name = "inputBlob", path = "test-input-java/{name}", dataType = "binary") byte[] inputBlob, + @BlobOutput(name = "outputBlob", path = "test-output-java/{name}", dataType = "binary") OutputBinding outputBlob, + final ExecutionContext context + ) { + context.getLogger().info("Java Blob trigger function BlobTriggerToBlobTest processed a blob.\n Name: " + fileName + "\n Size: " + triggerBlob.length + " Bytes"); + outputBlob.setValue(inputBlob); + } +} diff --git a/samples/triggers-bindings-example/src/main/java/com/functions/CosmosDBTriggerFunction.java b/samples/triggers-bindings-example/src/main/java/com/functions/CosmosDBTriggerFunction.java new file mode 100644 index 00000000..26188507 --- /dev/null +++ b/samples/triggers-bindings-example/src/main/java/com/functions/CosmosDBTriggerFunction.java @@ -0,0 +1,142 @@ +package com.functions; + +import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.*; + +import java.io.IOException; +import java.lang.reflect.Array; +import java.util.*; + +/** + * Azure Functions with Azure Cosmos DB. + */ +public class CosmosDBTriggerFunction { + + /** + * This function will be invoked when a message is posted to + * /api/CosmosDBInputId?docId={docId} contents are provided as the input to this + * function. + */ + @FunctionName("CosmosDBInputId") + public HttpResponseMessage CosmosDBInputId(@HttpTrigger(name = "req", methods = { HttpMethod.GET, + HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @CosmosDBInput(name = "item", databaseName = "%CosmosDBDatabaseName%", containerName = "ItemsCollectionIn", connection = "AzureWebJobsCosmosDBConnectionString", id = "{docId}") String item, + final ExecutionContext context) { + + context.getLogger().info("Java HTTP trigger processed a request."); + + if (item != null) { + return request.createResponseBuilder(HttpStatus.OK).body("Received Document" + item).build(); + } else { + return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Did not find expected item in ItemsCollectionIn").build(); + } + } + + @FunctionName("CosmosDBInputIdPOJO") + public HttpResponseMessage CosmosDBInputIdPOJO(@HttpTrigger(name = "req", methods = { HttpMethod.GET, + HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @CosmosDBInput(name = "item", databaseName = "%CosmosDBDatabaseName%", containerName = "ItemsCollectionIn", connection = "AzureWebJobsCosmosDBConnectionString", id = "{docId}") Document item, + final ExecutionContext context) { + + context.getLogger().info("Java HTTP trigger processed a request."); + + if (item != null) { + return request.createResponseBuilder(HttpStatus.OK).body("Received Document with Id " + item.id).build(); + } else { + return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Did not find expected item in ItemsCollectionIn").build(); + } + } + + + /** + * This function will be invoked when a message is posted to + * /api/CosmosDBInputQuery?name=joe Receives input with list of items matching + * the sqlQuery + */ + @FunctionName("CosmosDBInputQueryPOJOArray") + public HttpResponseMessage CosmosDBInputQueryPOJOArray(@HttpTrigger(name = "req", methods = { HttpMethod.GET, + HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @CosmosDBInput(name = "items", databaseName = "%CosmosDBDatabaseName%", containerName = "ItemsCollectionIn", connection = "AzureWebJobsCosmosDBConnectionString", sqlQuery = "SELECT f.id, f.name FROM f WHERE f.name = {name}") Document[] items, + @CosmosDBOutput(name = "itemsOut", databaseName = "%CosmosDBDatabaseName%", containerName = "ItemsCollectionOut", connection = "AzureWebJobsCosmosDBConnectionString") OutputBinding itemsOut, + final ExecutionContext context) { + context.getLogger().info("Java HTTP trigger processed a request."); + + if (items.length >= 2) { + itemsOut.setValue(items); + return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + items[0].name).build(); + } else { + return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Did not find expected items in CosmosDB input list").build(); + } + } + + @FunctionName("CosmosDBInputQueryPOJOList") + public HttpResponseMessage CosmosDBInputQueryPOJOList(@HttpTrigger(name = "req", methods = { HttpMethod.GET, + HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @CosmosDBInput(name = "item", databaseName = "%CosmosDBDatabaseName%", containerName = "ItemsCollectionIn", connection = "AzureWebJobsCosmosDBConnectionString", sqlQuery = "SELECT f.id, f.name FROM f WHERE f.name = {name}") List items, + @CosmosDBOutput(name = "itemsOut", databaseName = "%CosmosDBDatabaseName%", containerName = "ItemsCollectionOut", connection = "AzureWebJobsCosmosDBConnectionString") OutputBinding> itemsOut, + final ExecutionContext context) { + context.getLogger().info("Java HTTP trigger processed a request."); + + if (items.size() >= 2) { + itemsOut.setValue(items); + return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + items.get(0).name).build(); + } else { + return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Did not find expected items in CosmosDB input list").build(); + } + } + + @FunctionName("CosmosDBInputQuery") + public HttpResponseMessage CosmosDBInputQuery(@HttpTrigger(name = "req", methods = { HttpMethod.GET, + HttpMethod.POST }, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @CosmosDBInput(name = "item", databaseName = "%CosmosDBDatabaseName%", containerName = "ItemsCollectionIn", connection = "AzureWebJobsCosmosDBConnectionString", sqlQuery = "SELECT f.id, f.name FROM f WHERE f.name = {name}") List items, + final ExecutionContext context) { + context.getLogger().info("Java HTTP trigger processed a request."); + + // Parse query parameters + String query = request.getQueryParameters().get("name"); + String name = request.getBody().orElse(query); + + if (items.size() >= 2) { + return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build(); + } else { + return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Did not find expected items in CosmosDB input list").build(); + } + } + + /** + * This function will be invoked when a post request with file to + * http://localhost:7071/api/CosmosDBOutput. A new document will add to the + * container. + */ + @FunctionName("CosmosTriggerAndOutput") + public void CosmosTriggerAndOutput( + @CosmosDBTrigger(name = "itemIn", databaseName = "%CosmosDBDatabaseName%", containerName = "ItemCollectionIn", leaseContainerName = "leases", connection = "AzureWebJobsCosmosDBConnectionString", createLeaseContainerIfNotExists = true) Object inputItem, + @CosmosDBOutput(name = "itemOut", databaseName = "%CosmosDBDatabaseName%", containerName = "ItemCollectionOut", connection = "AzureWebJobsCosmosDBConnectionString") OutputBinding outPutItem, + final ExecutionContext context) { + + context.getLogger().info("Java Cosmos DB trigger function executed. Received document: " + inputItem); + + ArrayList inputItems = (ArrayList) inputItem; + String objString = inputItems.get(0).toString(); + String[] arrOfStr = objString.split("=", 2); + String[] arrOfStrWithId = arrOfStr[1].split(",", 2); + String docId = arrOfStrWithId[0]; + + context.getLogger().info("Writing to CosmosDB output binding Document id: " + docId); + Document testDoc = new Document(); + testDoc.id = docId; + testDoc.Description = "testdescription"; + outPutItem.setValue(testDoc); + } + + public class Document { + public String id; + public String name; + public String Description; + } +} diff --git a/samples/triggers-bindings-example/src/main/java/com/functions/DurableFunction.java b/samples/triggers-bindings-example/src/main/java/com/functions/DurableFunction.java new file mode 100644 index 00000000..95a30297 --- /dev/null +++ b/samples/triggers-bindings-example/src/main/java/com/functions/DurableFunction.java @@ -0,0 +1,66 @@ +package com.functions; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; +import com.microsoft.durabletask.DurableTaskClient; +import com.microsoft.durabletask.OrchestrationRunner; +import com.microsoft.durabletask.azurefunctions.DurableActivityTrigger; +import com.microsoft.durabletask.azurefunctions.DurableClientContext; +import com.microsoft.durabletask.azurefunctions.DurableClientInput; +import com.microsoft.durabletask.azurefunctions.DurableOrchestrationTrigger; + +import java.util.Optional; + +/** + * Azure Durable Functions with HTTP trigger. + */ +public class DurableFunction { + /** + * This HTTP-triggered function starts the orchestration. + */ + @FunctionName("StartOrchestration") + public HttpResponseMessage startOrchestration( + @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @DurableClientInput(name = "durableContext") DurableClientContext durableContext, + final ExecutionContext context) { + context.getLogger().info("Java HTTP trigger processed a request."); + + DurableTaskClient client = durableContext.getClient(); + String instanceId = client.scheduleNewOrchestrationInstance("Cities"); + context.getLogger().info("Created new Java orchestration with instance ID = " + instanceId); + return durableContext.createCheckStatusResponse(request, instanceId); + } + + /** + * This is the orchestrator function. The OrchestrationRunner.loadAndRun() static + * method is used to take the function input and execute the orchestrator logic. + */ + @FunctionName("Cities") + public String citiesOrchestrator( + @DurableOrchestrationTrigger(name = "orchestratorRequestProtoBytes") String orchestratorRequestProtoBytes) { + return OrchestrationRunner.loadAndRun(orchestratorRequestProtoBytes, ctx -> { + String result = ""; + result += ctx.callActivity("Capitalize", "Tokyo", String.class).await() + ", "; + result += ctx.callActivity("Capitalize", "London", String.class).await() + ", "; + result += ctx.callActivity("Capitalize", "Seattle", String.class).await() + ", "; + result += ctx.callActivity("Capitalize", "Austin", String.class).await(); + return result; + }); + } + + /** + * This is the activity function that gets invoked by the orchestration. + */ + @FunctionName("Capitalize") + public String capitalize( + @DurableActivityTrigger(name = "name") String name, + final ExecutionContext context) { + context.getLogger().info("Capitalizing: " + name); + return name.toUpperCase(); + } +} diff --git a/samples/triggers-bindings-example/src/main/java/com/functions/EventGridTriggerFunction.java b/samples/triggers-bindings-example/src/main/java/com/functions/EventGridTriggerFunction.java new file mode 100644 index 00000000..ffabc030 --- /dev/null +++ b/samples/triggers-bindings-example/src/main/java/com/functions/EventGridTriggerFunction.java @@ -0,0 +1,114 @@ +package com.functions; + +import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.*; + +import java.util.*; + +/** + * Azure Functions with Event Grid trigger. + * https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-grid-trigger?tabs=java%2Cbash + */ +public class EventGridTriggerFunction { + /** + * This function will be invoked when an event is received from Event Grid. + */ + @FunctionName("EventGridTriggerJava") + public void eventGridHandler( + @EventGridTrigger(name = "eventgrid") String eventContent, + final ExecutionContext context + ) { + context.getLogger().info("Java Event Grid trigger function executed."); + context.getLogger().info(eventContent); + } + + /** + * This function will be invoked when a http trigger is received, and sends a custom event to Event Grid. + */ + @FunctionName("EventGridOutputBindingJava") + public HttpResponseMessage eventGridOutputBinding( + @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, + authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @EventGridOutput(name = "outputEvent", + topicEndpointUri = "AzureWebJobsEventGridOutputBindingTopicUriString", + topicKeySetting = "AzureWebJobsEventGridOutputBindingTopicKeyString") OutputBinding outputEvent, + final ExecutionContext context + ) { + context.getLogger().info("Java HTTP trigger processed a request."); + + // Parse query parameter + String query = request.getQueryParameters().get("testuuid"); + String message = request.getBody().orElse(query); + context.getLogger().info("testuuid:" + message); + + final EventGridEvent eventGridOutputDocument = new EventGridEvent(); + eventGridOutputDocument.setId("test-id"); + eventGridOutputDocument.setEventType("test-event-1"); + eventGridOutputDocument.setEventTime("2020-01-31T10:10:10+00:00"); + eventGridOutputDocument.setDataVersion("1.0"); + eventGridOutputDocument.setSubject("test-subject"); + eventGridOutputDocument.setData("test-uuid: " + message); + + outputEvent.setValue(eventGridOutputDocument); + + return request.createResponseBuilder(HttpStatus.OK).build(); + } +} + + +class EventGridEvent { + private String id; + private String eventType; + private String subject; + private String eventTime; + private String dataVersion; + private String data; + + public String getId() { + return id; + } + + public String getData() { + return data; + } + + public void setData(String data) { + this.data = data; + } + + public String getDataVersion() { + return dataVersion; + } + + public void setDataVersion(String dataVersion) { + this.dataVersion = dataVersion; + } + + public String getEventTime() { + return eventTime; + } + + public void setEventTime(String eventTime) { + this.eventTime = eventTime; + } + + public String getSubject() { + return subject; + } + + public void setSubject(String subject) { + this.subject = subject; + } + + public String getEventType() { + return eventType; + } + + public void setEventType(String eventType) { + this.eventType = eventType; + } + + public void setId(String id) { + this.id = id; + } +} diff --git a/samples/triggers-bindings-example/src/main/java/com/functions/EventHubTriggerFunction.java b/samples/triggers-bindings-example/src/main/java/com/functions/EventHubTriggerFunction.java new file mode 100644 index 00000000..b4937f7c --- /dev/null +++ b/samples/triggers-bindings-example/src/main/java/com/functions/EventHubTriggerFunction.java @@ -0,0 +1,125 @@ +package com.functions; + +import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.*; + +import java.util.*; + +/** + * Azure Functions with Azure Event Hub. + * https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-event-hubs-trigger?tabs=java + */ +public class EventHubTriggerFunction { + /** + * This function will be invoked when a new message is received at the specified EventHub. The message contents are provided as input to this function. + */ + @FunctionName("EventHubTriggerAndOutputJSON") + public void EventHubTriggerAndOutputJSON( + @EventHubTrigger(name = "messages", eventHubName = "test-inputjson-java", connection = "AzureWebJobsEventHubSender", cardinality = Cardinality.MANY) List messages, + @EventHubOutput(name = "output", eventHubName = "test-outputjson-java", connection = "AzureWebJobsEventHubSender") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Event Hub trigger received " + messages.size() + " messages"); + output.setValue(messages.get(0)); + } + + @FunctionName("EventHubTriggerAndOutputString") + public void EventHubTriggerAndOutputString( + @EventHubTrigger(name = "messages", eventHubName = "test-input-java", connection = "AzureWebJobsEventHubSender", dataType = "string", cardinality = Cardinality.MANY) String[] messages, + @BindingName("SystemPropertiesArray") SystemProperty[] systemPropertiesArray, + @EventHubOutput(name = "output", eventHubName = "test-output-java", connection = "AzureWebJobsEventHubSender") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Event Hub trigger received " + messages.length + " messages"); + context.getLogger().info("SystemProperties for message[0]: EnqueuedTimeUtc=" + systemPropertiesArray[0].EnqueuedTimeUtc + " Offset=" + systemPropertiesArray[0].Offset); + output.setValue(messages[0]); + + } + + @FunctionName("EventHubTriggerCardinalityOne") + public void EventHubTriggerCardinalityOne( + @EventHubTrigger(name = "message", eventHubName = "test-inputOne-java", connection = "AzureWebJobsEventHubSender", dataType = "string", cardinality = Cardinality.ONE) String message, + @EventHubOutput(name = "output", eventHubName = "test-outputone-java", connection = "AzureWebJobsEventHubSender") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Event Hub trigger received message" + message); + output.setValue(message); + } + + /** + * This function verifies the above functions + */ + @FunctionName("EventHubOutputJson") + public void TestEventHubOutputJson( + @EventHubTrigger(name = "message", eventHubName = "test-outputjson-java", connection = "AzureWebJobsEventHubSender") String message, + @QueueOutput(name = "output", queueName = "test-eventhuboutputjson-java", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Event Hub Output function processed a message: " + message); + output.setValue(message); + } + + @FunctionName("EventHubOutput") + public void TestEventHubOutput( + @EventHubTrigger(name = "message", eventHubName = "test-output-java", connection = "AzureWebJobsEventHubSender", cardinality = Cardinality.ONE) String message, + @QueueOutput(name = "output", queueName = "test-eventhuboutput-java", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Event Hub Output function processed a message: " + message); + output.setValue(message); + } + + @FunctionName("EventHubOutputInputOne") + public void TestEventHubOutputInputOne( + @EventHubTrigger(name = "message", eventHubName = "test-outputone-java", connection = "AzureWebJobsEventHubSender", cardinality = Cardinality.ONE) String message, + @QueueOutput(name = "output", queueName = "test-eventhuboutputone-java", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Event Hub Output function processed a message: " + message); + output.setValue(message); + } + + @FunctionName("EventHubTriggerAndOutputBinaryCardinalityManyListBinary") + public void EventHubTriggerAndOutputBinaryCardinalityManyListBinary( + @EventHubTrigger(name = "messages", eventHubName = "test-binary-input-cardinality-many-list-java", connection = "AzureWebJobsEventHubSender_2", dataType = "binary", cardinality = Cardinality.MANY) List messages, + @QueueOutput(name = "output", queueName = "test-binary-output-cardinality-many-list-java", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Event Hub trigger received " + messages.size() + " messages"); + output.setValue(messages.get(0)); + } + + @FunctionName("EventHubTriggerAndOutputBinaryCardinalityOne") + public void EventHubTriggerAndOutputBinaryCardinalityOne( + @EventHubTrigger(name = "message", eventHubName = "test-binary-input-cardinality-one-java", connection = "AzureWebJobsEventHubSender_2", dataType = "binary", cardinality = Cardinality.ONE) byte[] message, + @QueueOutput(name = "output", queueName = "test-binary-output-cardinality-one-java", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Event Hub trigger received message" + message); + output.setValue(message); + } + + @FunctionName("EventHubTriggerAndOutputBinaryCardinalityManyArrayBinary") + public void EventHubTriggerAndOutputBinaryCardinalityManyArrayBinary( + @EventHubTrigger(name = "messages", eventHubName = "test-binary-input-cardinality-many-array-java", connection = "AzureWebJobsEventHubSender_2", dataType = "binary", cardinality = Cardinality.MANY) byte[][] messages, + @QueueOutput(name = "output", queueName = "test-binary-output-cardinality-many-array-java", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Event Hub trigger received " + messages.length + " messages"); + output.setValue(messages[0]); + } + + public static class SystemProperty { + public String SequenceNumber; + public String Offset; + public String PartitionKey; + public String EnqueuedTimeUtc; + + public SystemProperty(String sequenceNumber, String offset, String partitionKey, String enqueuedTimeUtc) { + this.SequenceNumber = sequenceNumber; + this.Offset = offset; + this.PartitionKey = partitionKey; + this.EnqueuedTimeUtc = enqueuedTimeUtc; + } + } +} diff --git a/samples/triggers-bindings-example/src/main/java/com/functions/Function.java b/samples/triggers-bindings-example/src/main/java/com/functions/Function.java new file mode 100644 index 00000000..fa71b586 --- /dev/null +++ b/samples/triggers-bindings-example/src/main/java/com/functions/Function.java @@ -0,0 +1,133 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for + * license information. + */ + +package com.functions; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.HttpResponseMessage; +import com.microsoft.azure.functions.HttpStatus; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.FixedDelayRetry; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; + +import java.io.File; +import java.nio.file.Files; +import java.util.Optional; + +/** + * Azure Functions with HTTP Trigger. + */ +public class Function { + /** + * This function listens at endpoint "/api/HttpExample". Two ways to invoke it using "curl" command in bash: + * 1. curl -d "HTTP Body" {your host}/api/HttpExample + * 2. curl "{your host}/api/HttpExample?name=HTTP%20Query" + */ + @FunctionName("HttpExample") + public HttpResponseMessage run( + @HttpTrigger( + name = "req", + methods = {HttpMethod.GET, HttpMethod.POST}, + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + final ExecutionContext context) { + context.getLogger().info("Java HTTP trigger processed a request."); + + // Parse query parameter + final String query = request.getQueryParameters().get("name"); + final String name = request.getBody().orElse(query); + + if (name == null) { + return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("Please pass a name on the query string or in the request body").build(); + } else { + return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build(); + } + } + + public static int count = 1; + + /** + * This function listens at endpoint "/api/HttpExampleRetry". The function is re-executed in case of errors until the maximum number of retries occur. + * Retry policies: https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-error-pages?tabs=java + */ + @FunctionName("HttpExampleRetry") + @FixedDelayRetry(maxRetryCount = 3, delayInterval = "00:00:05") + public HttpResponseMessage HttpExampleRetry( + @HttpTrigger( + name = "req", + methods = {HttpMethod.GET, HttpMethod.POST}, + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + final ExecutionContext context) throws Exception { + context.getLogger().info("Java HTTP trigger processed a request."); + + if(count<3) { + count ++; + throw new Exception("error"); + } + + // Parse query parameter + final String query = request.getQueryParameters().get("name"); + final String name = request.getBody().orElse(query); + + if (name == null) { + return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("Please pass a name on the query string or in the request body").build(); + } else { + return request.createResponseBuilder(HttpStatus.OK).body(name).build(); + } + } + + /** + * This function listens at endpoint "/api/HttpTriggerJavaVersion". + * It can be used to verify the Java home and java version currently in use in your Azure function + */ + @FunctionName("HttpTriggerJavaVersion") + public static HttpResponseMessage HttpTriggerJavaVersion( + @HttpTrigger( + name = "req", + methods = {HttpMethod.GET, HttpMethod.POST}, + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + final ExecutionContext context + ) { + context.getLogger().info("Java HTTP trigger processed a request."); + final String javaVersion = getJavaVersion(); + context.getLogger().info("Function - HttpTriggerJavaVersion" + javaVersion); + return request.createResponseBuilder(HttpStatus.OK).body("HttpTriggerJavaVersion").build(); + } + + public static String getJavaVersion() { + return String.join(" - ", System.getProperty("java.home"), System.getProperty("java.version")); + } + + /** + * This function listens at endpoint "/api/StaticWebPage". + * It can be used to read and serve a static web page. + * Note: Read the file from the right location for local machine and azure portal usage. + */ + @FunctionName("StaticWebPage") + public HttpResponseMessage getStaticWebPage( + @HttpTrigger( + name = "req", + methods = {HttpMethod.GET, HttpMethod.POST}, + authLevel = AuthorizationLevel.ANONYMOUS) + HttpRequestMessage> request, + final ExecutionContext context) { + context.getLogger().info("Java HTTP trigger processed a request."); + + File htmlFile = new File("index.html"); + try{ + byte[] fileContent = Files.readAllBytes(htmlFile.toPath()); + return request.createResponseBuilder(HttpStatus.OK).body(fileContent).build(); + }catch (Exception e){ + context.getLogger().info("Error reading file."); + return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR).build(); + } + } +} diff --git a/samples/triggers-bindings-example/src/main/java/com/functions/KafkaTriggerFunction.java b/samples/triggers-bindings-example/src/main/java/com/functions/KafkaTriggerFunction.java new file mode 100644 index 00000000..468c1618 --- /dev/null +++ b/samples/triggers-bindings-example/src/main/java/com/functions/KafkaTriggerFunction.java @@ -0,0 +1,53 @@ +package com.functions; + +import java.util.*; + +import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.*; + + +import java.util.Optional; + +public class KafkaTriggerFunction { + + @FunctionName("HttpTriggerAndKafkaOutput") + public HttpResponseMessage HttpTriggerAndKafkaOutput( + @HttpTrigger(name = "req", methods = {HttpMethod.GET}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @KafkaOutput( + name = "httpTriggerAndKafkaOutput", + topic = "ci", + brokerList = "%BrokerList%", + username = "%ConfluentCloudUsername%", + password = "%ConfluentCloudPassword%", + authenticationMode = BrokerAuthenticationMode.PLAIN, + sslCaLocation = "confluent_cloud_cacert.pem", + protocol = BrokerProtocol.SASLSSL + ) OutputBinding output, + final ExecutionContext context) { + String message = request.getQueryParameters().get("message"); + message = request.getBody().orElse(message); + context.getLogger().info("Java Http trigger received Message:" + message + " messages for Kafka Output"); + output.setValue(message); + return request.createResponseBuilder(HttpStatus.OK).body(message).build(); + } + + @FunctionName("KafkaTriggerAndKafkaOutput") + public void KafkaTriggerAndKafkaOutput( + @KafkaTrigger( + name = "kafkaTriggerAndKafkaOutput", + topic = "ci", + brokerList = "%BrokerList%", + consumerGroup = "$Default", + username = "%ConfluentCloudUsername%", + password = "%ConfluentCloudPassword%", + authenticationMode = BrokerAuthenticationMode.PLAIN, + protocol = BrokerProtocol.SASLSSL, + sslCaLocation = "confluent_cloud_cacert.pem", + dataType = "string" + ) String message, + @QueueOutput(name = "output", queueName = "test-kafka-output-cardinality-one-java", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context) { + context.getLogger().info("Java Kafka Output function processed a message: " + message); + output.setValue(message); + } +} diff --git a/samples/triggers-bindings-example/src/main/java/com/functions/QueueTriggerFunction.java b/samples/triggers-bindings-example/src/main/java/com/functions/QueueTriggerFunction.java new file mode 100644 index 00000000..ef90e5e1 --- /dev/null +++ b/samples/triggers-bindings-example/src/main/java/com/functions/QueueTriggerFunction.java @@ -0,0 +1,79 @@ +package com.functions; + +import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.*; + +import java.util.*; + +/** + * Azure Functions with Azure Storage Queue. + * https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-queue-trigger?tabs=java + */ +public class QueueTriggerFunction { + /** + * This function will be invoked when a http request is received. The message contents are provided as output to this function. + */ + @FunctionName("QueueTriggerAndOutput") + public void queuetriggerandoutput( + @QueueTrigger(name = "message", queueName = "test-input-java", connection = "AzureWebJobsStorage") String message, + @QueueOutput(name = "output", queueName = "test-output-java", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Queue trigger function processed a message: " + message); + output.setValue(message); + } + + @FunctionName("QueueOutputPOJOList") + public HttpResponseMessage QueueOutputPOJOList(@HttpTrigger(name = "req", methods = {HttpMethod.GET, + HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @QueueOutput(name = "output", queueName = "test-output-java-pojo", connection = "AzureWebJobsStorage") OutputBinding> itemsOut, + final ExecutionContext context) { + context.getLogger().info("Java HTTP trigger processed a request."); + + String query = request.getQueryParameters().get("queueMessageId"); + String queueMessageId = request.getBody().orElse(query); + itemsOut.setValue(new ArrayList()); + if (queueMessageId != null) { + TestData testData1 = new TestData(); + testData1.id = "msg1" + queueMessageId; + TestData testData2 = new TestData(); + testData2.id = "msg2" + queueMessageId; + + itemsOut.getValue().add(testData1); + itemsOut.getValue().add(testData2); + + return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + queueMessageId).build(); + } else { + return request.createResponseBuilder(HttpStatus.INTERNAL_SERVER_ERROR) + .body("Did not find expected items in CosmosDB input list").build(); + } + } + + @FunctionName("QueueTriggerAndOutputPOJO") + public void queuetriggerandoutputPOJO( + @QueueTrigger(name = "message", queueName = "test-input-java-pojo", connection = "AzureWebJobsStorage") TestData message, + @QueueOutput(name = "output", queueName = "test-output-java-pojo", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Queue trigger POJO function processed a message: " + message.id); + output.setValue(message); + } + + @FunctionName("QueueTriggerMetadata") + public void QueueTriggerMetadata( + @QueueTrigger(name = "message", queueName = "test-input-java-metadata", connection = "AzureWebJobsStorage") String message, @BindingName("Id") String metadataId, + @QueueOutput(name = "output", queueName = "test-output-java-metadata", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Queue trigger function processed a message: " + message + " whith metadaId:" + metadataId); + TestData testData = new TestData(); + testData.id = metadataId; + output.setValue(testData); + } + + public static class TestData { + public String id; + } +} + + diff --git a/samples/triggers-bindings-example/src/main/java/com/functions/ServiceBusQueueTriggerFunction.java b/samples/triggers-bindings-example/src/main/java/com/functions/ServiceBusQueueTriggerFunction.java new file mode 100644 index 00000000..a5ebf8dc --- /dev/null +++ b/samples/triggers-bindings-example/src/main/java/com/functions/ServiceBusQueueTriggerFunction.java @@ -0,0 +1,49 @@ +package com.functions; + +import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.*; + +import java.util.*; + +/** + * Azure Functions with Azure Service Bus Queue. + * https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus-trigger?tabs=java + */ +public class ServiceBusQueueTriggerFunction { + + @FunctionName("ServiceBusQueueTrigger") + public void serviceBusQueueTrigger( + @ServiceBusQueueTrigger(name = "message", queueName = "SBQueueNameSingle", connection = "AzureWebJobsServiceBus") String message, + @QueueOutput(name = "output", queueName = "test-servicebusqueuesingle-java", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Service Bus Queue trigger function processed a message: " + message); + output.setValue(message); + } + + @FunctionName("ServiceBusQueueBatchTrigger") + public void serviceBusQueueBatchTrigger( + @ServiceBusQueueTrigger(name = "message", queueName = "SBQueueNameBatch", connection = "AzureWebJobsServiceBus", cardinality = Cardinality.MANY, dataType = "String") String[] messages, + @QueueOutput(name = "output", queueName = "test-servicebusqueuebatch-java", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Service Bus Queue trigger function processed a message: " + messages[0]); + output.setValue(messages[0]); + } + + /** + * This function will be invoked when a http request is received. The message contents are provided as output to this function. + */ + @FunctionName("ServiceBusQueueOutput") + public void serviceBusQueueOutput( + @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @ServiceBusQueueOutput(name = "output", queueName = "%SBQueueName%", connection = "AzureWebJobsServiceBus") OutputBinding output, + final ExecutionContext context + ) { + String message = request.getBody().orElse("default message"); + output.setValue(message); + context.getLogger().info("Java Service Bugs Queue output function got a message: " + message); + } + + +} diff --git a/samples/triggers-bindings-example/src/main/java/com/functions/ServiceBusTopicTriggerFunction.java b/samples/triggers-bindings-example/src/main/java/com/functions/ServiceBusTopicTriggerFunction.java new file mode 100644 index 00000000..28724629 --- /dev/null +++ b/samples/triggers-bindings-example/src/main/java/com/functions/ServiceBusTopicTriggerFunction.java @@ -0,0 +1,48 @@ +package com.functions; + + +import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.*; + +import java.util.*; + +/** + * Azure Functions with Azure Service Bus Topic. + * https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-service-bus-trigger?tabs=java + */ +public class ServiceBusTopicTriggerFunction { + + @FunctionName("ServiceBusTopicTrigger") + public void serviceBusTopicTrigger( + @ServiceBusTopicTrigger(name = "message", topicName = "SBTopicNameSingle", subscriptionName = "SBTopicNameSingleSubName", connection = "AzureWebJobsServiceBus") String message, + @QueueOutput(name = "output", queueName = "test-servicebustopicbatch-java", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Service Bus Topic trigger function processed a message: " + message); + output.setValue(message); + } + + @FunctionName("ServiceBusTopicBatchTrigger") + public void serviceBusTopicBatchTrigger( + @ServiceBusTopicTrigger(name = "message", topicName = "SBTopicNameBatch", subscriptionName = "SBTopicNameBatchSubName", connection = "AzureWebJobsServiceBus", cardinality = Cardinality.MANY, dataType = "String") List messages, + @QueueOutput(name = "output", queueName = "test-servicebustopicbatch-java", connection = "AzureWebJobsStorage") OutputBinding output, + final ExecutionContext context + ) { + context.getLogger().info("Java Service Bus Topic trigger function processed a message: " + messages.get(0)); + output.setValue(messages.get(0)); + } + + /** + * This function will be invoked when a http request is received. The message contents are provided as output to this function. + */ + @FunctionName("ServiceBusTopicOutput") + public void serviceBusTopicOutput( + @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @ServiceBusTopicOutput(name = "message", topicName = "%SBTopicName%", subscriptionName = "%SBTopicSubName%", connection = "AzureWebJobsServiceBus") OutputBinding output, + final ExecutionContext context + ) { + String message = request.getBody().orElse("default message"); + output.setValue(message); + context.getLogger().info("Java Service Bus Topic output function got a message: " + message); + } +} diff --git a/samples/triggers-bindings-example/src/main/java/com/functions/TableFunction.java b/samples/triggers-bindings-example/src/main/java/com/functions/TableFunction.java new file mode 100644 index 00000000..0b884575 --- /dev/null +++ b/samples/triggers-bindings-example/src/main/java/com/functions/TableFunction.java @@ -0,0 +1,52 @@ +package com.functions; + +import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.*; + +import java.util.*; + +/** + * Azure Functions with Azure Storage table. + * https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-storage-table-output?tabs=java + */ +public class TableFunction { + /** + * This function will be invoked when a new queue message is received. + */ + @FunctionName("TableInput") + public void tableInputJava( + @QueueTrigger(name = "message", queueName = "mytablequeue", connection = "AzureWebJobsStorage") String message, + @TableInput(name = "personEntity", tableName = "Person", rowKey = "{queueTrigger}", partitionKey = "firstPartition", connection = "AzureWebJobsStorage") String personEntity, + final ExecutionContext context + ) { + context.getLogger().info("Java Queue trigger function processed a new message: " + message); + context.getLogger().info("Java Table Input function processed a Person entity:" + personEntity); + } + + /** + * This function will be invoked when a new http request is received at the specified path. + */ + @FunctionName("TableOutput") + public void tableOutputJava( + @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + @TableOutput(name = "myOutputTable", tableName = "Person", connection = "AzureWebJobsStorage") OutputBinding myOutputTable, + final ExecutionContext context + ) { + String httpbody = request.getBody().orElse("default"); + myOutputTable.setValue(new Person(httpbody + "Partition", httpbody + "Row", httpbody + "Name")); + context.getLogger().info("Java Table Output function write a new entity into table Person with name: " + httpbody + "Name"); + } + + public static class Person { + public String PartitionKey; + public String RowKey; + public String Name; + + public Person(String p, String r, String n) { + this.PartitionKey = p; + this.RowKey = r; + this.Name = n; + } + } +} + diff --git a/samples/triggers-bindings-example/src/main/java/com/functions/TimerTriggerFunction.java b/samples/triggers-bindings-example/src/main/java/com/functions/TimerTriggerFunction.java new file mode 100644 index 00000000..f30a658d --- /dev/null +++ b/samples/triggers-bindings-example/src/main/java/com/functions/TimerTriggerFunction.java @@ -0,0 +1,23 @@ +package com.functions; + +import com.microsoft.azure.functions.annotation.*; +import com.microsoft.azure.functions.*; +import java.time.*; + +/** + * Azure Functions with Timer trigger. + * https://docs.microsoft.com/en-us/azure/azure-functions/functions-bindings-timer?tabs=java + */ +public class TimerTriggerFunction { + /** + * This function will be invoked periodically according to the specified schedule. + * The below function is executed each time the minutes have a value divisible by five + */ + @FunctionName("TimerTrigger") + public void timerHandler( + @TimerTrigger(name = "timerInfo", schedule = "0 */5 * * * *") String timerInfo, + final ExecutionContext context + ) { + context.getLogger().info("Java Timer trigger function executed at: " + LocalDateTime.now()); + } +} diff --git a/samples/triggers-bindings-example/src/test/java/com/functions/EventHubTriggerFunctionTest.java b/samples/triggers-bindings-example/src/test/java/com/functions/EventHubTriggerFunctionTest.java new file mode 100644 index 00000000..99e35e7e --- /dev/null +++ b/samples/triggers-bindings-example/src/test/java/com/functions/EventHubTriggerFunctionTest.java @@ -0,0 +1,143 @@ +package com.functions; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.OutputBinding; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import java.nio.charset.StandardCharsets; +import java.time.Instant; +import java.util.ArrayList; +import java.util.List; +import java.util.logging.Logger; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; + +public class EventHubTriggerFunctionTest { + /** + * Unit test for EventHubTriggerFunction class. + */ + // Define the shared variables globally. + ExecutionContext context; + EventHubTriggerFunction eventHubTriggerFunction; + OutputBinding output; + OutputBinding outputAsByteArray; + + // Before the tests are run, execute the below configurations. + @BeforeEach + void setup() { + // Mock the Execution Context and its logger since we will be using this in the Azure Functions. + this.context = mock(ExecutionContext.class); + doReturn(Logger.getGlobal()).when(context).getLogger(); + + // Define the properties of the OutputBinding object. + this.output = new OutputBinding() { + String value; + + @Override + public String getValue() { + return value; + } + + @Override + public void setValue(String value) { + this.value = value; + } + }; + + this.outputAsByteArray = new OutputBinding() { + byte[] value; + + @Override + public byte[] getValue() { + return value; + } + + @Override + public void setValue(byte[] value) { + this.value = value; + } + }; + + // Instantiate the EventHubTriggerFunction class so that we can call its methods. + this.eventHubTriggerFunction = new EventHubTriggerFunction(); + } + + @Test + public void testEventHubTriggerAndOutputJSONJava() throws Exception { + List messages = new ArrayList<>(); + for(int i = 0; i < 3; ++i) { + messages.add("Message" + i); + } + + eventHubTriggerFunction.EventHubTriggerAndOutputJSON(messages, output, context); + + assertEquals(messages.get(0), output.getValue()); + } + + @Test + public void testEventHubTriggerAndOutputStringJava() throws Exception { + EventHubTriggerFunction.SystemProperty[] systemPropertiesArray; + String[] messages = new String[4]; + List systemPropertyList = new ArrayList<>(); + + for(int i = 0; i < 3; ++i) { + messages[i] = "Message" + i; + EventHubTriggerFunction.SystemProperty systemProperty = new EventHubTriggerFunction.SystemProperty("Sequence Number: " + i, + "Offset: " + (i * 2), + "Partition Key: " + (i * 3), + "EnqueuedTimeUtc: " + Instant.now()); + + systemPropertyList.add(systemProperty); + } + + systemPropertiesArray = systemPropertyList.toArray(new EventHubTriggerFunction.SystemProperty[0]); + + eventHubTriggerFunction.EventHubTriggerAndOutputString(messages, systemPropertiesArray, output, context); + + assertEquals(messages[0], output.getValue()); + } + + @Test + public void testEventHubTriggerCardinalityOneJava() throws Exception { + String message = "Hello World!"; + + eventHubTriggerFunction.EventHubTriggerCardinalityOne(message, output, context); + + assertEquals(message, output.getValue()); + } + + @Test + public void testEventHubTriggerAndOutputBinaryCardinalityManyListBinaryJava() throws Exception { + String message = "Hello World!"; + byte[] messageAsByteArray = message.getBytes(StandardCharsets.UTF_8); + + List messageByteArrayList = new ArrayList<>(); + messageByteArrayList.add(messageAsByteArray); + + eventHubTriggerFunction.EventHubTriggerAndOutputBinaryCardinalityManyListBinary(messageByteArrayList, outputAsByteArray, context); + + assertEquals(messageByteArrayList.get(0), outputAsByteArray.getValue()); + } + + @Test + public void testEventHubTriggerAndOutputBinaryCardinalityOneJava() throws Exception { + String message = "Hello World!"; + byte[] messageAsByteArray = message.getBytes(StandardCharsets.UTF_8); + + eventHubTriggerFunction.EventHubTriggerAndOutputBinaryCardinalityOne(messageAsByteArray, outputAsByteArray, context); + + assertEquals(messageAsByteArray, outputAsByteArray.getValue()); + } + + @Test + public void testEventHubTriggerAndOutputBinaryCardinalityManyArrayBinaryJava() throws Exception { + byte[][] twoDimensionalByteArray = new byte[2][2]; + + eventHubTriggerFunction.EventHubTriggerAndOutputBinaryCardinalityManyArrayBinary(twoDimensionalByteArray, outputAsByteArray, context); + + assertEquals(twoDimensionalByteArray[0], outputAsByteArray.getValue()); + } +} diff --git a/samples/triggers-bindings-example/src/test/java/com/functions/FunctionTest.java b/samples/triggers-bindings-example/src/test/java/com/functions/FunctionTest.java new file mode 100644 index 00000000..6e4d372d --- /dev/null +++ b/samples/triggers-bindings-example/src/test/java/com/functions/FunctionTest.java @@ -0,0 +1,58 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for + * license information. + */ +package com.functions; + +import com.microsoft.azure.functions.*; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; + +import java.util.*; +import java.util.logging.Logger; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.ArgumentMatchers.*; +import static org.mockito.Mockito.*; + + +/** + * Unit test for Function class. + */ +public class FunctionTest { + /** + * Unit test for HttpTriggerJava method. + */ + @Test + public void testHttpTriggerJava() throws Exception { + // Setup + @SuppressWarnings("unchecked") + final HttpRequestMessage> req = mock(HttpRequestMessage.class); + + final Map queryParams = new HashMap<>(); + queryParams.put("name", "Azure"); + doReturn(queryParams).when(req).getQueryParameters(); + + final Optional queryBody = Optional.empty(); + doReturn(queryBody).when(req).getBody(); + + doAnswer(new Answer() { + @Override + public HttpResponseMessage.Builder answer(InvocationOnMock invocation) { + HttpStatus status = (HttpStatus) invocation.getArguments()[0]; + return new HttpResponseMessageMock.HttpResponseMessageBuilderMock().status(status); + } + }).when(req).createResponseBuilder(any(HttpStatus.class)); + + final ExecutionContext context = mock(ExecutionContext.class); + doReturn(Logger.getGlobal()).when(context).getLogger(); + + // Invoke + final HttpResponseMessage ret = new Function().run(req, context); + + // Verify + assertEquals(ret.getStatus(), HttpStatus.OK); + } +} diff --git a/samples/triggers-bindings-example/src/test/java/com/functions/HttpResponseMessageMock.java b/samples/triggers-bindings-example/src/test/java/com/functions/HttpResponseMessageMock.java new file mode 100644 index 00000000..9c8665a2 --- /dev/null +++ b/samples/triggers-bindings-example/src/test/java/com/functions/HttpResponseMessageMock.java @@ -0,0 +1,86 @@ +/** + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for + * license information. + */ +package com.functions; + +import com.microsoft.azure.functions.*; + +import java.util.Map; +import java.util.HashMap; + +/** + * The mock for HttpResponseMessage, can be used in unit tests to verify if the + * returned response by HTTP trigger function is correct or not. + */ +public class HttpResponseMessageMock implements HttpResponseMessage { + private int httpStatusCode; + private HttpStatusType httpStatus; + private Object body; + private Map headers; + + public HttpResponseMessageMock(HttpStatusType status, Map headers, Object body) { + this.httpStatus = status; + this.httpStatusCode = status.value(); + this.headers = headers; + this.body = body; + } + + @Override + public HttpStatusType getStatus() { + return this.httpStatus; + } + + @Override + public int getStatusCode() { + return httpStatusCode; + } + + @Override + public String getHeader(String key) { + return this.headers.get(key); + } + + @Override + public Object getBody() { + return this.body; + } + + public static class HttpResponseMessageBuilderMock implements HttpResponseMessage.Builder { + private Object body; + private int httpStatusCode; + private Map headers = new HashMap<>(); + private HttpStatusType httpStatus; + + public Builder status(HttpStatus status) { + this.httpStatusCode = status.value(); + this.httpStatus = status; + return this; + } + + @Override + public Builder status(HttpStatusType httpStatusType) { + this.httpStatusCode = httpStatusType.value(); + this.httpStatus = httpStatusType; + return this; + } + + @Override + public HttpResponseMessage.Builder header(String key, String value) { + this.headers.put(key, value); + return this; + } + + @Override + public HttpResponseMessage.Builder body(Object body) { + this.body = body; + return this; + } + + @Override + public HttpResponseMessage build() { + return new HttpResponseMessageMock(this.httpStatus, this.headers, this.body); + } + } +}