Add FunctionFactoryMetadata interface for type discovery

The compiled functions implement that interface which means you can inspect
the signature of the method that created them and discover its
generic types.
This commit is contained in:
Dave Syer
2017-05-26 10:09:47 +01:00
parent 5a7c95bd97
commit 99c7b995e0
27 changed files with 551 additions and 55 deletions

View File

@@ -35,7 +35,7 @@ import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.ListableBeanFactory;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
@@ -51,6 +51,7 @@ import org.springframework.cloud.function.registry.FunctionCatalog;
import org.springframework.cloud.function.support.FluxConsumer;
import org.springframework.cloud.function.support.FluxFunction;
import org.springframework.cloud.function.support.FluxSupplier;
import org.springframework.cloud.function.support.FunctionFactoryMetadata;
import org.springframework.cloud.function.support.FunctionUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@@ -391,7 +392,7 @@ public class ContextFunctionCatalogAutoConfiguration {
private Class<?> findType(String name, AbstractBeanDefinition definition,
ParamType paramType) {
Object source = definition.getSource();
Type param;
Type param = null;
// Start by assuming output -> Function
int index = paramType.isOutput() ? 1 : 0;
if (source instanceof StandardMethodMetadata) {
@@ -422,18 +423,26 @@ public class ContextFunctionCatalogAutoConfiguration {
if (resolvable != null) {
param = resolvable.getGeneric(index).getGeneric(0).getType();
}
else {
// TODO: compiled functions (only work as String -> String)
if (paramType.isWrapper() && !Consumer.class.isAssignableFrom(definition.getBeanClass())) {
return Flux.class;
else if (registry instanceof BeanFactory) {
Object bean = ((BeanFactory) registry).getBean(name);
if (bean instanceof FunctionFactoryMetadata) {
FunctionFactoryMetadata factory = (FunctionFactoryMetadata) bean;
Type type = factory.getFactoryMethod().getGenericReturnType();
param = extractType(type, paramType, index);
}
return String.class;
}
}
if (param instanceof ParameterizedType) {
ParameterizedType concrete = (ParameterizedType) param;
param = concrete.getRawType();
}
if (param == null) {
// Last ditch attempt to guess: Flux<String>
if (paramType.isWrapper()) {
return Flux.class;
}
return String.class;
}
return (Class<?>) param;
}

View File

@@ -0,0 +1,266 @@
/*
* Copyright 2012-2015 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
*
* http://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.cloud.function.context;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import org.junit.After;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.cloud.function.compiler.CompiledFunctionFactory;
import org.springframework.cloud.function.compiler.FunctionCompiler;
import org.springframework.cloud.function.test.GenericFunction;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.core.io.FileSystemResource;
import org.springframework.util.StreamUtils;
import static org.assertj.core.api.Assertions.assertThat;
import reactor.core.publisher.Flux;
/**
* @author Dave Syer
*
*/
public class ContextFunctionCatalogAutoConfigurationTests {
private ConfigurableApplicationContext context;
private InMemoryFunctionCatalog catalog;
private FunctionInspector inspector;
@After
public void close() {
if (context != null) {
context.close();
}
}
@Test
public void simpleFunction() {
create(SimpleConfiguration.class);
assertThat(context.getBean("function")).isInstanceOf(Function.class);
assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class);
}
@Test
public void genericFunction() {
create(GenericConfiguration.class);
assertThat(context.getBean("function")).isInstanceOf(Function.class);
assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class);
assertThat(inspector.getInputType("function")).isAssignableFrom(Map.class);
assertThat(inspector.getInputWrapper("function")).isAssignableFrom(Map.class);
}
@Test
public void genericFluxFunction() {
create(GenericFluxConfiguration.class);
assertThat(context.getBean("function")).isInstanceOf(Function.class);
assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class);
assertThat(inspector.getInputType("function")).isAssignableFrom(Map.class);
assertThat(inspector.getInputWrapper("function")).isAssignableFrom(Flux.class);
}
@Test
public void externalFunction() {
create(ExternalConfiguration.class);
assertThat(context.getBean("function")).isInstanceOf(Function.class);
assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class);
assertThat(inspector.getInputType("function")).isAssignableFrom(Map.class);
assertThat(inspector.getInputWrapper("function")).isAssignableFrom(Map.class);
}
@Test
public void componentScanFunction() {
create(ComponentScanConfiguration.class);
assertThat(context.getBean("function")).isInstanceOf(Function.class);
assertThat(catalog.lookupFunction("function")).isInstanceOf(Function.class);
assertThat(inspector.getInputType("function")).isAssignableFrom(Map.class);
assertThat(inspector.getInputWrapper("function")).isAssignableFrom(Map.class);
}
@Test
public void simpleSupplier() {
create(SimpleConfiguration.class);
assertThat(context.getBean("supplier")).isInstanceOf(Supplier.class);
Supplier<Flux<String>> supplier = catalog.lookupSupplier("supplier");
assertThat(supplier.get().blockFirst()).isEqualTo("hello");
}
@Test
public void simpleConsumer() {
create(SimpleConfiguration.class);
assertThat(context.getBean("consumer")).isInstanceOf(Consumer.class);
Consumer<Flux<String>> consumer = catalog.lookupConsumer("consumer");
consumer.accept(Flux.just("foo", "bar"));
assertThat(context.getBean(SimpleConfiguration.class).list).hasSize(2);
}
@Test
public void qualifiedBean() {
create(QualifiedConfiguration.class);
assertThat(context.getBean("function")).isInstanceOf(Function.class);
assertThat(catalog.lookupFunction("function")).isNull();
assertThat(catalog.lookupFunction("other")).isInstanceOf(Function.class);
}
@Test
public void aliasBean() {
create(AliasConfiguration.class);
assertThat(context.getBean("function")).isInstanceOf(Function.class);
assertThat(catalog.lookupFunction("function")).isNotNull();
assertThat(catalog.lookupFunction("other")).isInstanceOf(Function.class);
}
@Test
public void registrationBean() {
create(RegistrationConfiguration.class);
assertThat(context.getBean("function")).isInstanceOf(Function.class);
assertThat(catalog.lookupFunction("function")).isNull();
assertThat(catalog.lookupFunction("registration")).isNull();
assertThat(catalog.lookupFunction("other")).isInstanceOf(Function.class);
}
@Test
public void compiledFunction() throws Exception {
CompiledFunctionFactory<Function<String, String>> compiled = new FunctionCompiler<String, String>(
String.class.getName()).compile("foos", "v -> v.toUpperCase()", "String",
"String");
FileSystemResource resource = new FileSystemResource("target/foos.fun");
StreamUtils.copy(compiled.getGeneratedClassBytes(), resource.getOutputStream());
create(EmptyConfiguration.class,
"spring.cloud.function.import.foos.location=file:./target/foos.fun");
assertThat(context.getBean("foos")).isInstanceOf(Function.class);
assertThat(catalog.lookupFunction("foos")).isInstanceOf(Function.class);
assertThat(inspector.getInputWrapper("foos")).isEqualTo(String.class);
}
private void create(Class<?> type, String... props) {
create(new Class<?>[] { type }, props);
}
private void create(Class<?>[] types, String... props) {
context = new SpringApplicationBuilder((Object[]) types).properties(props).run();
catalog = context.getBean(InMemoryFunctionCatalog.class);
inspector = context.getBean(FunctionInspector.class);
}
@EnableAutoConfiguration
@Configuration
protected static class EmptyConfiguration {
}
@EnableAutoConfiguration
@Configuration
protected static class SimpleConfiguration {
private List<String> list = new ArrayList<>();
@Bean
public Function<String, String> function() {
return value -> value.toUpperCase();
}
@Bean
public Supplier<String> supplier() {
return () -> "hello";
}
@Bean
public Consumer<String> consumer() {
return value -> list.add(value);
}
}
@EnableAutoConfiguration
@Configuration
protected static class GenericConfiguration {
@Bean
public Function<Map<String, String>, Map<String, String>> function() {
return m -> m.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(),
e -> e.getValue().toString().toUpperCase()));
}
}
@EnableAutoConfiguration
@Configuration
@Import(GenericFunction.class)
protected static class ExternalConfiguration {
}
@EnableAutoConfiguration
@Configuration
@ComponentScan(basePackageClasses = GenericFunction.class)
protected static class ComponentScanConfiguration {
}
@EnableAutoConfiguration
@Configuration
protected static class GenericFluxConfiguration {
@Bean
public Function<Flux<Map<String, String>>, Flux<Map<String, String>>> function() {
return flux -> flux.map(m -> m.entrySet().stream().collect(Collectors
.toMap(e -> e.getKey(), e -> e.getValue().toString().toUpperCase())));
}
}
@EnableAutoConfiguration
@Configuration
protected static class QualifiedConfiguration {
@Bean
@Qualifier("other")
public Function<String, String> function() {
return value -> value.toUpperCase();
}
}
@EnableAutoConfiguration
@Configuration
protected static class AliasConfiguration {
@Bean({ "function", "other" })
public Function<String, String> function() {
return value -> value.toUpperCase();
}
}
@EnableAutoConfiguration
@Configuration
protected static class RegistrationConfiguration {
@Bean
public FunctionRegistration<Function<String, String>> registration() {
return new FunctionRegistration<Function<String, String>>(function())
.name("other");
}
@Bean
public Function<String, String> function() {
return value -> value.toUpperCase();
}
}
}

View File

@@ -0,0 +1,38 @@
/*
* Copyright 2016-2017 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
*
* http://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.cloud.function.test;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* @author Dave Syer
*
*/
@Configuration
public class GenericFunction {
@Bean
public Function<Map<String, String>, Map<String, String>> function() {
return m -> m.entrySet().stream().collect(Collectors.toMap(e -> e.getKey(),
e -> e.getValue().toString().toUpperCase()));
}
}