diff --git a/azure-pipelines.yml b/azure-pipelines.yml
index fec68ad2..e671d684 100644
--- a/azure-pipelines.yml
+++ b/azure-pipelines.yml
@@ -11,7 +11,7 @@ steps:
checkLatest: true
- pwsh: |
Get-Command mvn
- displayName: 'Installing Maven'
+ displayName: 'Check Maven is installed'
- pwsh: |
$buildNumber = 0
if($env:APPVEYOR_REPO_TAG -eq "true") {
@@ -32,10 +32,10 @@ steps:
CleanTargetFolder: true
displayName: 'Copying files for artifacts'
- pwsh: |
- .\setup-tests.ps1
+ .\setup-tests-pipeline.ps1
displayName: 'Setting tests'
- pwsh: |
- .\run-tests.ps1
+ .\build-run-tests-pipeline.ps1
env:
AzureWebJobsStorage: $(AzureWebJobsStorage)
AzureWebJobsCosmosDBConnectionString: $(AzureWebJobsCosmosDBConnectionString)
@@ -47,8 +47,25 @@ steps:
SBTopicSubName: $(SBTopicSubName)
CosmosDBDatabaseName: $(CosmosDBDatabaseName)
SBQueueName: $(SBQueueName)
- displayName: 'running tests'
- continueOnError: true
+ displayName: 'Build & Run tests for java 8'
+ continueOnError: false
+- pwsh: |
+ .\build-run-tests-pipeline.ps1
+ env:
+ JAVA_HOME: 'C:\Program Files\Java\zulu-11-azure-jdk_11.33.15-11.0.4-win_x64'
+ AzureWebJobsStorage: $(AzureWebJobsStorage)
+ AzureWebJobsCosmosDBConnectionString: $(AzureWebJobsCosmosDBConnectionString)
+ AzureWebJobsServiceBus: $(AzureWebJobsServiceBus)
+ AzureWebJobsEventHubSender_2: $(AzureWebJobsEventHubSender_2)
+ AzureWebJobsEventHubReceiver: $(AzureWebJobsEventHubReceiver)
+ AzureWebJobsEventHubSender: $(AzureWebJobsEventHubSender)
+ AzureWebJobsEventHubPath: $(AzureWebJobsEventHubPath)
+ SBTopicName: $(SBTopicName)
+ SBTopicSubName: $(SBTopicSubName)
+ CosmosDBDatabaseName: $(CosmosDBDatabaseName)
+ SBQueueName: $(SBQueueName)
+ displayName: 'Build & Run tests for java 11'
+ continueOnError: false
- task: CopyFiles@2
inputs:
SourceFolder: '$(System.DefaultWorkingDirectory)/testResults'
diff --git a/build-run-tests-pipeline.ps1 b/build-run-tests-pipeline.ps1
new file mode 100644
index 00000000..956b47c7
--- /dev/null
+++ b/build-run-tests-pipeline.ps1
@@ -0,0 +1,53 @@
+# A function that checks exit codes and fails script if an error is found
+function StopOnFailedExecution {
+ if ($LastExitCode)
+ {
+ exit $LastExitCode
+ }
+}
+
+function RunTest([string] $project, [string] $description,[bool] $skipBuild = $false, $filter = $null) {
+ Write-Host "Running test: $description" -ForegroundColor DarkCyan
+ Write-Host "-----------------------------------------------------------------------------" -ForegroundColor DarkCyan
+ Write-Host
+
+ $cmdargs = "test", "$project", "-v", "q", "-l", "trx", "-r",".\testResults"
+
+ if ($filter) {
+ $cmdargs += "--filter", "$filter"
+ }
+
+ & dotnet $cmdargs | Out-Host
+ $r = $?
+
+ Write-Host
+ Write-Host "-----------------------------------------------------------------------------" -ForegroundColor DarkCyan
+ Write-Host
+
+ return $r
+}
+
+$currDir = Get-Location
+
+Write-Host "Building endtoendtests...."
+$Env:Path = $Env:Path+";$currDir\Azure.Functions.Cli"
+Push-Location -Path "./endtoendtests" -StackName javaWorkerDir
+Write-Host "Building azure-functions-maven-com.microsoft.azure.functions.endtoendtests"
+cmd.exe /c '.\..\mvnBuildSkipTests.bat'
+StopOnFailedExecution
+Pop-Location -StackName "javaWorkerDir"
+
+$tests = @(
+ @{project ="endtoendtests\Azure.Functions.Java.Tests.E2E\Azure.Functions.Java.Tests.E2E\Azure.Functions.Java.Tests.E2E.csproj"; description="E2E integration tests"}
+)
+
+$success = $true
+$testRunSucceeded = $true
+
+foreach ($test in $tests){
+ $testRunSucceeded = RunTest $test.project $test.description $testRunSucceeded $test.filter
+ $success = $testRunSucceeded -and $success
+}
+
+if (-not $success) { exit 1 }
+
diff --git a/endtoendtests/pom.xml b/endtoendtests/pom.xml
index 8edecacf..f0edcf24 100644
--- a/endtoendtests/pom.xml
+++ b/endtoendtests/pom.xml
@@ -67,6 +67,34 @@
com.microsoft.azure.functions
azure-functions-java-library
+
+ org.apache.httpcomponents
+ httpclient
+ 4.5.9
+
+
+ com.google.guava
+ guava
+ 28.1-jre
+
+
+ com.h2database
+ h2
+ 1.4.199
+ runtime
+
+
+ com.google.code.gson
+ gson
+ 2.8.6
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.10
+
+
diff --git a/endtoendtests/src/main/java/com/microsoft/azure/functions/endtoend/HttpTriggerTests.java b/endtoendtests/src/main/java/com/microsoft/azure/functions/endtoend/HttpTriggerTests.java
index b67c2839..cf57768a 100644
--- a/endtoendtests/src/main/java/com/microsoft/azure/functions/endtoend/HttpTriggerTests.java
+++ b/endtoendtests/src/main/java/com/microsoft/azure/functions/endtoend/HttpTriggerTests.java
@@ -4,6 +4,23 @@
import com.microsoft.azure.functions.*;
import java.util.*;
+import com.google.gson.Gson;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.impl.client.CloseableHttpClient;
+import org.apache.http.impl.client.HttpClients;
+
+import java.sql.Connection;
+import java.sql.DriverManager;
+import java.sql.Statement;
+
+import java.io.InputStream;
+import com.google.common.io.CharStreams;
+import java.io.IOException;
+import java.io.InputStreamReader;
+
+import org.apache.commons.lang3.SystemUtils;
+
/**
* Azure Functions with HTTP trigger.
*/
@@ -15,7 +32,7 @@ public class HttpTriggerTests {
public HttpResponseMessage HttpTriggerJava(
@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.");
// Parse query parameters
@@ -23,6 +40,20 @@ public HttpResponseMessage HttpTriggerJava(
String name = request.getBody().orElse(query);
String readEnv = System.getenv("AzureWebJobsStorage");
+ try (Connection connection = DriverManager.getConnection("jdbc:h2:mem:test")) {
+ try (Statement statement = connection.createStatement()) {
+ statement.execute("select 1");
+ }
+ }
+
+ Gson a = new Gson();
+
+// if(!SystemUtils.IS_JAVA_15) {
+// context.getLogger().info("Java version not 15");
+// }
+
+ get("https://httpstat.us/200");
+
if (name == null ) {
return request.createResponseBuilder(HttpStatus.BAD_REQUEST).body("Please pass a name on the query string or in the request body").build();
}
@@ -32,6 +63,18 @@ public HttpResponseMessage HttpTriggerJava(
return request.createResponseBuilder(HttpStatus.OK).body("Hello, " + name).build();
}
+ private static String get(String url) throws IOException {
+ CloseableHttpClient httpClient = HttpClients.createDefault();
+ HttpGet httpGet = new HttpGet(url);
+ httpGet.setHeader("Accept", "application/json");
+ HttpResponse response = httpClient.execute(httpGet);
+ InputStream content = response.getEntity().getContent();
+ String body = CharStreams.toString(new InputStreamReader(content));
+ content.close();
+ httpClient.close();
+ return "Response from " + url + " was: " + body;
+ }
+
@FunctionName("HttpTriggerJavaThrows")
public HttpResponseMessage HttpTriggerThrows(
@HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request,
diff --git a/pom.xml b/pom.xml
index d5eb2e54..84dcb86b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -22,7 +22,7 @@
UTF-8
- 1.3.1-SNAPSHOT
+ 1.3.1
diff --git a/setup-tests-pipeline.ps1 b/setup-tests-pipeline.ps1
new file mode 100644
index 00000000..d03017b8
--- /dev/null
+++ b/setup-tests-pipeline.ps1
@@ -0,0 +1,39 @@
+Write-Host "$args[0]"
+Write-Host $args[0]
+
+$skipCliDownload = $false
+if($args[0])
+{
+ $skipCliDownload = $args[0]
+}
+Write-Host $skipCliDownload
+
+$currDir = Get-Location
+if(!$skipCliDownload)
+{
+ Write-Host "Deleting Functions Core Tools if exists...."
+ Remove-Item -Force ./Azure.Functions.Cli.zip -ErrorAction Ignore
+ Remove-Item -Recurse -Force ./Azure.Functions.Cli -ErrorAction Ignore
+
+ Write-Host "Downloading Functions Core Tools...."
+ Invoke-RestMethod -Uri 'https://functionsclibuilds.blob.core.windows.net/builds/3/latest/version.txt' -OutFile version.txt
+ Write-Host "Using Functions Core Tools version: $(Get-Content -Raw version.txt)"
+ $version = "$(Get-Content -Raw version.txt)"
+ Remove-Item version.txt
+
+ if ($version -and $version.trim())
+ {
+ $env:CORE_TOOLS_URL = "https://functionsclibuilds.blob.core.windows.net/builds/3/latest/Azure.Functions.Cli.win-x86.zip"
+ }
+ Write-Host "CORE_TOOLS_URL: $env:CORE_TOOLS_URL"
+ $output = "$currDir\Azure.Functions.Cli.zip"
+ $wc = New-Object System.Net.WebClient
+ $wc.DownloadFile($env:CORE_TOOLS_URL, $output)
+
+ Write-Host "Extracting Functions Core Tools...."
+ Expand-Archive ".\Azure.Functions.Cli.zip" -DestinationPath ".\Azure.Functions.Cli"
+}
+Write-Host "Copying azure-functions-java-worker to Functions Host workers directory...."
+Get-ChildItem -Path .\target\* -Include 'azure*' -Exclude '*shaded.jar','*tests.jar' | %{ Copy-Item $_.FullName ".\Azure.Functions.Cli\workers\java\azure-functions-java-worker.jar" }
+Copy-Item ".\worker.config.json" ".\Azure.Functions.Cli\workers\java"
+
diff --git a/setup-tests.ps1 b/setup-tests.ps1
index 3fae4bc7..eb812254 100644
--- a/setup-tests.ps1
+++ b/setup-tests.ps1
@@ -1,9 +1,9 @@
# A function that checks exit codes and fails script if an error is found
function StopOnFailedExecution {
- if ($LastExitCode)
- {
- exit $LastExitCode
+ if ($LastExitCode)
+ {
+ exit $LastExitCode
}
}
Write-Host "$args[0]"
@@ -24,13 +24,14 @@ if(!$skipCliDownload)
Remove-Item -Recurse -Force ./Azure.Functions.Cli -ErrorAction Ignore
Write-Host "Downloading Functions Core Tools...."
- Invoke-RestMethod -Uri 'https://functionsclibuilds.blob.core.windows.net/builds/2/latest/version.txt' -OutFile version.txt
+ Invoke-RestMethod -Uri 'https://functionsclibuilds.blob.core.windows.net/builds/3/latest/version.txt' -OutFile version.txt
Write-Host "Using Functions Core Tools version: $(Get-Content -Raw version.txt)"
+ $version = "$(Get-Content -Raw version.txt)"
Remove-Item version.txt
- if (-not (Test-Path env:CORE_TOOLS_URL))
- {
- $env:CORE_TOOLS_URL = "https://functionsclibuilds.blob.core.windows.net/builds/2/latest/Azure.Functions.Cli.win-x86.zip"
+ if ($version -and $version.trim())
+ {
+ $env:CORE_TOOLS_URL = "https://functionsclibuilds.blob.core.windows.net/builds/3/latest/Azure.Functions.Cli.win-x86.zip"
}
Write-Host "CORE_TOOLS_URL: $env:CORE_TOOLS_URL"
$output = "$currDir\Azure.Functions.Cli.zip"
@@ -41,13 +42,13 @@ if(!$skipCliDownload)
Expand-Archive ".\Azure.Functions.Cli.zip" -DestinationPath ".\Azure.Functions.Cli"
}
Write-Host "Copying azure-functions-java-worker to Functions Host workers directory...."
-Get-ChildItem -Path .\target\* -Include 'azure*' -Exclude '*shaded.jar' | %{ Copy-Item $_.FullName ".\Azure.Functions.Cli\workers\java\azure-functions-java-worker.jar" }
+Get-ChildItem -Path .\target\* -Include 'azure*' -Exclude '*shaded.jar','*tests.jar' | %{ Copy-Item $_.FullName ".\Azure.Functions.Cli\workers\java\azure-functions-java-worker.jar" }
Copy-Item ".\worker.config.json" ".\Azure.Functions.Cli\workers\java"
Write-Host "Building endtoendtests...."
$Env:Path = $Env:Path+";$currDir\Azure.Functions.Cli"
Push-Location -Path "./endtoendtests" -StackName javaWorkerDir
-Write-Host "Building azure-functions-maven-com.microsoft.azure.functions.endtoendtests"
+Write-Host "Building azure-functions-maven-com.microsoft.azure.functions.endtoendtests"
cmd.exe /c '.\..\mvnBuildSkipTests.bat'
StopOnFailedExecution
Pop-Location -StackName "javaWorkerDir"
\ No newline at end of file
diff --git a/src/main/java/com/microsoft/azure/functions/worker/JavaWorkerClient.java b/src/main/java/com/microsoft/azure/functions/worker/JavaWorkerClient.java
index 112e11f4..803bb63b 100644
--- a/src/main/java/com/microsoft/azure/functions/worker/JavaWorkerClient.java
+++ b/src/main/java/com/microsoft/azure/functions/worker/JavaWorkerClient.java
@@ -28,7 +28,7 @@ public JavaWorkerClient(IApplication app) {
this.channel = chanBuilder.build();
this.peer = new AtomicReference<>(null);
this.handlerSuppliers = new HashMap<>();
- this.classPathProvider = new DefaultClassLoaderProvider();
+ this.classPathProvider = new FactoryClassLoader().createClassLoaderProvider();
this.addHandlers();
}
@@ -116,5 +116,5 @@ private synchronized void send(String requestId, MessageHandler, ?> marshaller
private final ManagedChannel channel;
private final AtomicReference peer;
private final Map>> handlerSuppliers;
- private final DefaultClassLoaderProvider classPathProvider;
+ private final ClassLoaderProvider classPathProvider;
}
diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/EnhancedJavaMethodExecutorImpl.java b/src/main/java/com/microsoft/azure/functions/worker/broker/EnhancedJavaMethodExecutorImpl.java
new file mode 100644
index 00000000..26e3c287
--- /dev/null
+++ b/src/main/java/com/microsoft/azure/functions/worker/broker/EnhancedJavaMethodExecutorImpl.java
@@ -0,0 +1,70 @@
+package com.microsoft.azure.functions.worker.broker;
+
+import java.lang.reflect.*;
+import java.util.*;
+
+import com.microsoft.azure.functions.worker.binding.*;
+import com.microsoft.azure.functions.worker.description.*;
+import com.microsoft.azure.functions.worker.reflect.*;
+import com.microsoft.azure.functions.rpc.messages.*;
+
+/**
+ * Used to executor of arbitrary Java method in any JAR using reflection.
+ * Thread-Safety: Multiple thread.
+ */
+public class EnhancedJavaMethodExecutorImpl implements JavaMethodExecutor {
+ public EnhancedJavaMethodExecutorImpl(FunctionMethodDescriptor descriptor, Map bindingInfos, ClassLoaderProvider classLoaderProvider)
+ throws ClassNotFoundException, NoSuchMethodException
+ {
+ descriptor.validateMethodInfo();
+
+ this.classLoader = classLoaderProvider.createClassLoader();
+ this.containingClass = getContainingClass(descriptor.getFullClassName());
+ this.overloadResolver = new ParameterResolver();
+
+ for (Method method : this.containingClass.getMethods()) {
+ if (method.getName().equals(descriptor.getMethodName())) {
+ this.overloadResolver.addCandidate(method);
+ }
+ }
+
+ if (!this.overloadResolver.hasCandidates()) {
+ throw new NoSuchMethodException("There are no methods named \"" + descriptor.getName() + "\" in class \"" + descriptor.getFullClassName() + "\"");
+ }
+
+ if (this.overloadResolver.hasMultipleCandidates()) {
+ throw new UnsupportedOperationException("Found more than one function with method name \"" + descriptor.getName() + "\" in class \"" + descriptor.getFullClassName() + "\"");
+ }
+
+ this.bindingDefinitions = new HashMap<>();
+
+ for (Map.Entry entry : bindingInfos.entrySet()) {
+ this.bindingDefinitions.put(entry.getKey(), new BindingDefinition(entry.getKey(), entry.getValue()));
+ }
+ }
+
+ public Map getBindingDefinitions() { return this.bindingDefinitions; }
+
+ public ParameterResolver getOverloadResolver() { return this.overloadResolver; }
+
+ public void execute(BindingDataStore dataStore) throws Exception {
+ try {
+ Thread.currentThread().setContextClassLoader(this.classLoader);
+ Object retValue = this.overloadResolver.resolve(dataStore)
+ .orElseThrow(() -> new NoSuchMethodException("Cannot locate the method signature with the given input"))
+ .invoke(() -> this.containingClass.newInstance());
+ dataStore.setDataTargetValue(BindingDataStore.RETURN_NAME, retValue);
+ } finally {
+ Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
+ }
+ }
+
+ private Class> getContainingClass(String className) throws ClassNotFoundException {
+ return Class.forName(className, true, this.classLoader);
+ }
+
+ private final Class> containingClass;
+ private final ClassLoader classLoader;
+ private final ParameterResolver overloadResolver;
+ private final Map bindingDefinitions;
+}
diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/FactoryJavaMethodExecutor.java b/src/main/java/com/microsoft/azure/functions/worker/broker/FactoryJavaMethodExecutor.java
new file mode 100644
index 00000000..f2c4babb
--- /dev/null
+++ b/src/main/java/com/microsoft/azure/functions/worker/broker/FactoryJavaMethodExecutor.java
@@ -0,0 +1,23 @@
+package com.microsoft.azure.functions.worker.broker;
+
+import com.microsoft.azure.functions.rpc.messages.BindingInfo;
+import com.microsoft.azure.functions.worker.WorkerLogManager;
+import com.microsoft.azure.functions.worker.description.FunctionMethodDescriptor;
+import com.microsoft.azure.functions.worker.reflect.ClassLoaderProvider;
+import org.apache.commons.lang3.SystemUtils;
+
+import java.net.MalformedURLException;
+import java.util.Map;
+
+public class FactoryJavaMethodExecutor {
+ public JavaMethodExecutor getJavaMethodExecutor(FunctionMethodDescriptor descriptor, Map bindings, ClassLoaderProvider classLoaderProvider)
+ throws MalformedURLException, ClassNotFoundException, NoSuchMethodException {
+ if(SystemUtils.IS_JAVA_1_8) {
+ WorkerLogManager.getSystemLogger().info("Loading JavaMethodExecutorImpl");
+ return new JavaMethodExecutorImpl(descriptor, bindings, classLoaderProvider);
+ } else {
+ WorkerLogManager.getSystemLogger().info("Loading EnhancedJavaMethodExecutorImpl");
+ return new EnhancedJavaMethodExecutorImpl(descriptor, bindings, classLoaderProvider);
+ }
+ }
+}
diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBroker.java b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBroker.java
index 8471df12..3838f050 100644
--- a/src/main/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBroker.java
+++ b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaFunctionBroker.java
@@ -31,7 +31,7 @@ public void loadMethod(FunctionMethodDescriptor descriptor, Map(descriptor.getName(), executor));
}
diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutor.java b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutor.java
index b87b4274..00e1d87b 100644
--- a/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutor.java
+++ b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutor.java
@@ -14,53 +14,10 @@
* Used to executor of arbitrary Java method in any JAR using reflection.
* Thread-Safety: Multiple thread.
*/
-public class JavaMethodExecutor {
- public JavaMethodExecutor(FunctionMethodDescriptor descriptor, Map bindingInfos, ClassLoaderProvider classLoaderProvider)
- throws MalformedURLException, ClassNotFoundException, NoSuchMethodException
- {
- descriptor.validateMethodInfo();
+public interface JavaMethodExecutor {
+ Map getBindingDefinitions();
- this.containingClass = getContainingClass(descriptor.getFullClassName(), classLoaderProvider);
- this.overloadResolver = new ParameterResolver();
+ ParameterResolver getOverloadResolver();
- for (Method method : this.containingClass.getMethods()) {
- if (method.getName().equals(descriptor.getMethodName())) {
- this.overloadResolver.addCandidate(method);
- }
- }
-
- if (!this.overloadResolver.hasCandidates()) {
- throw new NoSuchMethodException("There are no methods named \"" + descriptor.getName() + "\" in class \"" + descriptor.getFullClassName() + "\"");
- }
-
- if (this.overloadResolver.hasMultipleCandidates()) {
- throw new UnsupportedOperationException("Found more than one function with method name \"" + descriptor.getName() + "\" in class \"" + descriptor.getFullClassName() + "\"");
- }
-
- this.bindingDefinitions = new HashMap<>();
-
- for (Map.Entry entry : bindingInfos.entrySet()) {
- this.bindingDefinitions.put(entry.getKey(), new BindingDefinition(entry.getKey(), entry.getValue()));
- }
- }
-
- Map getBindingDefinitions() { return this.bindingDefinitions; }
-
- public ParameterResolver getOverloadResolver() { return this.overloadResolver; }
-
- void execute(BindingDataStore dataStore) throws Exception {
- Object retValue = this.overloadResolver.resolve(dataStore)
- .orElseThrow(() -> new NoSuchMethodException("Cannot locate the method signature with the given input"))
- .invoke(() -> this.containingClass.newInstance());
- dataStore.setDataTargetValue(BindingDataStore.RETURN_NAME, retValue);
- }
-
- Class> getContainingClass(String className, ClassLoaderProvider classLoaderProvider) throws ClassNotFoundException {
- ClassLoader classLoader = classLoaderProvider.getClassLoader();
- return Class.forName(className, true, classLoader);
- }
-
- private Class> containingClass;
- private final ParameterResolver overloadResolver;
- private final Map bindingDefinitions;
+ void execute(BindingDataStore dataStore) throws Exception;
}
diff --git a/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutorImpl.java b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutorImpl.java
new file mode 100644
index 00000000..9e58fb94
--- /dev/null
+++ b/src/main/java/com/microsoft/azure/functions/worker/broker/JavaMethodExecutorImpl.java
@@ -0,0 +1,66 @@
+package com.microsoft.azure.functions.worker.broker;
+
+import java.lang.reflect.*;
+import java.net.*;
+import java.util.*;
+
+import com.microsoft.azure.functions.annotation.*;
+import com.microsoft.azure.functions.worker.binding.*;
+import com.microsoft.azure.functions.worker.description.*;
+import com.microsoft.azure.functions.worker.reflect.*;
+import com.microsoft.azure.functions.rpc.messages.*;
+
+/**
+ * Used to executor of arbitrary Java method in any JAR using reflection.
+ * Thread-Safety: Multiple thread.
+ */
+public class JavaMethodExecutorImpl implements JavaMethodExecutor {
+ public JavaMethodExecutorImpl(FunctionMethodDescriptor descriptor, Map bindingInfos, ClassLoaderProvider classLoaderProvider)
+ throws MalformedURLException, ClassNotFoundException, NoSuchMethodException
+ {
+ descriptor.validateMethodInfo();
+
+ this.containingClass = getContainingClass(descriptor.getFullClassName(), classLoaderProvider);
+ this.overloadResolver = new ParameterResolver();
+
+ for (Method method : this.containingClass.getMethods()) {
+ if (method.getName().equals(descriptor.getMethodName())) {
+ this.overloadResolver.addCandidate(method);
+ }
+ }
+
+ if (!this.overloadResolver.hasCandidates()) {
+ throw new NoSuchMethodException("There are no methods named \"" + descriptor.getName() + "\" in class \"" + descriptor.getFullClassName() + "\"");
+ }
+
+ if (this.overloadResolver.hasMultipleCandidates()) {
+ throw new UnsupportedOperationException("Found more than one function with method name \"" + descriptor.getName() + "\" in class \"" + descriptor.getFullClassName() + "\"");
+ }
+
+ this.bindingDefinitions = new HashMap<>();
+
+ for (Map.Entry entry : bindingInfos.entrySet()) {
+ this.bindingDefinitions.put(entry.getKey(), new BindingDefinition(entry.getKey(), entry.getValue()));
+ }
+ }
+
+ public Map getBindingDefinitions() { return this.bindingDefinitions; }
+
+ public ParameterResolver getOverloadResolver() { return this.overloadResolver; }
+
+ public void execute(BindingDataStore dataStore) throws Exception {
+ Object retValue = this.overloadResolver.resolve(dataStore)
+ .orElseThrow(() -> new NoSuchMethodException("Cannot locate the method signature with the given input"))
+ .invoke(() -> this.containingClass.newInstance());
+ dataStore.setDataTargetValue(BindingDataStore.RETURN_NAME, retValue);
+ }
+
+ private Class> getContainingClass(String className, ClassLoaderProvider classLoaderProvider) throws ClassNotFoundException {
+ ClassLoader classLoader = classLoaderProvider.createClassLoader();
+ return Class.forName(className, true, classLoader);
+ }
+
+ private Class> containingClass;
+ private final ParameterResolver overloadResolver;
+ private final Map bindingDefinitions;
+}
diff --git a/src/main/java/com/microsoft/azure/functions/worker/reflect/ClassLoaderProvider.java b/src/main/java/com/microsoft/azure/functions/worker/reflect/ClassLoaderProvider.java
index ecef2d24..a8e52e6a 100644
--- a/src/main/java/com/microsoft/azure/functions/worker/reflect/ClassLoaderProvider.java
+++ b/src/main/java/com/microsoft/azure/functions/worker/reflect/ClassLoaderProvider.java
@@ -16,7 +16,7 @@ public interface ClassLoaderProvider {
void addDirectory(File directory) throws MalformedURLException, IOException;
/*
- * Gets the class loader with the required search paths
+ * Create the class loader with the required search paths
*/
- ClassLoader getClassLoader();
+ ClassLoader createClassLoader();
}
diff --git a/src/main/java/com/microsoft/azure/functions/worker/reflect/DefaultClassLoaderProvider.java b/src/main/java/com/microsoft/azure/functions/worker/reflect/DefaultClassLoaderProvider.java
index 67a74751..4aece1f7 100644
--- a/src/main/java/com/microsoft/azure/functions/worker/reflect/DefaultClassLoaderProvider.java
+++ b/src/main/java/com/microsoft/azure/functions/worker/reflect/DefaultClassLoaderProvider.java
@@ -19,10 +19,10 @@ public DefaultClassLoaderProvider() {
}
/*
- * @see com.microsoft.azure.functions.reflect.ClassLoaderProvider#getClassLoader()
+ * @see com.microsoft.azure.functions.reflect.ClassLoaderProvider#createClassLoader()
*/
@Override
- public ClassLoader getClassLoader() {
+ public ClassLoader createClassLoader() {
URL[] urlsForClassLoader = new URL[urls.size()];
urls.toArray(urlsForClassLoader);
@@ -59,6 +59,8 @@ public void addUrl(URL url) throws IOException {
WorkerLogManager.getSystemLogger().info("Loading file URL: " + url);
urls.add(url);
+
+
addUrlToSystemClassLoader(url);
}
diff --git a/src/main/java/com/microsoft/azure/functions/worker/reflect/EnhancedClassLoaderProvider.java b/src/main/java/com/microsoft/azure/functions/worker/reflect/EnhancedClassLoaderProvider.java
new file mode 100644
index 00000000..7e2bf923
--- /dev/null
+++ b/src/main/java/com/microsoft/azure/functions/worker/reflect/EnhancedClassLoaderProvider.java
@@ -0,0 +1,76 @@
+package com.microsoft.azure.functions.worker.reflect;
+
+import java.io.*;
+import java.net.*;
+import java.sql.Driver;
+import java.util.*;
+import java.util.concurrent.*;
+
+
+import com.microsoft.azure.functions.worker.*;
+
+public class EnhancedClassLoaderProvider implements ClassLoaderProvider {
+ public EnhancedClassLoaderProvider() {
+ urls = Collections.newSetFromMap(new ConcurrentHashMap());
+ }
+
+ /*
+ * @see com.microsoft.azure.functions.reflect.ClassLoaderProvider#createClassLoader()
+ */
+ @Override
+ public ClassLoader createClassLoader() {
+ URL[] urlsForClassLoader = new URL[urls.size()];
+ urls.toArray(urlsForClassLoader);
+
+ URLClassLoader classLoader = new URLClassLoader(urlsForClassLoader);
+ loadDrivers(classLoader);
+ return classLoader;
+ }
+
+ private void loadDrivers(URLClassLoader classLoader) {
+ Thread.currentThread().setContextClassLoader(classLoader);
+ try {
+ ServiceLoader loadedDrivers = ServiceLoader.load(Driver.class);
+ Iterator driversIterator = loadedDrivers.iterator();
+ try {
+ while (driversIterator.hasNext()) {
+ driversIterator.next();
+ }
+ } catch (Throwable t) {
+ // Do nothing
+ }
+ } finally {
+ Thread.currentThread().setContextClassLoader(ClassLoader.getSystemClassLoader());
+ }
+ }
+
+ @Override
+ public void addDirectory(File directory) throws MalformedURLException, IOException {
+ if (!directory.exists()) {
+ return;
+ }
+
+ File[] jarFiles = directory.listFiles(new FileFilter() {
+ @Override
+ public boolean accept(File file) {
+ return file.isFile() && file.getName().endsWith(".jar");
+ }
+ });
+
+ for (File file : jarFiles) {
+ addUrl(file.toURI().toURL());
+ }
+ }
+
+ @Override
+ public void addUrl(URL url) throws IOException {
+ if (urls.contains(url)) {
+ return;
+ }
+
+ WorkerLogManager.getSystemLogger().info("Loading file URL: " + url);
+
+ urls.add(url);
+ }
+ private final Set urls;
+}
diff --git a/src/main/java/com/microsoft/azure/functions/worker/reflect/FactoryClassLoader.java b/src/main/java/com/microsoft/azure/functions/worker/reflect/FactoryClassLoader.java
new file mode 100644
index 00000000..dcb5c869
--- /dev/null
+++ b/src/main/java/com/microsoft/azure/functions/worker/reflect/FactoryClassLoader.java
@@ -0,0 +1,13 @@
+package com.microsoft.azure.functions.worker.reflect;
+
+import org.apache.commons.lang3.SystemUtils;
+
+public class FactoryClassLoader {
+ public ClassLoaderProvider createClassLoaderProvider(){
+ if(SystemUtils.IS_JAVA_1_8) {
+ return new DefaultClassLoaderProvider();
+ } else {
+ return new EnhancedClassLoaderProvider();
+ }
+ }
+}
diff --git a/src/test/java/com/microsoft/azure/functions/worker/broker/tests/JavaMethodExecutorTests.java b/src/test/java/com/microsoft/azure/functions/worker/broker/tests/JavaMethodExecutorTests.java
index b9750d2d..c1bad3a4 100644
--- a/src/test/java/com/microsoft/azure/functions/worker/broker/tests/JavaMethodExecutorTests.java
+++ b/src/test/java/com/microsoft/azure/functions/worker/broker/tests/JavaMethodExecutorTests.java
@@ -14,29 +14,59 @@
public class JavaMethodExecutorTests {
@Test
- public void functionMethodLoadSucceeds() throws Exception {
+ public void functionMethodLoadSucceeds_8() throws Exception {
FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTrigger","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTrigger","TestFunctionsClass.jar");
Map bindings = new HashMap<>();
bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build());
- JavaMethodExecutor executor = new JavaMethodExecutor(descriptor, bindings, new DefaultClassLoaderProvider());
+ JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
assertTrue(executor.getOverloadResolver().hasCandidates());
assertFalse(executor.getOverloadResolver().hasMultipleCandidates());
}
@Test(expected = NoSuchMethodException.class)
- public void functionMethod_doesnotexist_LoadFails() throws Exception {
+ public void functionMethod_doesnotexist_LoadFails_8() throws Exception {
FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTrigger1","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTrigger1","TestFunctionsClass.jar");
Map bindings = new HashMap<>();
bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build());
- JavaMethodExecutor executor = new JavaMethodExecutor(descriptor, bindings, new DefaultClassLoaderProvider());
+ JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
}
@Test(expected = UnsupportedOperationException.class)
- public void functionMethod_DuplicateAnnotations_LoadFails() throws Exception {
+ public void functionMethod_DuplicateAnnotations_LoadFails_8() throws Exception {
FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTriggerOverload","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTriggerOverload","TestFunctionsClass.jar");
Map bindings = new HashMap<>();
bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build());
- JavaMethodExecutor executor = new JavaMethodExecutor(descriptor, bindings, new DefaultClassLoaderProvider());
+ JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
}
+
+ @Test
+ public void functionMethodLoadSucceeds_11() throws Exception {
+ FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTrigger","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTrigger","TestFunctionsClass.jar");
+ Map bindings = new HashMap<>();
+ bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build());
+ System.setProperty("java.specification.version", "11");
+ JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
+ assertTrue(executor.getOverloadResolver().hasCandidates());
+ assertFalse(executor.getOverloadResolver().hasMultipleCandidates());
+ }
+
+ @Test(expected = NoSuchMethodException.class)
+ public void functionMethod_doesnotexist_LoadFails_11() throws Exception {
+ System.setProperty("java.specification.version", "11");
+ FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTrigger1","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTrigger1","TestFunctionsClass.jar");
+ Map bindings = new HashMap<>();
+ bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build());
+ JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
+
+ }
+
+ @Test(expected = UnsupportedOperationException.class)
+ public void functionMethod_DuplicateAnnotations_LoadFails_11() throws Exception {
+ System.setProperty("java.specification.version", "11");
+ FunctionMethodDescriptor descriptor = new FunctionMethodDescriptor("testid", "TestHttpTriggerOverload","com.microsoft.azure.functions.worker.broker.tests.TestFunctionsClass.TestHttpTriggerOverload","TestFunctionsClass.jar");
+ Map bindings = new HashMap<>();
+ bindings.put("$return", BindingInfo.newBuilder().setDirection(BindingInfo.Direction.out).build());
+ JavaMethodExecutor executor = new FactoryJavaMethodExecutor().getJavaMethodExecutor(descriptor, bindings, new FactoryClassLoader().createClassLoaderProvider());
+ }
}