diff --git a/spring-data-geode/src/main/java/org/springframework/data/gemfire/function/execution/GemfireFunctionProxyFactoryBean.java b/spring-data-geode/src/main/java/org/springframework/data/gemfire/function/execution/GemfireFunctionProxyFactoryBean.java index 483fc990..3b359c69 100644 --- a/spring-data-geode/src/main/java/org/springframework/data/gemfire/function/execution/GemfireFunctionProxyFactoryBean.java +++ b/spring-data-geode/src/main/java/org/springframework/data/gemfire/function/execution/GemfireFunctionProxyFactoryBean.java @@ -1,9 +1,5 @@ /* -<<<<<<< Updated upstream * Copyright 2002-2020 the original author or authors. -======= - * Copyright 2002-2020 the original author or authors. ->>>>>>> Stashed changes * * 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 @@ -17,6 +13,7 @@ package org.springframework.data.gemfire.function.execution; import java.lang.reflect.Method; +import java.util.stream.StreamSupport; import org.aopalliance.intercept.MethodInterceptor; import org.aopalliance.intercept.MethodInvocation; @@ -35,6 +32,7 @@ import org.springframework.util.ClassUtils; * * @author David Turanski * @author John Blum + * @author Patrick Johnson * @see java.lang.reflect.Method * @see org.aopalliance.intercept.MethodInterceptor * @see org.springframework.beans.factory.BeanClassLoaderAware @@ -93,7 +91,16 @@ public class GemfireFunctionProxyFactoryBean implements BeanClassLoaderAware, Fa logger.debug("Invoking method {}", invocation.getMethod().getName()); } - return invokeFunction(invocation.getMethod(), invocation.getArguments()); + Object result = invokeFunction(invocation.getMethod(), invocation.getArguments()); + + if(!invocation.getMethod().getReturnType().isAssignableFrom(result.getClass()) && result instanceof Iterable) { + Iterable iter = (Iterable)result; + if(StreamSupport.stream(iter.spliterator(), false).count() == 1) { + result = iter.iterator().next(); + } + } + + return result; } protected Object invokeFunction(Method method, Object[] args) { diff --git a/spring-data-geode/src/test/java/org/springframework/data/gemfire/function/result/FunctionResultTypeIntegrationTest.java b/spring-data-geode/src/test/java/org/springframework/data/gemfire/function/result/FunctionResultTypeIntegrationTest.java new file mode 100644 index 00000000..c612abde --- /dev/null +++ b/spring-data-geode/src/test/java/org/springframework/data/gemfire/function/result/FunctionResultTypeIntegrationTest.java @@ -0,0 +1,164 @@ +/* + * Copyright 2002-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 org.springframework.data.gemfire.function.result; + +import org.apache.geode.cache.DataPolicy; +import org.apache.geode.cache.GemFireCache; +import org.apache.geode.cache.client.ClientRegionShortcut; +import org.junit.AfterClass; +import org.junit.BeforeClass; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.AnnotationConfigApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.data.gemfire.ReplicatedRegionFactoryBean; +import org.springframework.data.gemfire.client.ClientRegionFactoryBean; +import org.springframework.data.gemfire.config.annotation.CacheServerApplication; +import org.springframework.data.gemfire.config.annotation.ClientCacheApplication; +import org.springframework.data.gemfire.function.annotation.FunctionId; +import org.springframework.data.gemfire.function.annotation.GemfireFunction; +import org.springframework.data.gemfire.function.annotation.OnRegion; +import org.springframework.data.gemfire.function.config.EnableGemfireFunctionExecutions; +import org.springframework.data.gemfire.function.config.EnableGemfireFunctions; +import org.springframework.data.gemfire.process.ProcessWrapper; +import org.springframework.data.gemfire.test.support.ClientServerIntegrationTestsSupport; +import org.springframework.data.gemfire.transaction.config.EnableGemfireCacheTransactions; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringRunner; + +import java.io.IOException; +import java.math.BigDecimal; +import java.util.Collections; +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * @author Patrick Johnson + */ +@RunWith(SpringRunner.class) +@ContextConfiguration(classes = FunctionResultTypeIntegrationTest.FunctionInvocationClientApplicationConfig.class) +public class FunctionResultTypeIntegrationTest extends ClientServerIntegrationTestsSupport { + + private static ProcessWrapper server; + + @Autowired + private FunctionExecutions functionExecutions; + + @BeforeClass + public static void startServer() throws IOException { + + final String GEMFIRE_LOCALHOST_PORT = "localhost[%d]"; + + int availablePort = findAvailablePort(); + + System.setProperty(GEMFIRE_CACHE_SERVER_PORT_PROPERTY, String.valueOf(availablePort)); + System.setProperty(GEMFIRE_POOL_SERVERS_PROPERTY, String.format(GEMFIRE_LOCALHOST_PORT, availablePort)); + + server = run(FunctionServerApplicationConfig.class, + String.format("-D%s=%d", GEMFIRE_CACHE_SERVER_PORT_PROPERTY, availablePort)); + + waitForServerToStart(DEFAULT_HOSTNAME, availablePort); + } + + @AfterClass + public static void stopServer() { + server.stop(); + server = null; + } + + @Test + public void singleResultFunctionsExecuteCorrectly() { + BigDecimal num = functionExecutions.returnFive(); + assertThat(num.doubleValue()).isEqualTo(5); + } + + @Test + public void listResultFunctionsExecuteCorrectly() { + List list = functionExecutions.returnList(); + assertThat(list.size()).isEqualTo(1); + } + + @Test + public void primitiveResultFunctionsExecuteCorrectly() { + int num = functionExecutions.returnPrimitive(); + assertThat(num).isEqualTo(7); + } + + @EnableGemfireFunctionExecutions(basePackageClasses = FunctionExecutions.class) + @ClientCacheApplication(name = "SingleResultFunctionExecutionTest") + @EnableGemfireCacheTransactions + public static class FunctionInvocationClientApplicationConfig { + + @Bean("Numbers") + protected ClientRegionFactoryBean configureProxyClientNumberRegion(GemFireCache gemFireCache) { + ClientRegionFactoryBean clientRegionFactoryBean = new ClientRegionFactoryBean<>(); + clientRegionFactoryBean.setCache(gemFireCache); + clientRegionFactoryBean.setName("Numbers"); + clientRegionFactoryBean.setShortcut(ClientRegionShortcut.PROXY); + return clientRegionFactoryBean; + } + } + + @EnableGemfireFunctions + @CacheServerApplication(name = "SingleResultFunctionExecutionTestServer") + public static class FunctionServerApplicationConfig { + + public static void main(String[] args) { + + AnnotationConfigApplicationContext applicationContext = + new AnnotationConfigApplicationContext(FunctionServerApplicationConfig.class); + + applicationContext.registerShutdownHook(); + } + + @Bean + ReplicatedRegionFactoryBean createNumberRegion(GemFireCache gemfireCache) { + ReplicatedRegionFactoryBean replicatedRegionFactoryBean = new ReplicatedRegionFactoryBean(); + replicatedRegionFactoryBean.setCache(gemfireCache); + replicatedRegionFactoryBean.setRegionName("Numbers"); + replicatedRegionFactoryBean.setDataPolicy(DataPolicy.REPLICATE); + return replicatedRegionFactoryBean; + } + + @GemfireFunction(id = "returnSingleObject", hasResult = true) + public BigDecimal returnSingleObject() { + return new BigDecimal(5); + } + + @GemfireFunction(id = "returnList", hasResult = true) + public List returnList() { + return Collections.singletonList(new BigDecimal(10)); + } + + @GemfireFunction(id = "returnPrimitive", hasResult = true) + public int returnPrimitive() { + return 7; + } + } +} + +@OnRegion(region = "Numbers") +interface FunctionExecutions { + + @FunctionId("returnSingleObject") + BigDecimal returnFive(); + + @FunctionId("returnList") + List returnList(); + + @FunctionId("returnPrimitive") + int returnPrimitive(); +}