Azure/azure-functions-java-library

Support all bindings OOB

pragnagopa opened this issue · 11 comments

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

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.

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.

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.

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.

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)

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] .

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

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 :-)

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

From @jeffhollan on November 19, 2018 20:50

Yes I think that would be good. And customTrigger

@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.