GH-77 added initial Kotlin support

Added mixed java/kotlin POM configuration

added tests, javadocs

Resolves #77
Resolves #204
This commit is contained in:
Oleg Zhurakousky
2018-09-10 10:03:05 +02:00
parent b28e22d255
commit 87c67427df
8 changed files with 862 additions and 532 deletions

View File

@@ -51,7 +51,7 @@ public class FunctionRegistration<T> {
private FunctionType type;
/**
* @deprecated as of v1.0.0 in favor of {@link #FunctionRegistration(Object, String, String...)}
* @deprecated as of v1.0.0 in favor of {@link #FunctionRegistration(Object, String...)}
*/
@Deprecated
public FunctionRegistration(T target) {
@@ -64,7 +64,6 @@ public class FunctionRegistration<T> {
* Creates instance of FunctionRegistration.
*
* @param target instance of {@link Supplier}, {@link Function} or {@link Consumer}
* @param name initial name for this registration.
* @param names additional set of names for this registration. Additional names
* can be provided {@link #name(String)} or {@link #names(String...)} operations.
*/

View File

@@ -98,592 +98,463 @@ import org.springframework.util.StringUtils;
@ConditionalOnMissingBean(FunctionCatalog.class)
public class ContextFunctionCatalogAutoConfiguration {
static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-json-mapper";
static final String PREFERRED_MAPPER_PROPERTY = "spring.http.converters.preferred-json-mapper";
@Autowired(required = false)
private Map<String, Supplier<?>> suppliers = Collections.emptyMap();
@Autowired(required = false)
private Map<String, Supplier<?>> suppliers = Collections.emptyMap();
@Autowired(required = false)
private Map<String, Function<?, ?>> functions = Collections.emptyMap();
@Autowired(required = false)
private Map<String, Function<?, ?>> functions = Collections.emptyMap();
@Autowired(required = false)
private Map<String, Consumer<?>> consumers = Collections.emptyMap();
@Autowired(required = false)
private Map<String, Consumer<?>> consumers = Collections.emptyMap();
@Autowired(required = false)
private Map<String, FunctionRegistration<?>> registrations = Collections.emptyMap();
@Autowired(required = false)
private Map<String, FunctionRegistration<?>> registrations = Collections.emptyMap();
@Bean
public FunctionRegistry functionCatalog(ContextFunctionRegistry processor) {
processor.merge(registrations, consumers, suppliers, functions);
return new BeanFactoryFunctionCatalog(processor);
}
@Bean
public FunctionRegistry functionCatalog(ContextFunctionRegistry processor) {
processor.merge(registrations, consumers, suppliers, functions);
return new BeanFactoryFunctionCatalog(processor);
}
@Bean
public FunctionInspector functionInspector(ContextFunctionRegistry processor) {
return new BeanFactoryFunctionInspector(processor);
}
@Bean
public FunctionInspector functionInspector(ContextFunctionRegistry processor) {
return new BeanFactoryFunctionInspector(processor);
}
protected static class BeanFactoryFunctionCatalog implements FunctionRegistry {
protected static class BeanFactoryFunctionCatalog implements FunctionRegistry {
private final ContextFunctionRegistry processor;
private final ContextFunctionRegistry processor;
@Override
public <T> void register(FunctionRegistration<T> registration) {
Assert.notEmpty(registration.getNames(), "'registration' must contain at least one name before it is registered in catalog.");
processor.register(registration);
}
@Override
public <T> void register(FunctionRegistration<T> registration) {
Assert.notEmpty(registration.getNames(), "'registration' must contain at least one name before it is registered in catalog.");
processor.register(registration);
}
@Override
@SuppressWarnings("unchecked")
public <T> T lookup(Class<T> type, String name) {
T function = null;
if (type == null) {
function = (T) processor.lookupFunction(name);
if (function == null) {
function = (T) processor.lookupConsumer(name);
}
if (function == null) {
function = (T) processor.lookupSupplier(name);
}
}
else if (Supplier.class.isAssignableFrom(type)) {
function = (T) processor.lookupSupplier(name);
}
else if (Consumer.class.isAssignableFrom(type)) {
function = (T) processor.lookupConsumer(name);
}
else if (Function.class.isAssignableFrom(type)) {
function = (T) processor.lookupFunction(name);
}
return function;
}
@Override
@SuppressWarnings("unchecked")
public <T> T lookup(Class<T> type, String name) {
T function = null;
if (type == null) {
function = (T) processor.lookupFunction(name);
if (function == null) {
function = (T) processor.lookupConsumer(name);
}
if (function == null) {
function = (T) processor.lookupSupplier(name);
}
} else if (Supplier.class.isAssignableFrom(type)) {
function = (T) processor.lookupSupplier(name);
} else if (Consumer.class.isAssignableFrom(type)) {
function = (T) processor.lookupConsumer(name);
} else if (Function.class.isAssignableFrom(type)) {
function = (T) processor.lookupFunction(name);
}
return function;
}
@Override
public Set<String> getNames(Class<?> type) {
if (Supplier.class.isAssignableFrom(type)) {
return this.processor.getSuppliers();
}
if (Consumer.class.isAssignableFrom(type)) {
return this.processor.getConsumers();
}
if (Function.class.isAssignableFrom(type)) {
return this.processor.getFunctions();
}
return Collections.emptySet();
}
@Override
public Set<String> getNames(Class<?> type) {
if (Supplier.class.isAssignableFrom(type)) {
return this.processor.getSuppliers();
}
if (Consumer.class.isAssignableFrom(type)) {
return this.processor.getConsumers();
}
if (Function.class.isAssignableFrom(type)) {
return this.processor.getFunctions();
}
return Collections.emptySet();
}
public BeanFactoryFunctionCatalog(ContextFunctionRegistry processor) {
this.processor = processor;
}
public BeanFactoryFunctionCatalog(ContextFunctionRegistry processor) {
this.processor = processor;
}
}
}
protected class BeanFactoryFunctionInspector implements FunctionInspector {
protected class BeanFactoryFunctionInspector implements FunctionInspector {
private ContextFunctionRegistry processor;
private ContextFunctionRegistry processor;
public BeanFactoryFunctionInspector(ContextFunctionRegistry processor) {
this.processor = processor;
}
public BeanFactoryFunctionInspector(ContextFunctionRegistry processor) {
this.processor = processor;
}
@Override
public FunctionRegistration<?> getRegistration(Object function) {
FunctionRegistration<?> registration = processor.getRegistration(function);
return registration;
}
@Override
public FunctionRegistration<?> getRegistration(Object function) {
FunctionRegistration<?> registration = processor.getRegistration(function);
return registration;
}
}
}
@Configuration
@ConditionalOnClass(Gson.class)
@ConditionalOnBean(Gson.class)
@Conditional(PreferGsonOrMissingJacksonCondition.class)
protected static class GsonConfiguration {
@Bean
public GsonMapper jsonMapper(Gson gson) {
return new GsonMapper(gson);
}
}
@Configuration
@ConditionalOnClass(Gson.class)
@ConditionalOnBean(Gson.class)
@Conditional(PreferGsonOrMissingJacksonCondition.class)
protected static class GsonConfiguration {
@Bean
public GsonMapper jsonMapper(Gson gson) {
return new GsonMapper(gson);
}
}
@Configuration
@ConditionalOnClass(ObjectMapper.class)
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(name = ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jackson", matchIfMissing = true)
protected static class JacksonConfiguration {
@Bean
public JacksonMapper jsonMapper(ObjectMapper mapper) {
return new JacksonMapper(mapper);
}
}
@Configuration
@ConditionalOnClass(ObjectMapper.class)
@ConditionalOnBean(ObjectMapper.class)
@ConditionalOnProperty(name = ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "jackson", matchIfMissing = true)
protected static class JacksonConfiguration {
@Bean
public JacksonMapper jsonMapper(ObjectMapper mapper) {
return new JacksonMapper(mapper);
}
}
@Component
protected static class ContextFunctionRegistry {
@Component
protected static class ContextFunctionRegistry {
private Map<String, Object> suppliers = new ConcurrentHashMap<>();
private Map<String, Object> suppliers = new ConcurrentHashMap<>();
private Map<String, Object> functions = new ConcurrentHashMap<>();
private Map<String, Object> functions = new ConcurrentHashMap<>();
private Map<String, Object> consumers = new ConcurrentHashMap<>();
private Map<String, Object> consumers = new ConcurrentHashMap<>();
@Autowired(required = false)
private ApplicationEventPublisher publisher;
@Autowired(required = false)
private ApplicationEventPublisher publisher;
@Autowired
private ConfigurableListableBeanFactory registry;
@Autowired
private ConfigurableListableBeanFactory registry;
private Map<Object, String> names = new ConcurrentHashMap<>();
private Map<Object, String> names = new ConcurrentHashMap<>();
private Map<String, FunctionType> types = new ConcurrentHashMap<>();
private Map<String, FunctionType> types = new ConcurrentHashMap<>();
public Set<String> getSuppliers() {
return this.suppliers.keySet();
}
public Set<String> getSuppliers() {
return this.suppliers.keySet();
}
public FunctionRegistration<?> getRegistration(Object function) {
if (function == null || !names.containsKey(function)) {
return null;
}
return new FunctionRegistration<>(function, names.get(function))
.type(findType(function).getType());
}
public FunctionRegistration<?> getRegistration(Object function) {
if (function == null || !names.containsKey(function)) {
return null;
}
return new FunctionRegistration<>(function, names.get(function))
.type(findType(function).getType());
}
public Set<String> getConsumers() {
return this.consumers.keySet();
}
public Set<String> getConsumers() {
return this.consumers.keySet();
}
public Set<String> getFunctions() {
return this.functions.keySet();
}
public Set<String> getFunctions() {
return this.functions.keySet();
}
public Supplier<?> lookupSupplier(String name) {
Object composed = compose(name, this.suppliers, false);
if (composed instanceof Supplier) {
return (Supplier<?>) composed;
}
return null;
}
public Supplier<?> lookupSupplier(String name) {
Object composed = compose(name, this.suppliers, false);
if (composed instanceof Supplier) {
return (Supplier<?>) composed;
}
return null;
}
public Consumer<?> lookupConsumer(String name) {
Object composed = compose(name, this.consumers, true);
if (composed instanceof Consumer) {
return (Consumer<?>) composed;
}
return null;
}
public Consumer<?> lookupConsumer(String name) {
Object composed = compose(name, this.consumers, true);
if (composed instanceof Consumer) {
return (Consumer<?>) composed;
}
return null;
}
public Function<?, ?> lookupFunction(String name) {
Object composed = compose(name, this.functions, true);
if (composed instanceof Function) {
return (Function<?, ?>) composed;
}
return null;
}
public Function<?, ?> lookupFunction(String name) {
Object composed = compose(name, this.functions, true);
if (composed instanceof Function) {
return (Function<?, ?>) composed;
}
return null;
}
private Object compose(String name, Map<String, Object> lookup,
boolean hasInput) {
name = name.replaceAll(",", "|");
if (lookup.containsKey(name)) {
return lookup.get(name);
}
String[] stages = StringUtils.delimitedListToStringArray(name, "|");
Map<String, Object> source = !hasInput || stages.length <= 1 ? lookup
: this.functions;
if (stages.length == 0 && source.size() == 1) {
stages = new String[] { source.keySet().iterator().next() };
}
Object function = stages.length > 0 ? lookup(stages[0], source) : null;
if (function == null) {
return null;
}
Object other = null;
for (int i = 1; i < stages.length - 1; i++) {
other = lookup(stages[i], this.functions);
if (other == null) {
return null;
}
function = compose(function, other);
}
if (stages.length > 1) {
other = lookup(stages[stages.length - 1],
hasInput ? lookup : this.functions);
if (other == null) {
return null;
}
function = compose(function, other);
}
final Object value = function;
lookup.computeIfAbsent(name, key -> value);
if (!types.containsKey(name)) {
if (types.containsKey(stages[0])
&& types.containsKey(stages[stages.length - 1])) {
FunctionType input = types.get(stages[0]);
FunctionType output = types.get(stages[stages.length - 1]);
types.put(name, FunctionType.compose(input, output));
}
}
names.put(function, name);
return function;
}
private Object compose(String name, Map<String, Object> lookup,
boolean hasInput) {
name = name.replaceAll(",", "|");
if (lookup.containsKey(name)) {
return lookup.get(name);
}
String[] stages = StringUtils.delimitedListToStringArray(name, "|");
Map<String, Object> source = !hasInput || stages.length <= 1 ? lookup
: this.functions;
if (stages.length == 0 && source.size() == 1) {
stages = new String[]{source.keySet().iterator().next()};
}
Object function = stages.length > 0 ? lookup(stages[0], source) : null;
if (function == null) {
return null;
}
Object other = null;
for (int i = 1; i < stages.length - 1; i++) {
other = lookup(stages[i], this.functions);
if (other == null) {
return null;
}
function = compose(function, other);
}
if (stages.length > 1) {
other = lookup(stages[stages.length - 1],
hasInput ? lookup : this.functions);
if (other == null) {
return null;
}
function = compose(function, other);
}
final Object value = function;
lookup.computeIfAbsent(name, key -> value);
if (!types.containsKey(name)) {
if (types.containsKey(stages[0])
&& types.containsKey(stages[stages.length - 1])) {
FunctionType input = types.get(stages[0]);
FunctionType output = types.get(stages[stages.length - 1]);
types.put(name, FunctionType.compose(input, output));
}
}
names.put(function, name);
return function;
}
private Object lookup(String name, Map<String, Object> lookup) {
Object result = lookup.get(name);
if (result != null) {
findType(result);
}
return result;
}
private Object lookup(String name, Map<String, Object> lookup) {
Object result = lookup.get(name);
if (result != null) {
findType(result);
}
return result;
}
@SuppressWarnings("unchecked")
private Object compose(Object a, Object b) {
if (a instanceof Supplier && b instanceof Function) {
Supplier<Object> supplier = (Supplier<Object>) a;
Function<Object, Object> function = (Function<Object, Object>) b;
return (Supplier<Object>) () -> function.apply(supplier.get());
}
else if (a instanceof Function && b instanceof Function) {
Function<Object, Object> function1 = (Function<Object, Object>) a;
Function<Object, Object> function2 = (Function<Object, Object>) b;
return function1.andThen(function2);
}
else if (a instanceof Function && b instanceof Consumer) {
Function<Object, Object> function = (Function<Object, Object>) a;
Consumer<Object> consumer = (Consumer<Object>) b;
return (Consumer<Object>) v -> consumer.accept(function.apply(v));
}
else {
throw new IllegalArgumentException(String.format(
"Could not compose %s and %s", a.getClass(), b.getClass()));
}
}
@SuppressWarnings("unchecked")
private Object compose(Object a, Object b) {
if (a instanceof Supplier && b instanceof Function) {
Supplier<Object> supplier = (Supplier<Object>) a;
Function<Object, Object> function = (Function<Object, Object>) b;
return (Supplier<Object>) () -> function.apply(supplier.get());
} else if (a instanceof Function && b instanceof Function) {
Function<Object, Object> function1 = (Function<Object, Object>) a;
Function<Object, Object> function2 = (Function<Object, Object>) b;
return function1.andThen(function2);
} else if (a instanceof Function && b instanceof Consumer) {
Function<Object, Object> function = (Function<Object, Object>) a;
Consumer<Object> consumer = (Consumer<Object>) b;
return (Consumer<Object>) v -> consumer.accept(function.apply(v));
} else {
throw new IllegalArgumentException(String.format(
"Could not compose %s and %s", a.getClass(), b.getClass()));
}
}
@SuppressWarnings("unchecked")
public <T> void register(FunctionRegistration<T> function) {
wrap((FunctionRegistration<Object>) function,
function.getNames().iterator().next());
}
@SuppressWarnings("unchecked")
public <T> void register(FunctionRegistration<T> function) {
wrap((FunctionRegistration<Object>) function,
function.getNames().iterator().next());
}
@PreDestroy
public void close() {
if (publisher != null) {
if (!functions.isEmpty()) {
publisher.publishEvent(new FunctionUnregistrationEvent(this,
Function.class, functions.keySet()));
}
if (!consumers.isEmpty()) {
publisher.publishEvent(new FunctionUnregistrationEvent(this,
Consumer.class, consumers.keySet()));
}
if (!suppliers.isEmpty()) {
publisher.publishEvent(new FunctionUnregistrationEvent(this,
Supplier.class, suppliers.keySet()));
}
}
}
@PreDestroy
public void close() {
if (publisher != null) {
if (!functions.isEmpty()) {
publisher.publishEvent(new FunctionUnregistrationEvent(this,
Function.class, functions.keySet()));
}
if (!consumers.isEmpty()) {
publisher.publishEvent(new FunctionUnregistrationEvent(this,
Consumer.class, consumers.keySet()));
}
if (!suppliers.isEmpty()) {
publisher.publishEvent(new FunctionUnregistrationEvent(this,
Supplier.class, suppliers.keySet()));
}
}
}
public Set<FunctionRegistration<?>> merge(
Map<String, FunctionRegistration<?>> initial,
Map<String, Consumer<?>> consumers, Map<String, Supplier<?>> suppliers,
Map<String, Function<?, ?>> functions) {
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);
}
public Set<FunctionRegistration<?>> merge(
Map<String, FunctionRegistration<?>> initial,
Map<String, Consumer<?>> consumers, Map<String, Supplier<?>> suppliers,
Map<String, Function<?, ?>> functions) {
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);
}
Stream.concat(consumers.entrySet().stream(), Stream.concat(suppliers.entrySet().stream(), functions.entrySet().stream()))
.forEach(entry -> {
if (!targets.containsKey(entry.getValue())) {
FunctionRegistration<Object> target = new FunctionRegistration<Object>(
entry.getValue(), getAliases(entry.getKey()).toArray(new String[] {}));
targets.put(target.getTarget(), entry.getKey());
registrations.add(target);
}
});
// Wrap the functions so they handle reactive inputs and outputs
registrations.forEach(registration -> wrap(registration, targets.get(registration.getTarget())));
return registrations;
}
Stream.concat(consumers.entrySet().stream(), Stream.concat(suppliers.entrySet().stream(), functions.entrySet().stream()))
.forEach(entry -> {
if (!targets.containsKey(entry.getValue())) {
FunctionRegistration<Object> target = new FunctionRegistration<Object>(
entry.getValue(), getAliases(entry.getKey()).toArray(new String[]{}));
targets.put(target.getTarget(), entry.getKey());
registrations.add(target);
}
});
// Wrap the functions so they handle reactive inputs and outputs
registrations.forEach(registration -> wrap(registration, targets.get(registration.getTarget())));
return registrations;
}
private Collection<String> getAliases(String key) {
Collection<String> names = new LinkedHashSet<>();
String value = getQualifier(key);
if (value.equals(key) && registry != null) {
names.addAll(Arrays.asList(registry.getAliases(key)));
}
names.add(value);
return names;
}
private Collection<String> getAliases(String key) {
Collection<String> names = new LinkedHashSet<>();
String value = getQualifier(key);
if (value.equals(key) && registry != null) {
names.addAll(Arrays.asList(registry.getAliases(key)));
}
names.add(value);
return names;
}
private void wrap(FunctionRegistration<?> registration, String key) {
Object target = registration.getTarget();
this.names.put(target, key);
if (registration.getType() != null) {
this.types.put(key, registration.getType());
}
else {
registration.type(findType(target).getType());
}
Class<?> type;
registration = transform(registration);
target = registration.getTarget();
if (target instanceof Supplier) {
type = Supplier.class;
for (String name : registration.getNames()) {
this.suppliers.put(name, registration.getTarget());
}
}
else if (target instanceof Consumer) {
type = Consumer.class;
for (String name : registration.getNames()) {
this.consumers.put(name, registration.getTarget());
}
}
else if (target instanceof Function) {
type = Function.class;
for (String name : registration.getNames()) {
this.functions.put(name, registration.getTarget());
}
}
else {
return;
}
// this.names.remove(target);
this.names.put(registration.getTarget(), key);
if (publisher != null) {
publisher.publishEvent(new FunctionRegistrationEvent(
registration.getTarget(), type, registration.getNames()));
}
}
private void wrap(FunctionRegistration<?> registration, String key) {
Object target = registration.getTarget();
this.names.put(target, key);
if (registration.getType() != null) {
this.types.put(key, registration.getType());
} else {
registration.type(findType(target).getType());
}
Class<?> type;
registration = transform(registration);
target = registration.getTarget();
if (target instanceof Supplier) {
type = Supplier.class;
for (String name : registration.getNames()) {
this.suppliers.put(name, registration.getTarget());
}
} else if (target instanceof Consumer) {
type = Consumer.class;
for (String name : registration.getNames()) {
this.consumers.put(name, registration.getTarget());
}
} else if (target instanceof Function) {
type = Function.class;
for (String name : registration.getNames()) {
this.functions.put(name, registration.getTarget());
}
} else {
return;
}
// this.names.remove(target);
this.names.put(registration.getTarget(), key);
if (publisher != null) {
publisher.publishEvent(new FunctionRegistrationEvent(
registration.getTarget(), type, registration.getNames()));
}
}
private FunctionRegistration<?> transform(FunctionRegistration<?> registration) {
return fluxify(isolated(registration));
}
private FunctionRegistration<?> transform(FunctionRegistration<?> registration) {
return fluxify(isolated(registration));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private FunctionRegistration<?> fluxify(FunctionRegistration<?> input) {
FunctionRegistration<Object> registration = (FunctionRegistration<Object>) input;
Object target = registration.getTarget();
FunctionType type = registration.getType();
boolean flux = hasFluxTypes(type);
if (!flux) {
if (target instanceof Supplier<?>) {
target = new FluxSupplier((Supplier<?>) target);
}
else if (target instanceof Function<?, ?>) {
target = new FluxFunction((Function<?, ?>) target);
}
else if (target instanceof Consumer<?>) {
target = new FluxConsumer((Consumer<?>) target);
}
registration.target(target);
}
return registration;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private FunctionRegistration<?> fluxify(FunctionRegistration<?> input) {
FunctionRegistration<Object> registration = (FunctionRegistration<Object>) input;
Object target = registration.getTarget();
FunctionType type = registration.getType();
boolean flux = hasFluxTypes(type);
if (!flux) {
if (target instanceof Supplier<?>) {
target = new FluxSupplier((Supplier<?>) target);
} else if (target instanceof Function<?, ?>) {
target = new FluxFunction((Function<?, ?>) target);
} else if (target instanceof Consumer<?>) {
target = new FluxConsumer((Consumer<?>) target);
}
registration.target(target);
}
return registration;
}
private boolean hasFluxTypes(FunctionType type) {
return type.isWrapper();
}
private boolean hasFluxTypes(FunctionType type) {
return type.isWrapper();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private FunctionRegistration<?> isolated(FunctionRegistration<?> input) {
FunctionRegistration<Object> registration = (FunctionRegistration<Object>) input;
Object target = registration.getTarget();
boolean isolated = getClass().getClassLoader() != target.getClass()
.getClassLoader();
if (target instanceof Supplier<?>) {
if (isolated) {
target = new IsolatedSupplier((Supplier<?>) target);
}
}
else if (target instanceof Function<?, ?>) {
if (isolated) {
target = new IsolatedFunction((Function<?, ?>) target);
}
}
else if (target instanceof Consumer<?>) {
if (isolated) {
target = new IsolatedConsumer((Consumer<?>) target);
}
}
registration.target(target);
return registration;
}
@SuppressWarnings({"rawtypes", "unchecked"})
private FunctionRegistration<?> isolated(FunctionRegistration<?> input) {
FunctionRegistration<Object> registration = (FunctionRegistration<Object>) input;
Object target = registration.getTarget();
boolean isolated = getClass().getClassLoader() != target.getClass()
.getClassLoader();
if (target instanceof Supplier<?>) {
if (isolated) {
target = new IsolatedSupplier((Supplier<?>) target);
}
} else if (target instanceof Function<?, ?>) {
if (isolated) {
target = new IsolatedFunction((Function<?, ?>) target);
}
} else if (target instanceof Consumer<?>) {
if (isolated) {
target = new IsolatedConsumer((Consumer<?>) target);
}
}
registration.target(target);
return registration;
}
private String getQualifier(String key) {
if (registry != null && registry.containsBeanDefinition(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 key;
}
private String getQualifier(String key) {
if (registry != null && registry.containsBeanDefinition(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 key;
}
private FunctionType findType(String name, AbstractBeanDefinition definition) {
Object source = definition.getSource();
FunctionType param = null;
// Start by assuming output -> Function
if (source instanceof StandardMethodMetadata) {
// Standard @Bean metadata
Type beanType = ((StandardMethodMetadata) source).getIntrospectedMethod()
.getGenericReturnType();
if (beanType instanceof ParameterizedType) {
ParameterizedType type = (ParameterizedType) beanType;
param = new FunctionType(type);
}
else {
param = new FunctionType(beanType);
}
}
else if (source instanceof MethodMetadataReadingVisitor) {
// A component scan with @Beans
MethodMetadataReadingVisitor visitor = (MethodMetadataReadingVisitor) source;
Type type = findBeanType(definition, visitor);
param = new FunctionType(type);
}
else if (source instanceof Resource) {
Class<?> beanType = this.registry.getType(name);
param = new FunctionType(beanType);
}
else {
ResolvableType resolvable = (ResolvableType) getField(definition,
"targetType");
if (resolvable != null) {
param = new FunctionType(resolvable.getType());
}
else {
Class<?> beanClass = definition.getBeanClass();
if (beanClass != null && !FunctionFactoryMetadata.class
.isAssignableFrom(beanClass)) {
Type type = beanClass;
param = new FunctionType(type);
}
else {
Object bean = this.registry.getBean(name);
if (bean instanceof FunctionFactoryMetadata) {
FunctionFactoryMetadata<?> factory = (FunctionFactoryMetadata<?>) bean;
Type type = factory.getFactoryMethod().getGenericReturnType();
param = new FunctionType(type);
}
else {
param = new FunctionType(bean.getClass());
}
}
}
}
return param;
}
private FunctionType findType(Object function) {
String name = names.get(function);
if (types.containsKey(name)) {
return types.get(name);
}
FunctionType param;
if (name == null || registry == null
|| !registry.containsBeanDefinition(name)) {
if (function != null) {
param = new FunctionType(function.getClass());
} else {
param = FunctionType.UNCLASSIFIED;
}
} else {
param = FunctionContextUtils.findType(name, registry);
}
types.computeIfAbsent(name, str -> param);
return param;
}
private Type findBeanType(AbstractBeanDefinition definition,
MethodMetadataReadingVisitor visitor) {
Class<?> factory = ClassUtils
.resolveClassName(visitor.getDeclaringClassName(), null);
Class<?>[] params = getParamTypes(factory, definition);
Method method = ReflectionUtils.findMethod(factory, visitor.getMethodName(),
params);
Type type = method.getGenericReturnType();
return type;
}
}
private Method[] getCandidateMethods(final Class<?> factoryClass,
final RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
@Override
public Method[] run() {
return (mbd.isNonPublicAccessAllowed()
? ReflectionUtils.getAllDeclaredMethods(factoryClass)
: factoryClass.getMethods());
}
});
}
else {
return (mbd.isNonPublicAccessAllowed()
? ReflectionUtils.getAllDeclaredMethods(factoryClass)
: factoryClass.getMethods());
}
}
private static class PreferGsonOrMissingJacksonCondition extends AnyNestedCondition {
private Class<?>[] getParamTypes(Class<?> factory,
AbstractBeanDefinition definition) {
if (definition instanceof RootBeanDefinition) {
RootBeanDefinition root = (RootBeanDefinition) definition;
for (Method method : getCandidateMethods(factory, root)) {
if (root.isFactoryMethod(method)) {
return method.getParameterTypes();
}
}
}
List<Class<?>> params = new ArrayList<>();
for (ValueHolder holder : definition.getConstructorArgumentValues()
.getIndexedArgumentValues().values()) {
params.add(ClassUtils.resolveClassName(holder.getType(), null));
}
return params.toArray(new Class<?>[0]);
}
PreferGsonOrMissingJacksonCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
private Object getField(Object target, String name) {
Field field = ReflectionUtils.findField(target.getClass(), name);
if (field == null) {
return null;
}
ReflectionUtils.makeAccessible(field);
return ReflectionUtils.getField(field, target);
}
@ConditionalOnProperty(name = ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "gson", matchIfMissing = false)
static class GsonPreferred {
private FunctionType findType(Object function) {
String name = names.get(function);
if (types.containsKey(name)) {
return types.get(name);
}
FunctionType param;
if (name == null || registry == null
|| !registry.containsBeanDefinition(name)) {
if (function != null) {
param = new FunctionType(function.getClass());
}
else {
param = FunctionType.UNCLASSIFIED;
}
}
else {
param = findType(name,
(AbstractBeanDefinition) registry.getBeanDefinition(name));
}
types.computeIfAbsent(name, str -> param);
return param;
}
}
}
@ConditionalOnMissingBean(ObjectMapper.class)
static class JacksonMissing {
private static class PreferGsonOrMissingJacksonCondition extends AnyNestedCondition {
}
PreferGsonOrMissingJacksonCondition() {
super(ConfigurationPhase.REGISTER_BEAN);
}
@ConditionalOnProperty(name = ContextFunctionCatalogAutoConfiguration.PREFERRED_MAPPER_PROPERTY, havingValue = "gson", matchIfMissing = false)
static class GsonPreferred {
}
@ConditionalOnMissingBean(ObjectMapper.class)
static class JacksonMissing {
}
}
}
}

View File

@@ -0,0 +1,160 @@
/*
* Copyright 2018 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.config;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.beans.factory.config.ConstructorArgumentValues;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.cloud.function.context.FunctionType;
import org.springframework.cloud.function.core.FunctionFactoryMetadata;
import org.springframework.core.ResolvableType;
import org.springframework.core.io.Resource;
import org.springframework.core.type.StandardMethodMetadata;
import org.springframework.core.type.classreading.MethodMetadataReadingVisitor;
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.security.AccessController;
import java.security.PrivilegedAction;
import java.util.ArrayList;
import java.util.List;
/**
* @author Oleg Zhurakousky
*
* @since 2.0
*/
abstract class FunctionContextUtils {
public static FunctionType findType(String name, ConfigurableListableBeanFactory registry) {
AbstractBeanDefinition definition = (AbstractBeanDefinition) registry.getBeanDefinition(name);
Object source = definition.getSource();
FunctionType param = null;
// Start by assuming output -> Function
if (source instanceof StandardMethodMetadata) {
// Standard @Bean metadata
Type beanType = ((StandardMethodMetadata) source).getIntrospectedMethod()
.getGenericReturnType();
if (beanType instanceof ParameterizedType) {
ParameterizedType type = (ParameterizedType) beanType;
param = new FunctionType(type);
}
else {
param = new FunctionType(beanType);
}
}
else if (source instanceof MethodMetadataReadingVisitor) {
// A component scan with @Beans
MethodMetadataReadingVisitor visitor = (MethodMetadataReadingVisitor) source;
Type type = findBeanType(definition, visitor);
param = new FunctionType(type);
}
else if (source instanceof Resource) {
Class<?> beanType = registry.getType(name);
param = new FunctionType(beanType);
}
else {
ResolvableType resolvable = (ResolvableType) getField(definition,
"targetType");
if (resolvable != null) {
param = new FunctionType(resolvable.getType());
}
else {
Class<?> beanClass = definition.getBeanClass();
if (beanClass != null && !FunctionFactoryMetadata.class
.isAssignableFrom(beanClass)) {
Type type = beanClass;
param = new FunctionType(type);
}
else {
Object bean = registry.getBean(name);
if (bean instanceof FunctionFactoryMetadata) {
FunctionFactoryMetadata<?> factory = (FunctionFactoryMetadata<?>) bean;
Type type = factory.getFactoryMethod().getGenericReturnType();
param = new FunctionType(type);
}
else {
param = new FunctionType(bean.getClass());
}
}
}
}
return param;
}
private static Type findBeanType(AbstractBeanDefinition definition,
MethodMetadataReadingVisitor visitor) {
Class<?> factory = ClassUtils
.resolveClassName(visitor.getDeclaringClassName(), null);
Class<?>[] params = getParamTypes(factory, definition);
Method method = ReflectionUtils.findMethod(factory, visitor.getMethodName(),
params);
Type type = method.getGenericReturnType();
return type;
}
private static Class<?>[] getParamTypes(Class<?> factory,
AbstractBeanDefinition definition) {
if (definition instanceof RootBeanDefinition) {
RootBeanDefinition root = (RootBeanDefinition) definition;
for (Method method : getCandidateMethods(factory, root)) {
if (root.isFactoryMethod(method)) {
return method.getParameterTypes();
}
}
}
List<Class<?>> params = new ArrayList<>();
for (ConstructorArgumentValues.ValueHolder holder : definition.getConstructorArgumentValues()
.getIndexedArgumentValues().values()) {
params.add(ClassUtils.resolveClassName(holder.getType(), null));
}
return params.toArray(new Class<?>[0]);
}
private static Method[] getCandidateMethods(final Class<?> factoryClass,
final RootBeanDefinition mbd) {
if (System.getSecurityManager() != null) {
return AccessController.doPrivileged(new PrivilegedAction<Method[]>() {
@Override
public Method[] run() {
return (mbd.isNonPublicAccessAllowed()
? ReflectionUtils.getAllDeclaredMethods(factoryClass)
: factoryClass.getMethods());
}
});
}
else {
return (mbd.isNonPublicAccessAllowed()
? ReflectionUtils.getAllDeclaredMethods(factoryClass)
: factoryClass.getMethods());
}
}
private static Object getField(Object target, String name) {
Field field = ReflectionUtils.findField(target.getClass(), name);
if (field == null) {
return null;
}
ReflectionUtils.makeAccessible(field);
return ReflectionUtils.getField(field, target);
}
}

View File

@@ -0,0 +1,157 @@
/*
* Copyright 2018 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.config;
import kotlin.Unit;
import kotlin.jvm.functions.Function0;
import kotlin.jvm.functions.Function1;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
import org.springframework.beans.factory.BeanFactoryAware;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.cloud.function.context.FunctionType;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
/**
* Configuration class which defines the required infrastructure to
* bootstrap Kotlin lambdas as invocable functions within the context of the framework.
*
* @author Oleg Zhurakousky
*
* @since 2.0
*/
@Configuration
@ConditionalOnClass(name="kotlin.jvm.functions.Function1")
class KotlinLambdaToFunctionAutoConfiguration implements BeanFactoryAware {
protected final Log logger = LogFactory.getLog(getClass());
private ConfigurableListableBeanFactory beanFactory;
@Override
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {
this.beanFactory = (ConfigurableListableBeanFactory) beanFactory;
}
/**
* Will transform all discovered Kotlin's Function1 and Function0 lambdas to
* java Supplier, Function and Consumer, retaining the original Kotlin type characteristics.
* In other words the resulting bean coudl be cast to both java and kotlin
* types (i.e., java Function<I,O> vs. kotlin Function1<I,O>)
*/
@Bean
public BeanPostProcessor kotlinPostProcessor() {
return new BeanPostProcessor() {
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
if (bean instanceof Function1) {
FunctionType functionType = FunctionContextUtils.findType(beanName, beanFactory);
if (Unit.class.isAssignableFrom(functionType.getOutputType())) {
logger.debug("Transforming Kotlin lambda " + beanName + " to java Consumer");
bean = new KotlinConsumer((Function1) bean);
}
else {
logger.debug("Transforming Kotlin lambda " + beanName + " to java Function");
bean = new KotlinFunction((Function1) bean);
}
}
else if (bean instanceof Function0) {
logger.debug("Transforming Kotlin lambda " + beanName + " to java Supplier");
bean = new KotlinSupplier((Function0) bean);
}
return bean;
}
};
}
/**
* Wrapper for Kotlin lambda to be represented as both Java Function<I,O> as well as Kotlin's Function1<I,O>
*/
private static class KotlinFunction<I,O> implements Function<I,O>, Function1<I,O> {
private final Function1<I,O> kotlinLambda;
KotlinFunction(Function1<I,O> kotlinLambda) {
this.kotlinLambda = kotlinLambda;
}
@Override
public O apply(I i) {
return this.kotlinLambda.invoke(i);
}
@Override
public O invoke(I i) {
return this.apply(i);
}
}
/**
* Wrapper for Kotlin lambda to be represented as both Java Consumer<I> as well as Kotlin's Function1<I,Unit>
*/
private static class KotlinConsumer<I,Unit> implements Consumer<I>, Function1<I,Unit> {
private final Function1<I,Unit> kotlinLambda;
KotlinConsumer(Function1<I,Unit> kotlinLambda) {
this.kotlinLambda = kotlinLambda;
}
@Override
public Unit invoke(I i) {
return this.kotlinLambda.invoke(i);
}
@Override
public void accept(I i) {
this.kotlinLambda.invoke(i);
}
}
/**
* Wrapper for Kotlin lambda to be represented as both Java Supplier<O> as well as Kotlin's Function0<O>
*/
private static class KotlinSupplier<O> implements Supplier<O>, Function0<O> {
private final Function0<O> kotlinLambda;
KotlinSupplier(Function0<O> kotlinLambda) {
this.kotlinLambda = kotlinLambda;
}
@Override
public O get() {
return this.invoke();
}
@Override
public O invoke() {
return this.kotlinLambda.invoke();
}
}
}

View File

@@ -1,5 +1,6 @@
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration
org.springframework.cloud.function.context.config.ContextFunctionCatalogAutoConfiguration,\
org.springframework.cloud.function.context.config.KotlinLambdaToFunctionAutoConfiguration
org.springframework.cloud.function.context.WrapperDetector=\
org.springframework.cloud.function.context.config.FluxWrapperDetector

View File

@@ -27,6 +27,8 @@ import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import kotlin.jvm.functions.Function0;
import kotlin.jvm.functions.Function1;
import org.junit.After;
import org.junit.Test;
import org.reactivestreams.Publisher;
@@ -48,6 +50,7 @@ import org.springframework.cloud.function.context.FunctionRegistration;
import org.springframework.cloud.function.context.FunctionScan;
import org.springframework.cloud.function.context.catalog.FunctionInspector;
import org.springframework.cloud.function.inject.FooConfiguration;
import org.springframework.cloud.function.kotlin.KotlinLambdasConfiguration;
import org.springframework.cloud.function.scan.ScannedFunction;
import org.springframework.cloud.function.test.GenericFunction;
import org.springframework.context.ConfigurableApplicationContext;
@@ -372,6 +375,34 @@ public class ContextFunctionCatalogAutoConfigurationTests {
}
}
@Test
public void kotlinLambdas() {
create(KotlinLambdasConfiguration.class);
assertThat(context.getBean("kotlinFunction")).isInstanceOf(Function.class);
assertThat(context.getBean("kotlinFunction")).isInstanceOf(Function1.class);
assertThat(catalog.lookup(Function.class, "kotlinFunction"))
.isInstanceOf(Function.class);
assertThat(inspector.getInputType(catalog.lookup(Function.class, "kotlinFunction")))
.isAssignableFrom(String.class);
assertThat(inspector.getOutputType(catalog.lookup(Function.class, "kotlinFunction")))
.isAssignableFrom(String.class);
assertThat(context.getBean("kotlinConsumer")).isInstanceOf(Consumer.class);
assertThat(context.getBean("kotlinConsumer")).isInstanceOf(Function1.class);
assertThat(catalog.lookup(Function.class, "kotlinConsumer"))
.isInstanceOf(Function.class);
assertThat(inspector.getInputType(catalog.lookup(Function.class, "kotlinConsumer")))
.isAssignableFrom(String.class);
assertThat(context.getBean("kotlinSupplier")).isInstanceOf(Supplier.class);
assertThat(context.getBean("kotlinSupplier")).isInstanceOf(Function0.class);
assertThat(catalog.lookup(Supplier.class, "kotlinSupplier"))
.isInstanceOf(Supplier.class);
assertThat(inspector.getOutputType(catalog.lookup(Supplier.class, "kotlinSupplier")))
.isAssignableFrom(String.class);
}
private void create(String jarfile, Class<?> config, String... props) {
try {
URL[] urls = new URL[] { new ClassPathResource(jarfile).getURL() };

View File

@@ -0,0 +1,44 @@
/*
* Copyright 2018 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.kotlin
import org.springframework.boot.autoconfigure.EnableAutoConfiguration
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
/**
* @author Oleg Zhurakousky
*
*/
@EnableAutoConfiguration
@Configuration
open class KotlinLambdasConfiguration {
@Bean
open fun kotlinFunction(): (String) -> String {
return { it.toUpperCase() }
}
@Bean
open fun kotlinConsumer(): (String) -> Unit {
return { }
}
@Bean
open fun kotlinSupplier(): () -> String {
return { "Hello" }
}
}