GH-77 added initial Kotlin support
Added mixed java/kotlin POM configuration added tests, javadocs Resolves #77 Resolves #204
This commit is contained in:
@@ -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.
|
||||
*/
|
||||
|
||||
@@ -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 {
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -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() };
|
||||
|
||||
@@ -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" }
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user