Azure / azure-functions-java-library

Contains annotations for writing Azure Functions in Java
MIT License
43 stars 43 forks source link

Support all bindings OOB #75

Closed pragnagopa closed 5 years ago

pragnagopa commented 5 years ago

From @asavaritayal on October 10, 2018 5:26

Currently, there's no way to leverage bindings such as Office Graph, SignalR, etc. without having a defined annotation in the language worker / library. We need guidance on how to bring-your-own-binding with Java.

Copied from original issue: Azure/azure-functions-java-worker#197

pragnagopa commented 5 years ago

From @stuartleeks on November 6, 2018 16:20

I have a custom binding that I've successfully tested with .NET and JavaScript functions. I tried to get it working with Java and failed.

I assumed that I could follow the inbuilt bindings and create an annotation for my binding and apply that, but it failed with Cannot locate the method signature with the given input. Looking at the Java worker code, it looks like the built-in bindings are hardcoded and nothing else is handled - is that correct?

I also made an assumption that I could use the function.json to configure the custom binding as a fallback, but that didn't seem to work either.

pragnagopa commented 5 years ago

Java worker relies on annotations to resolve inputs to method parameters https://github.com/Azure/azure-functions-java-worker/blob/80ad72fb02bfe266b9ef1de6939998fc9bc3e152/src/main/java/com/microsoft/azure/functions/worker/broker/CoreTypeResolver.java#L52

Until we add first class support for providing custom annotations, please try the following


//Sample custom output binding
@Target(ElementType.PARAMETER)
@Retention(RetentionPolicy.RUNTIME)
public @interface ElasticOutput {
    /**
     * Defines the trigger metadata name or binding name defined in function.json.
     * @return The trigger metadata name or binding name.
     */
    public BlobOutput superBlob(); //Note: This can be any type defined in azure-functions-java-library
    public String name(); // This is required
    public String index();
    public String indexType();
}

//Sample custom input binding
@Target(ElementType.PARAMETER)
    @Retention(RetentionPolicy.RUNTIME)
    @interface CustomInputBinding {
        /**
         * Defines the trigger metadata name or binding name defined in function.json.
         * @return The trigger metadata name or binding name.
         */
        BlobInput superBlob();
        String name();
    }

 @FunctionName("HttpTrigger-Java")
    public HttpResponseMessage run(
            @HttpTrigger(name = "req", methods = {HttpMethod.GET, HttpMethod.POST}, authLevel = AuthorizationLevel.FUNCTION) HttpRequestMessage<Optional<String>> request,
            @ElasticOutput(name = "message", index="people", indexType="wibble", superBlob = @BlobOutput(name = "customInput", path = "")) OutputBinding<String> customElastic,
            final ExecutionContext context)

You still have to manually update the generated function.json.

pragnagopa commented 5 years ago

From @stuartleeks on November 16, 2018 14:8

Thanks @pragnagopa

What is the superBlob property in the ElasticOutput type for?

I've pushed the WIP Java integration of my sample code in case that helps. The Java function folder is here. I tried to create an annotation without the superBlob property and also updated the function.json, but I still get the error:

[16/11/2018 14:02:43] Cannot locate the method signature with the given input
[16/11/2018 14:02:43] Result: Cannot locate the method signature with the given input
Exception: Cannot locate the method signature with the given input
Stack: java.lang.NoSuchMethodException: Cannot locate the method signature with the given input
[16/11/2018 14:02:43]   at com.microsoft.azure.functions.worker.broker.JavaMethodExecutor.lambda$execute$0(JavaMethodExecutor.java:49)
[16/11/2018 14:02:43]   at java.util.Optional.orElseThrow(Optional.java:290)
[16/11/2018 14:02:43]   at com.microsoft.azure.functions.worker.broker.JavaMethodExecutor.execute(JavaMethodExecutor.java:49)
[16/11/2018 14:02:43]   at com.microsoft.azure.functions.worker.broker.JavaFunctionBroker.invokeMethod(JavaFunctionBroker.java:47)
[16/11/2018 14:02:43]   at com.microsoft.azure.functions.worker.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:33)
[16/11/2018 14:02:43]   at com.microsoft.azure.functions.worker.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:10)
[16/11/2018 14:02:43]   at com.microsoft.azure.functions.worker.handler.MessageHandler.handle(MessageHandler.java:45)
[16/11/2018 14:02:43]   at com.microsoft.azure.functions.worker.JavaWorkerClient$StreamingMessagePeer.lambda$onNext$0(JavaWorkerClient.java:91)
[16/11/2018 14:02:43]   at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1386)
[16/11/2018 14:02:43]   at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
[16/11/2018 14:02:43]   at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
[16/11/2018 14:02:43]   at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
[16/11/2018 14:02:43]   at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
[16/11/2018 14:02:43] .

For comparison, there are JavaScript and C# equivalents in the sample.

pragnagopa commented 5 years ago

You do need a type that is defined in azure-functions-java-library because java worker relies on the annotation defined in this library to figure out how to pass inputs to a java method.

Annotations do not support inheritance. Defining a superBlob of type BlobOutput is purely for the worker to parse the name of the input/output binding. Eventually, we plan to introduce a generic binding that can be used for defining custom bindings.

pragnagopa commented 5 years ago

From @stuartleeks on November 16, 2018 14:55

Still no luck - I'm thinking I must have missed something.

I've created the annotations as per the code you shared: https://github.com/stuartleeks/azure-functions-custom-integration-elastic/blob/java/ElasticFunctionDemoJava/src/main/java/ElasticFunctionDemoJava/ElasticOutput.java

Applied the annotation here: https://github.com/stuartleeks/azure-functions-custom-integration-elastic/blob/java/ElasticFunctionDemoJava/src/main/java/ElasticFunctionDemoJava/Function.java#L22

And manually updated the function.json: https://github.com/stuartleeks/azure-functions-custom-integration-elastic/blob/java/ElasticFunctionDemoJava/src/main/java/ElasticFunctionDemoJava/function.json#L18-L22

The error is:

[16/11/2018 14:57:30] Executing 'Functions.HttpTrigger-Java' (Reason='This function was programmatically called via the host APIs.', Id=484a5346-05f9-478b-987b-b07285a02beb)
[16/11/2018 14:57:30] Cannot locate the method signature with the given input
[16/11/2018 14:57:30] Result: Cannot locate the method signature with the given input
Exception: Cannot locate the method signature with the given input
Stack: java.lang.NoSuchMethodException: Cannot locate the method signature with the given input
[16/11/2018 14:57:30]   at com.microsoft.azure.functions.worker.broker.JavaMethodExecutor.lambda$execute$0(JavaMethodExecutor.java:49)
[16/11/2018 14:57:30]   at java.util.Optional.orElseThrow(Optional.java:290)
[16/11/2018 14:57:30]   at com.microsoft.azure.functions.worker.broker.JavaMethodExecutor.execute(JavaMethodExecutor.java:49)
[16/11/2018 14:57:30]   at com.microsoft.azure.functions.worker.broker.JavaFunctionBroker.invokeMethod(JavaFunctionBroker.java:47)
[16/11/2018 14:57:30]   at com.microsoft.azure.functions.worker.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:33)
[16/11/2018 14:57:30]   at com.microsoft.azure.functions.worker.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:10)
[16/11/2018 14:57:30]   at com.microsoft.azure.functions.worker.handler.MessageHandler.handle(MessageHandler.java:45)
[16/11/2018 14:57:30]   at com.microsoft.azure.functions.worker.JavaWorkerClient$StreamingMessagePeer.lambda$onNext$0(JavaWorkerClient.java:91)
[16/11/2018 14:57:30]   at java.util.concurrent.ForkJoinTask$AdaptedRunnableAction.exec(ForkJoinTask.java:1386)
[16/11/2018 14:57:30]   at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
[16/11/2018 14:57:30]   at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
[16/11/2018 14:57:30]   at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
[16/11/2018 14:57:30]   at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
pragnagopa commented 5 years ago

From @stuartleeks on November 16, 2018 15:18

UPDATE: I've updated to a newer func CLI (version 2.2.70) and now get this error:

[16/11/2018 15:17:39] Executed 'Functions.HttpTrigger-Java' (Failed, Id=84013263-2304-4620-b5b1-28d86453249b)
[16/11/2018 15:17:39] System.Private.CoreLib: Exception while executing function: Functions.HttpTrigger-Java. System.Private.CoreLib: Result: Failure
Exception: WrongMethodTypeException:
Stack: java.lang.invoke.WrongMethodTypeException
[16/11/2018 15:17:39]   at java.util.Optional.orElseThrow(Optional.java:290)
[16/11/2018 15:17:39]   at com.microsoft.azure.functions.worker.broker.ParameterResolver.resolve(ParameterResolver.java:67)
[16/11/2018 15:17:39]   at com.microsoft.azure.functions.worker.broker.ParameterResolver.resolve(ParameterResolver.java:42)
[16/11/2018 15:17:39]   at com.microsoft.azure.functions.worker.broker.JavaMethodExecutor.execute(JavaMethodExecutor.java:52)
[16/11/2018 15:17:39]   at com.microsoft.azure.functions.worker.broker.JavaFunctionBroker.invokeMethod(JavaFunctionBroker.java:51)
[16/11/2018 15:17:39]   at com.microsoft.azure.functions.worker.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:33)
[16/11/2018 15:17:39]   at com.microsoft.azure.functions.worker.handler.InvocationRequestHandler.execute(InvocationRequestHandler.java:10)
[16/11/2018 15:17:39]   at com.microsoft.azure.functions.worker.handler.MessageHandler.handle(MessageHandler.java:45)
[16/11/2018 15:17:39]   at com.microsoft.azure.functions.worker.JavaWorkerClient$StreamingMessagePeer.lambda$onNext$0(JavaWorkerClient.java:91)
[16/11/2018 15:17:39]   at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511)
[16/11/2018 15:17:39]   at java.util.concurrent.FutureTask.run(FutureTask.java:266)
[16/11/2018 15:17:39]   at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
[16/11/2018 15:17:39]   at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
[16/11/2018 15:17:39]   at java.lang.Thread.run(Thread.java:748)
[16/11/2018 15:17:39] .
pragnagopa commented 5 years ago

Thanks for the sample code and repro. Will look into it.

pragnagopa commented 5 years ago

From @stuartleeks on November 16, 2018 20:48

After going through the steps with @pragnagopa I learned that the function.json in the source tree is irrelevant as it is auto-generated on build in the target folder. Manually amending that after building works as a work-around :-)

pragnagopa commented 5 years ago

@jeffhollan / @asavaritayal Should we add Generic annotations customInput and customOutput ?

pragnagopa commented 5 years ago

From @jeffhollan on November 19, 2018 20:50

Yes I think that would be good. And customTrigger

pragnagopa commented 5 years ago

@jeffhollan @asavaritayal - Instead of separate annotations for Input, Output and Trigger, added a single CustomBinding annotation. Users have to define required fields: direction, name and type.