Add support for explicit FunctionRegistration

A bean of type FunctionRegistration registers the function with
user-specified name and other properties, rather than relying on the
bean name.

Alternatively, function catalog keys can be specified as a
@Qualifier, which will be used instead of the bean name if
no registration is found.
This commit is contained in:
Dave Syer
2017-03-31 14:15:04 +01:00
committed by markfisher
parent 82e19894b2
commit d67159729d
6 changed files with 427 additions and 38 deletions

View File

@@ -19,9 +19,12 @@ package org.springframework.cloud.function.context;
import java.lang.reflect.Field;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
@@ -32,6 +35,7 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.config.BeanDefinition;
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -48,6 +52,7 @@ import org.springframework.cloud.function.support.FunctionUtils;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.ResolvableType;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.type.StandardMethodMetadata;
import org.springframework.stereotype.Component;
import org.springframework.util.ClassUtils;
@@ -73,12 +78,14 @@ public class ContextFunctionCatalogAutoConfiguration {
@Autowired(required = false)
private Map<String, Consumer<?>> consumers = Collections.emptyMap();
@Autowired(required = false)
private Map<String, FunctionRegistration<?>> registrations = Collections.emptyMap();
@Bean
public FunctionCatalog functionCatalog(ContextFunctionPostProcessor processor,
ObjectMapper mapper) {
return new InMemoryFunctionCatalog(processor.wrapSuppliers(mapper, suppliers),
processor.wrapFunctions(mapper, functions),
processor.wrapConsumers(mapper, consumers));
return new InMemoryFunctionCatalog(
processor.merge(registrations, consumers, suppliers, functions, mapper));
}
@Component
@@ -96,17 +103,101 @@ public class ContextFunctionCatalogAutoConfiguration {
this.registry = registry;
}
public Map<String, Supplier<?>> wrapSuppliers(ObjectMapper mapper,
Map<String, Supplier<?>> suppliers) {
Map<String, Supplier<?>> result = new HashMap<>();
for (String key : suppliers.keySet()) {
Supplier<?> target = target(suppliers.get(key), mapper, key);
result.put(key, target);
for (String name : registry.getAliases(key)) {
result.put(name, target);
public Set<FunctionRegistration<?>> merge(
Map<String, FunctionRegistration<?>> initial,
Map<String, Consumer<?>> consumers, Map<String, Supplier<?>> suppliers,
Map<String, Function<?, ?>> functions, ObjectMapper mapper) {
Set<FunctionRegistration<?>> registrations = new HashSet<>();
Map<Object, String> targets = new HashMap<>();
// Replace the initial registrations with new ones that have the right names
for (String key : initial.keySet()) {
FunctionRegistration<?> registration = initial.get(key);
if (registration.getNames().isEmpty()) {
registration.names(getAliases(key));
}
registrations.add(registration);
targets.put(registration.getTarget(), key);
}
// Add consumers that were not already registered
for (String key : consumers.keySet()) {
if (!targets.containsKey(consumers.get(key))) {
FunctionRegistration<Object> target = new FunctionRegistration<Object>()
.target(consumers.get(key)).names(getAliases(key));
targets.put(target.getTarget(), key);
registrations.add(target);
}
}
return result;
// Add suppliers that were not already registered
for (String key : suppliers.keySet()) {
if (!targets.containsKey(suppliers.get(key))) {
FunctionRegistration<Object> target = new FunctionRegistration<Object>()
.target(suppliers.get(key)).names(getAliases(key));
targets.put(target.getTarget(), key);
registrations.add(target);
}
}
// Add functions that were not already registered
for (String key : functions.keySet()) {
if (!targets.containsKey(functions.get(key))) {
FunctionRegistration<Object> target = new FunctionRegistration<Object>()
.target(functions.get(key)).names(getAliases(key));
targets.put(target.getTarget(), key);
registrations.add(target);
}
}
// Wrap the functions so they handle reactive inputs and outputs
for (FunctionRegistration<?> registration : registrations) {
@SuppressWarnings("unchecked")
FunctionRegistration<Object> target = (FunctionRegistration<Object>) registration;
String key = targets.get(target.getTarget());
wrap(target, mapper, key);
}
return registrations;
}
private Collection<String> getAliases(String key) {
Collection<String> names = new LinkedHashSet<>();
String value = getQualifier(key);
if (value.equals(key)) {
names.add(key);
names.addAll(Arrays.asList(registry.getAliases(key)));
}
else {
names.add(value);
}
return names;
}
private void wrap(FunctionRegistration<Object> registration,
ObjectMapper mapper, String key) {
Object target = registration.getTarget();
if (target instanceof Supplier) {
registration.target(target((Supplier<?>) target, mapper, key));
}
else if (target instanceof Consumer) {
registration.target(target((Consumer<?>) target, mapper, key));
}
else if (target instanceof Function) {
registration.target(target((Function<?, ?>) target, mapper, key));
}
}
private String getQualifier(String key) {
if (!registry.containsBeanDefinition(key)) {
return key;
}
String value = key;
BeanDefinition beanDefinition = registry.getBeanDefinition(key);
Object source = beanDefinition.getSource();
if (source instanceof StandardMethodMetadata) {
StandardMethodMetadata metadata = (StandardMethodMetadata) source;
Qualifier qualifier = AnnotatedElementUtils.findMergedAnnotation(
metadata.getIntrospectedMethod(), Qualifier.class);
if (qualifier != null && qualifier.value().length() > 0) {
return qualifier.value();
}
}
return value;
}
private Supplier<?> target(Supplier<?> target, ObjectMapper mapper, String key) {
@@ -125,19 +216,6 @@ public class ContextFunctionCatalogAutoConfiguration {
}
}
public Map<String, Function<?, ?>> wrapFunctions(ObjectMapper mapper,
Map<String, Function<?, ?>> functions) {
Map<String, Function<?, ?>> result = new HashMap<>();
for (String key : functions.keySet()) {
Function<?, ?> target = target(functions.get(key), mapper, key);
result.put(key, target);
for (String name : registry.getAliases(key)) {
result.put(name, target);
}
}
return result;
}
private Function<?, ?> target(Function<?, ?> target, ObjectMapper mapper,
String key) {
if (this.functions.contains(key)) {
@@ -155,19 +233,6 @@ public class ContextFunctionCatalogAutoConfiguration {
}
}
public Map<String, Consumer<?>> wrapConsumers(ObjectMapper mapper,
Map<String, Consumer<?>> consumers) {
Map<String, Consumer<?>> result = new HashMap<>();
for (String key : consumers.keySet()) {
Consumer<?> target = target(consumers.get(key), mapper, key);
result.put(key, target);
for (String name : registry.getAliases(key)) {
result.put(name, target);
}
}
return result;
}
private Consumer<?> target(Consumer<?> target, ObjectMapper mapper, String key) {
if (this.consumers.contains(key)) {
@SuppressWarnings("unchecked")

View File

@@ -0,0 +1,45 @@
/*
* Copyright 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.context;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.cloud.function.registry.FunctionCatalog;
import org.springframework.core.annotation.AliasFor;
/**
* A {@link Qualifier} annotation that specifies how to locate a function in the
* {@link FunctionCatalog} (instead of using the bean name).
*
* @author Dave Syer
*/
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
ElementType.ANNOTATION_TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
@Documented
@Qualifier
public @interface FunctionEntry {
@AliasFor(annotation=Qualifier.class)
String value() default "";
}

View File

@@ -0,0 +1,86 @@
/*
* 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.Arrays;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.Map;
import java.util.Set;
/**
* @author Dave Syer
*
*/
public class FunctionRegistration<T> {
private T target;
private Set<String> names = new LinkedHashSet<>();
private Map<String, String> properties = new LinkedHashMap<>();
public FunctionRegistration() {
}
public FunctionRegistration(T target) {
this.target = target;
}
public Map<String, String> getProperties() {
return properties;
}
public Set<String> getNames() {
return names;
}
public void setNames(Set<String> names) {
this.names = names;
}
public T getTarget() {
return target;
}
public FunctionRegistration<T> properties(Map<String, String> properties) {
this.properties.putAll(properties);
return this;
}
public FunctionRegistration<T> target(T target) {
this.target = target;
return this;
}
public FunctionRegistration<T> name(String name) {
this.names.add(name);
return this;
}
public FunctionRegistration<T> names(Collection<String> names) {
this.names.addAll(names);
return this;
}
public FunctionRegistration<T> names(String... names) {
this.names.addAll(Arrays.asList(names));
return this;
}
}

View File

@@ -16,7 +16,9 @@
package org.springframework.cloud.function.context;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
@@ -43,6 +45,29 @@ public class InMemoryFunctionCatalog implements FunctionCatalog {
this.consumers = consumers;
}
public InMemoryFunctionCatalog(Set<FunctionRegistration<?>> registrations) {
this.suppliers = new HashMap<>();
this.functions = new HashMap<>();
this.consumers = new HashMap<>();
for (FunctionRegistration<?> registration : registrations) {
if (registration.getTarget() instanceof Consumer) {
for (String name : registration.getNames()) {
consumers.put(name, (Consumer<?>) registration.getTarget());
}
}
if (registration.getTarget() instanceof Supplier) {
for (String name : registration.getNames()) {
suppliers.put(name, (Supplier<?>) registration.getTarget());
}
}
if (registration.getTarget() instanceof Function) {
for (String name : registration.getNames()) {
functions.put(name, (Function<?, ?>) registration.getTarget());
}
}
}
}
@Override
@SuppressWarnings("unchecked")
public <T> Supplier<T> lookupSupplier(String name) {