Add ChannelsEndpoint back for introspecting bindings

Bindable picks up 2 new methods to enumerate the binding names, and
then it's a simple matter to list all the inputs and outputs and their
properties.

Fixes gh-149
This commit is contained in:
Dave Syer
2015-10-16 18:01:42 -04:00
parent 6f8039c617
commit 3a1b030dc8
5 changed files with 153 additions and 26 deletions

View File

@@ -16,6 +16,8 @@
package org.springframework.cloud.stream.binding;
import java.util.Set;
/**
* Marker interface for instances that can bind/unbind groups of inputs and outputs.
*
@@ -45,4 +47,14 @@ public interface Bindable {
*/
void unbindOutputs(ChannelBindingService adapter);
/**
* Enumerates all the input binding names.
*/
Set<String> getInputs();
/**
* Enumerates all the output binding names.
*/
Set<String> getOutputs();
}

View File

@@ -19,12 +19,12 @@ package org.springframework.cloud.stream.binding;
import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import org.aopalliance.intercept.MethodInterceptor;
import org.aopalliance.intercept.MethodInvocation;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.aop.framework.ProxyFactory;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.BeanFactory;
@@ -102,7 +102,17 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
@Override
public void afterPropertiesSet() throws Exception {
Assert.notNull(beanFactory, "Bean factory cannot be empty");
Assert.notNull(this.beanFactory, "Bean factory cannot be empty");
}
@Override
public Set<String> getInputs() {
return this.inputs.keySet();
}
@Override
public Set<String> getOutputs() {
return this.outputs.keySet();
}
private void createChannels(Class<?> type) throws Exception {
@@ -119,11 +129,11 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
MessageChannel sharedChannel = locateSharedChannel(name);
if (sharedChannel == null) {
MessageChannel inputChannel = createMessageChannel(inputChannelType);
inputs.put(name, new ChannelHolder(inputChannel, true));
BindableProxyFactory.this.inputs.put(name, new ChannelHolder(inputChannel, true));
}
else {
if (inputChannelType.isAssignableFrom(sharedChannel.getClass())) {
inputs.put(name, new ChannelHolder(sharedChannel, false));
BindableProxyFactory.this.inputs.put(name, new ChannelHolder(sharedChannel, false));
}
else {
// handle the special case where the shared channel is of a different nature
@@ -137,7 +147,7 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
bridgeSubscribableToPollableChannel(
(SubscribableChannel) sharedChannel, inputChannel);
}
inputs.put(name, new ChannelHolder(inputChannel, false));
BindableProxyFactory.this.inputs.put(name, new ChannelHolder(inputChannel, false));
}
}
}
@@ -150,11 +160,11 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
MessageChannel sharedChannel = locateSharedChannel(name);
if (sharedChannel == null) {
MessageChannel outputChannel = createMessageChannel(messageChannelType);
outputs.put(name, new ChannelHolder(outputChannel, true));
BindableProxyFactory.this.outputs.put(name, new ChannelHolder(outputChannel, true));
}
else {
if (messageChannelType.isAssignableFrom(sharedChannel.getClass())) {
outputs.put(name, new ChannelHolder(sharedChannel, false));
BindableProxyFactory.this.outputs.put(name, new ChannelHolder(sharedChannel, false));
}
else {
// handle the special case where the shared channel is of a different nature
@@ -169,7 +179,7 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
(SubscribableChannel) outputChannel,
sharedChannel);
}
outputs.put(name, new ChannelHolder(outputChannel, false));
BindableProxyFactory.this.outputs.put(name, new ChannelHolder(outputChannel, false));
}
}
}
@@ -178,11 +188,11 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
}
private MessageChannel locateSharedChannel(String name) {
return sharedChannelRegistry != null ? sharedChannelRegistry.get(getNamespacePrefixedChannelName(name)) : null;
return this.sharedChannelRegistry != null ? this.sharedChannelRegistry.get(getNamespacePrefixedChannelName(name)) : null;
}
private String getNamespacePrefixedChannelName(String name) {
return channelNamespace + "." + name;
return this.channelNamespace + "." + name;
}
private void bridgeSubscribableToPollableChannel(SubscribableChannel sharedChannel,
@@ -196,12 +206,12 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
ConsumerEndpointFactoryBean consumerEndpointFactoryBean = new ConsumerEndpointFactoryBean();
consumerEndpointFactoryBean.setInputChannel(pollableChannel);
PollerMetadata pollerMetadata = new PollerMetadata();
pollerMetadata.setTrigger(new PeriodicTrigger(pollableBridgeDefaultFrequency));
pollerMetadata.setTrigger(new PeriodicTrigger(this.pollableBridgeDefaultFrequency));
consumerEndpointFactoryBean.setPollerMetadata(pollerMetadata);
consumerEndpointFactoryBean
.setHandler(new MessageChannelBinderSupport.DirectHandler(
subscribableChannel));
consumerEndpointFactoryBean.setBeanFactory(beanFactory);
consumerEndpointFactoryBean.setBeanFactory(this.beanFactory);
try {
consumerEndpointFactoryBean.afterPropertiesSet();
} catch (Exception e) {
@@ -243,7 +253,7 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
public synchronized Object getObject() throws Exception {
if (this.proxy == null) {
createChannels(this.type);
ProxyFactory factory = new ProxyFactory(type, this);
ProxyFactory factory = new ProxyFactory(this.type, this);
this.proxy = factory.getProxy();
}
return this.proxy;
@@ -264,7 +274,7 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
if (log.isDebugEnabled()) {
log.debug(String.format("Binding inputs for %s:%s", this.channelNamespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : inputs.entrySet()) {
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.inputs.entrySet()) {
String inputChannelName = channelHolderEntry.getKey();
ChannelHolder channelHolder = channelHolderEntry.getValue();
channelBindingService.configureMessageConverters(channelHolder.getMessageChannel(), inputChannelName);
@@ -282,7 +292,7 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
if (log.isDebugEnabled()) {
log.debug(String.format("Binding outputs for %s:%s", this.channelNamespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : outputs.entrySet()) {
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.outputs.entrySet()) {
ChannelHolder channelHolder = channelHolderEntry.getValue();
String outputChannelName = channelHolderEntry.getKey();
channelBindingService.configureMessageConverters(channelHolder.getMessageChannel(), outputChannelName);
@@ -300,7 +310,7 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
if (log.isDebugEnabled()) {
log.debug(String.format("Unbinding inputs for %s:%s", this.channelNamespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : inputs.entrySet()) {
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.inputs.entrySet()) {
if (channelHolderEntry.getValue().isBindable()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Unbinding %s:%s:%s", this.channelNamespace, this.type, channelHolderEntry.getKey()));
@@ -315,7 +325,7 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
if (log.isDebugEnabled()) {
log.debug(String.format("Unbinding outputs for %s:%s", this.channelNamespace, this.type));
}
for (Map.Entry<String, ChannelHolder> channelHolderEntry : outputs.entrySet()) {
for (Map.Entry<String, ChannelHolder> channelHolderEntry : this.outputs.entrySet()) {
if (channelHolderEntry.getValue().isBindable()) {
if (log.isDebugEnabled()) {
log.debug(String.format("Binding %s:%s:%s", this.channelNamespace, this.type, channelHolderEntry.getKey()));
@@ -342,11 +352,11 @@ public class BindableProxyFactory implements MethodInterceptor, FactoryBean<Obje
}
public MessageChannel getMessageChannel() {
return messageChannel;
return this.messageChannel;
}
public boolean isBindable() {
return bindable;
return this.bindable;
}
}

View File

@@ -16,12 +16,16 @@
package org.springframework.cloud.stream.config;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
/**
* Contains the properties of a binding.
*
* @author Marius Bogoevici
* @author Ilayaperumal Gopinathan
*/
@JsonInclude(value = Include.NON_DEFAULT)
public class BindingProperties {
private String destination;
@@ -41,7 +45,7 @@ public class BindingProperties {
private String contentType;
public String getDestination() {
return destination;
return this.destination;
}
public void setDestination(String destination) {
@@ -49,7 +53,7 @@ public class BindingProperties {
}
public boolean isPartitioned() {
return partitioned;
return this.partitioned;
}
public void setPartitioned(boolean partitioned) {
@@ -57,7 +61,7 @@ public class BindingProperties {
}
public int getPartitionCount() {
return partitionCount;
return this.partitionCount;
}
public void setPartitionCount(int partitionCount) {
@@ -65,7 +69,7 @@ public class BindingProperties {
}
public String getPartitionKeyExpression() {
return partitionKeyExpression;
return this.partitionKeyExpression;
}
public void setPartitionKeyExpression(String partitionKeyExpression) {
@@ -73,7 +77,7 @@ public class BindingProperties {
}
public String getPartitionKeyExtractorClass() {
return partitionKeyExtractorClass;
return this.partitionKeyExtractorClass;
}
public void setPartitionKeyExtractorClass(String partitionKeyExtractorClass) {
@@ -81,7 +85,7 @@ public class BindingProperties {
}
public String getPartitionSelectorClass() {
return partitionSelectorClass;
return this.partitionSelectorClass;
}
public void setPartitionSelectorClass(String partitionSelectorClass) {
@@ -89,7 +93,7 @@ public class BindingProperties {
}
public String getPartitionSelectorExpression() {
return partitionSelectorExpression;
return this.partitionSelectorExpression;
}
public void setPartitionSelectorExpression(String partitionSelectorExpression) {

View File

@@ -16,11 +16,15 @@
package org.springframework.cloud.stream.config;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.cloud.stream.binding.Bindable;
import org.springframework.cloud.stream.binding.ChannelBindingService;
import org.springframework.cloud.stream.endpoint.ChannelsEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.integration.scheduling.PollerMetadata;
@@ -46,4 +50,10 @@ public class ChannelBindingAutoConfiguration {
return this.poller.getPollerMetadata();
}
@Bean
@Autowired(required=false)
public ChannelsEndpoint channelsEndpoint(List<Bindable> adapters, ChannelBindingServiceProperties properties) {
return new ChannelsEndpoint(adapters, properties);
}
}

View File

@@ -0,0 +1,91 @@
/*
* Copyright 2015 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.cloud.stream.endpoint;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import org.springframework.boot.actuate.endpoint.AbstractEndpoint;
import org.springframework.cloud.stream.binding.Bindable;
import org.springframework.cloud.stream.config.BindingProperties;
import org.springframework.cloud.stream.config.ChannelBindingServiceProperties;
import org.springframework.cloud.stream.endpoint.ChannelsEndpoint.ChannelsMetaData;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonInclude.Include;
/**
* @author Dave Syer
*/
@RestController
public class ChannelsEndpoint extends AbstractEndpoint<ChannelsMetaData> {
private List<Bindable> adapters;
private ChannelBindingServiceProperties properties;
public ChannelsEndpoint(List<Bindable> adapters,
ChannelBindingServiceProperties properties) {
super("channels");
this.adapters = adapters;
this.properties = properties;
}
@Override
public ChannelsMetaData invoke() {
ChannelsMetaData map = new ChannelsMetaData();
Map<String, BindingProperties> inputs = map.getInputs();
Map<String, BindingProperties> outputs = map.getOutputs();
for (Bindable factory : this.adapters) {
Map<String, BindingProperties> bindings = this.properties.getBindings();
for (String name : factory.getInputs()) {
inputs.put(name, bindings.containsKey(name) ? bindings.get(name)
: new BindingProperties());
}
for (String name : factory.getOutputs()) {
outputs.put(name, bindings.containsKey(name) ? bindings.get(name)
: new BindingProperties());
}
}
return map;
}
@JsonInclude(value = Include.NON_DEFAULT)
public static class ChannelsMetaData {
private Map<String, BindingProperties> inputs = new LinkedHashMap<>();
private Map<String, BindingProperties> outputs = new LinkedHashMap<>();
public Map<String, BindingProperties> getInputs() {
return this.inputs;
}
public void setInputs(Map<String, BindingProperties> inputs) {
this.inputs = inputs;
}
public Map<String, BindingProperties> getOutputs() {
return this.outputs;
}
public void setOutputs(Map<String, BindingProperties> outputs) {
this.outputs = outputs;
}
}
}