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()); + } }