diff --git a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringBootRequestHandler.java b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringBootRequestHandler.java index a6c9f8893..408e7eece 100644 --- a/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringBootRequestHandler.java +++ b/spring-cloud-function-adapters/spring-cloud-function-adapter-azure/src/main/java/org/springframework/cloud/function/adapter/azure/AzureSpringBootRequestHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2017-2019 the original author or authors. + * Copyright 2017-2021 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. @@ -28,6 +28,7 @@ import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; import org.springframework.cloud.function.context.AbstractSpringFunctionAdapterInitializer; +import org.springframework.cloud.function.context.FunctionCatalog; import org.springframework.cloud.function.context.catalog.FunctionTypeUtils; import org.springframework.cloud.function.context.catalog.SimpleFunctionRegistry.FunctionInvocationWrapper; import org.springframework.messaging.Message; @@ -45,7 +46,7 @@ public class AzureSpringBootRequestHandler extends AbstractSpringFunctionA @SuppressWarnings("rawtypes") private static AzureSpringBootRequestHandler thisInitializer; - private String functionName; + private static FunctionCatalog functionCatalog; private final static ExecutionContextDelegate EXECUTION_CTX_DELEGATE = new ExecutionContextDelegate(); @@ -82,13 +83,15 @@ public class AzureSpringBootRequestHandler extends AbstractSpringFunctionA * since Azure creates a new instance of this handler for each invocation, * see https://github.com/spring-cloud/spring-cloud-function/issues/425 */ - if (thisInitializer == null || !thisInitializer.functionName.equals(name)) { + if (thisInitializer == null /*|| !thisInitializer.functionName.equals(name)*/) { initialize(EXECUTION_CTX_DELEGATE); - this.functionName = name; + functionCatalog = this.catalog; thisInitializer = this; return (O) thisInitializer.handleRequest(input, context); } else { + this.catalog = functionCatalog; + thisInitializer.clear(name); Publisher events = input == null ? Mono.empty() : extract(convertEvent(input)); if (events instanceof Flux) { events = Flux.from(events).map(v -> this.toMessage(v, context)); @@ -109,21 +112,10 @@ public class AzureSpringBootRequestHandler extends AbstractSpringFunctionA } } - private Message toMessage(Object value, ExecutionContext context) { - if (value instanceof Message) { - return (Message) value; - } - else { - Object payload = value; - if (value instanceof HttpRequestMessage) { - payload = ((HttpRequestMessage) value).getBody(); - if (payload == null) { - payload = ((HttpRequestMessage) value).getQueryParameters(); - } - } - return MessageBuilder.withPayload(payload) - .setHeader(AbstractSpringFunctionAdapterInitializer.TARGET_EXECUTION_CTX_NAME, context).build(); - } + public void handleOutput(I input, OutputBinding binding, + ExecutionContext context) { + O result = handleRequest(input, context); + binding.setValue(result); } @Override @@ -131,12 +123,6 @@ public class AzureSpringBootRequestHandler extends AbstractSpringFunctionA return ((ExecutionContext) targetContext).getFunctionName(); } - public void handleOutput(I input, OutputBinding binding, - ExecutionContext context) { - O result = handleRequest(input, context); - binding.setValue(result); - } - protected Object convertEvent(I input) { return input; } @@ -148,7 +134,6 @@ public class AzureSpringBootRequestHandler extends AbstractSpringFunctionA return Flux.just(input); } - protected boolean isSingleInput(Function function, Object input) { if (!(input instanceof Collection)) { return true; @@ -171,6 +156,25 @@ public class AzureSpringBootRequestHandler extends AbstractSpringFunctionA return ((Collection) output).size() <= 1; } + @SuppressWarnings("rawtypes") + private Message toMessage(Object value, ExecutionContext context) { + if (value instanceof Message) { + return (Message) value; + } + else { + Object payload = value; + if (value instanceof HttpRequestMessage) { + payload = ((HttpRequestMessage) value).getBody(); + if (payload == null) { + payload = ((HttpRequestMessage) value).getQueryParameters(); + } + } + return MessageBuilder.withPayload(payload) + .setHeader(AbstractSpringFunctionAdapterInitializer.TARGET_EXECUTION_CTX_NAME, context).build(); + } + } + + private static class ExecutionContextDelegate implements ExecutionContext { ExecutionContext targetContext; diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java index f8df8d180..8aea9e37f 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/context/AbstractSpringFunctionAdapterInitializer.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2019 the original author or authors. + * Copyright 2019-2021 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. @@ -62,6 +62,7 @@ import org.springframework.util.CollectionUtils; * * @deprecated since 3.1 in favor of individual implementations of invokers */ +@SuppressWarnings("rawtypes") @Deprecated public abstract class AbstractSpringFunctionAdapterInitializer implements Closeable { @@ -74,11 +75,11 @@ public abstract class AbstractSpringFunctionAdapterInitializer implements Clo private final Class configurationClass; - private Function, Publisher> function; + private Function function; - private Consumer> consumer; + private Consumer consumer; - private Supplier> supplier; + private Supplier supplier; private FunctionRegistration functionRegistration; @@ -145,6 +146,7 @@ public abstract class AbstractSpringFunctionAdapterInitializer implements Clo return Object.class; } + @SuppressWarnings("unchecked") protected Function, Publisher> getFunction() { return function; } @@ -162,6 +164,7 @@ public abstract class AbstractSpringFunctionAdapterInitializer implements Clo return null; } + @SuppressWarnings("unchecked") protected Publisher apply(Publisher input) { if (this.function != null) { Object result = this.function.apply(input); @@ -223,6 +226,19 @@ public abstract class AbstractSpringFunctionAdapterInitializer implements Clo return CollectionUtils.isEmpty(result) ? null : value; } + protected void clear(String name) { + FunctionInvocationWrapper f = this.catalog.lookup(name); + if (f.isFunction()) { + this.function = f; + } + else if (f.isConsumer()) { + this.consumer = f; + } + else { + this.supplier = f; + } + } + private boolean isSingleInput(Function function, Object input) { if (!(input instanceof Collection)) { return true; @@ -263,7 +279,7 @@ public abstract class AbstractSpringFunctionAdapterInitializer implements Clo throw new IllegalStateException("Unknown type " + type); } - @SuppressWarnings({ "unchecked", "rawtypes" }) + @SuppressWarnings({ "unchecked" }) private T getAndInstrumentFromContext(String name) { this.functionRegistration = new FunctionRegistration(context.getBean(name), name); diff --git a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/utils/FunctionClassUtils.java b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/utils/FunctionClassUtils.java index d7ba8d1d5..09ba0725d 100644 --- a/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/utils/FunctionClassUtils.java +++ b/spring-cloud-function-context/src/main/java/org/springframework/cloud/function/utils/FunctionClassUtils.java @@ -1,5 +1,5 @@ /* - * Copyright 2019-2020 the original author or authors. + * Copyright 2019-2021 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. @@ -90,7 +90,9 @@ public final class FunctionClassUtils { } private static Class getStartClass(List list, ClassLoader classLoader) { - logger.info("Searching manifests: " + list); + if (logger.isTraceEnabled()) { + logger.trace("Searching manifests: " + list); + } for (URL url : list) { try { InputStream inputStream = null; diff --git a/spring-cloud-function-samples/function-sample-azure/README.adoc b/spring-cloud-function-samples/function-sample-azure/README.adoc index a3fa89466..c8d9d6313 100644 --- a/spring-cloud-function-samples/function-sample-azure/README.adoc +++ b/spring-cloud-function-samples/function-sample-azure/README.adoc @@ -1,4 +1,5 @@ -You can run this Azure function locally, similar to other Spring Cloud Function samples, however this time by using the Azure Maven plugin, as the Microsoft Azure functions execution context must be available. +You can run this Azure function locally, similar to other Spring Cloud Function samples, however +this time by using the Azure Maven plugin, as the Microsoft Azure functions execution context must be available. ---- # Build and package @@ -15,8 +16,9 @@ or $ mvn azure-functions:run ---- -The `uppercase` function takes `Function uppercase()` and it's input is JSON, therefore we need to -provide the appropriate content-type (in this case `application/json`). The function iterates then over each element and returns its `uppercase` mapped value. +The `uppercase` function takes `Function uppercase()` and its expected input is JSON map, therefore we need to +provide the appropriate content-type (in this case `application/json`). The function iterates then over each element +and returns its `uppercase` mapped value. Test the function using cURL or HTTPie and notice that the URL is formed by concatenating `/api/` ---- @@ -33,6 +35,8 @@ $ http POST localhost:7071/api/uppercase greeting=hello name='your name' } ---- +The same is for `echo` function, however it will take any input since all it does is just echos it back. + To run locally on top of Azure Functions, and to deploy to your live Azure environment, you will need the Azure Functions Core Tools installed along with the Azure CLI (see https://docs.microsoft.com/en-us/azure/azure-functions/functions-create-first-java-maven for more details). To deploy the function to your live Azure environment, including an automatic provisioning of an HTTPTrigger for the function: diff --git a/spring-cloud-function-samples/function-sample-azure/src/main/java/example/Config.java b/spring-cloud-function-samples/function-sample-azure/src/main/java/example/Config.java index 7f33999e7..72e77958d 100644 --- a/spring-cloud-function-samples/function-sample-azure/src/main/java/example/Config.java +++ b/spring-cloud-function-samples/function-sample-azure/src/main/java/example/Config.java @@ -1,5 +1,5 @@ /* - * Copyright 2012-2019 the original author or authors. + * Copyright 2012-2021 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. @@ -34,6 +34,11 @@ public class Config { SpringApplication.run(Config.class, args); } + @Bean + public Function, String> echo() { + return message -> message.getPayload(); + } + @Bean public Function, String> uppercase(JsonMapper mapper) { return message -> { @@ -46,7 +51,8 @@ public class Config { map.forEach((k, v) -> map.put(k, v != null ? v.toUpperCase() : null)); if(context != null) - context.getLogger().info(new StringBuilder().append("Function: ").append(context.getFunctionName()).append(" is uppercasing ").append(value.toString()).toString()); + context.getLogger().info(new StringBuilder().append("Function: ") + .append(context.getFunctionName()).append(" is uppercasing ").append(value.toString()).toString()); return mapper.toString(map); } catch (Exception e) { diff --git a/spring-cloud-function-samples/function-sample-azure/src/main/java/example/EchoHandler.java b/spring-cloud-function-samples/function-sample-azure/src/main/java/example/EchoHandler.java new file mode 100644 index 000000000..bd94f2a0c --- /dev/null +++ b/spring-cloud-function-samples/function-sample-azure/src/main/java/example/EchoHandler.java @@ -0,0 +1,42 @@ +/* + * Copyright 2012-2020 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. + */ + +package example; + +import com.microsoft.azure.functions.ExecutionContext; +import com.microsoft.azure.functions.HttpMethod; +import com.microsoft.azure.functions.HttpRequestMessage; +import com.microsoft.azure.functions.annotation.AuthorizationLevel; +import com.microsoft.azure.functions.annotation.FunctionName; +import com.microsoft.azure.functions.annotation.HttpTrigger; + +import java.util.Optional; + +import org.springframework.cloud.function.adapter.azure.AzureSpringBootRequestHandler; + +/** + * @author Soby Chacko + */ +public class EchoHandler extends AzureSpringBootRequestHandler { + + @FunctionName("echo") + public String execute(@HttpTrigger(name = "req", methods = {HttpMethod.GET, + HttpMethod.POST}, authLevel = AuthorizationLevel.ANONYMOUS) HttpRequestMessage> request, + ExecutionContext context) { + return handleRequest(request.getBody().get(), context); + } + +}