Make discovery work for channels
Here's how it works. User adds a bean of type ChannelLocator qualified as @InputChannel (or @OutputChannel) and it gets used instead of the default. The discovery version needs a service id. What it does is invert the naming convention in channel binding - "input" locally requires "output" remotely, "input.topic:foo" requires "output.topic:foo" remotely etc.
This commit is contained in:
@@ -1,73 +0,0 @@
|
||||
/*
|
||||
* 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.bus.runner.adapter;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DiscoveryChannelLocator implements ChannelLocator {
|
||||
|
||||
private DiscoveryClient discovery;
|
||||
|
||||
private RestTemplate restTemplate = new RestTemplate();
|
||||
|
||||
private String serviceId;
|
||||
|
||||
public DiscoveryChannelLocator(DiscoveryClient discovery, String serviceId) {
|
||||
this.discovery = discovery;
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String locate(String name) {
|
||||
List<ServiceInstance> instances = discovery.getInstances(serviceId);
|
||||
if (instances==null || instances.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
URI uri = pickUrl(instances);
|
||||
try {
|
||||
ChannelsMetadata channels = restTemplate.getForObject(uri, ChannelsMetadata.class);
|
||||
for (OutputChannelSpec spec : channels.getOutputChannels()) {
|
||||
if (name.equals(spec.getLocalName())) {
|
||||
return spec.getName();
|
||||
}
|
||||
}
|
||||
for (InputChannelSpec spec : channels.getInputChannels()) {
|
||||
if (name.equals(spec.getLocalName())) {
|
||||
return spec.getName();
|
||||
}
|
||||
}
|
||||
} catch (Exception e) {
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
private URI pickUrl(List<ServiceInstance> instances) {
|
||||
return instances.get(new Random().nextInt(instances.size())).getUri();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
/*
|
||||
* 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.bus.runner.adapter;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DiscoveryClientChannelLocator implements ChannelLocator {
|
||||
|
||||
private Log logger = LogFactory.getLog(DiscoveryClientChannelLocator.class);
|
||||
|
||||
private DiscoveryClient discovery;
|
||||
|
||||
private RestOperations restTemplate = new RestTemplate();
|
||||
|
||||
private String serviceId;
|
||||
|
||||
public DiscoveryClientChannelLocator(DiscoveryClient discovery, String serviceId) {
|
||||
this.discovery = discovery;
|
||||
this.serviceId = serviceId;
|
||||
}
|
||||
|
||||
public void setRestTemplate(RestOperations restTemplate) {
|
||||
this.restTemplate = restTemplate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String locate(String name) {
|
||||
List<ServiceInstance> instances = this.discovery.getInstances(this.serviceId);
|
||||
if (instances == null || instances.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
URI uri = pickUrl(instances);
|
||||
try {
|
||||
ChannelsMetadata channels = this.restTemplate.getForObject(uri,
|
||||
ChannelsMetadata.class);
|
||||
Collection<? extends InputChannelSpec> specs = Collections.emptySet();
|
||||
if (name.startsWith("input")) {
|
||||
name = name.replace("input", "output");
|
||||
specs = channels.getOutputChannels();
|
||||
}
|
||||
else if (name.startsWith("output")) {
|
||||
name = name.replace("output", "input");
|
||||
specs = channels.getInputChannels();
|
||||
}
|
||||
for (InputChannelSpec spec : specs) {
|
||||
if (name.equals(spec.getLocalName())) {
|
||||
this.logger.debug("Discovered channel for '" + this.serviceId + "' ("
|
||||
+ name + "=" + spec.getName() + ")");
|
||||
return spec.getName();
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (Exception e) {
|
||||
this.logger.warn("Could not discover channel for '" + this.serviceId + "' ("
|
||||
+ e.getClass() + ": " + e.getMessage() + ")");
|
||||
return null;
|
||||
}
|
||||
this.logger.warn("No channel disccovered for '" + this.serviceId + "' (" + name + ")");
|
||||
return null;
|
||||
}
|
||||
|
||||
private URI pickUrl(List<ServiceInstance> instances) {
|
||||
return UriComponentsBuilder
|
||||
.fromUri(instances.get(new Random().nextInt(instances.size())).getUri())
|
||||
.path("channels").build().toUri();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.bus.runner.adapter;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
/**
|
||||
* Qualifier annotation for a bean relating input channels.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Qualifier
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
|
||||
ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface InputChannel {
|
||||
|
||||
}
|
||||
@@ -25,6 +25,10 @@ public class InputChannelSpec {
|
||||
private String name;
|
||||
private String localName;
|
||||
|
||||
protected InputChannelSpec() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public InputChannelSpec(String localName) {
|
||||
this.localName = localName;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright 2012-2015 the original author or authors.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package org.springframework.bus.runner.adapter;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
|
||||
/**
|
||||
* Qualifier annotation for a bean relating input channels.
|
||||
*
|
||||
* @author Dave Syer
|
||||
*/
|
||||
@Qualifier
|
||||
@Target({ ElementType.FIELD, ElementType.METHOD, ElementType.PARAMETER, ElementType.TYPE,
|
||||
ElementType.ANNOTATION_TYPE })
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Inherited
|
||||
@Documented
|
||||
public @interface OutputChannel {
|
||||
|
||||
}
|
||||
@@ -26,6 +26,10 @@ public class OutputChannelSpec extends InputChannelSpec {
|
||||
private boolean tapped = false;
|
||||
private String tapChannelName;
|
||||
|
||||
protected OutputChannelSpec() {
|
||||
this(null);
|
||||
}
|
||||
|
||||
public OutputChannelSpec(String localName) {
|
||||
super(localName);
|
||||
}
|
||||
|
||||
@@ -29,8 +29,11 @@ import org.springframework.beans.factory.BeanFactoryUtils;
|
||||
import org.springframework.beans.factory.ListableBeanFactory;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.bus.runner.adapter.ChannelLocator;
|
||||
import org.springframework.bus.runner.adapter.InputChannel;
|
||||
import org.springframework.bus.runner.adapter.InputChannelSpec;
|
||||
import org.springframework.bus.runner.adapter.MessageBusAdapter;
|
||||
import org.springframework.bus.runner.adapter.OutputChannel;
|
||||
import org.springframework.bus.runner.adapter.OutputChannelSpec;
|
||||
import org.springframework.bus.runner.endpoint.ChannelsEndpoint;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
@@ -56,12 +59,26 @@ public class MessageBusAdapterConfiguration {
|
||||
@Autowired
|
||||
private ListableBeanFactory beanFactory;
|
||||
|
||||
@Autowired(required=false)
|
||||
@InputChannel
|
||||
private ChannelLocator inputChannelLocator;
|
||||
|
||||
@Autowired(required=false)
|
||||
@OutputChannel
|
||||
private ChannelLocator outputChannelLocator;
|
||||
|
||||
@Bean
|
||||
public MessageBusAdapter messageBusAdapter(MessageBusProperties module,
|
||||
MessageBus messageBus) {
|
||||
MessageBusAdapter adapter = new MessageBusAdapter(module, messageBus);
|
||||
adapter.setOutputChannels(getOutputChannels());
|
||||
adapter.setInputChannels(getInputChannels());
|
||||
if (this.inputChannelLocator!=null) {
|
||||
adapter.setInputChannelLocator(this.inputChannelLocator);
|
||||
}
|
||||
if (this.outputChannelLocator!=null) {
|
||||
adapter.setOutputChannelLocator(this.outputChannelLocator);
|
||||
}
|
||||
return adapter;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* 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.bus.runner.adapter;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.mockito.Matchers.any;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.mockito.Mockito;
|
||||
import org.springframework.bus.runner.config.MessageBusProperties;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
/**
|
||||
* @author Dave Syer
|
||||
*
|
||||
*/
|
||||
public class DiscoveryClientChannelLocatorTests {
|
||||
|
||||
private DiscoveryClient client = Mockito.mock(DiscoveryClient.class);
|
||||
|
||||
private RestOperations restTemplate = Mockito.mock(RestOperations.class);
|
||||
|
||||
private DiscoveryClientChannelLocator locator = new DiscoveryClientChannelLocator(
|
||||
this.client, "service");
|
||||
|
||||
private ChannelsMetadata metadata = new ChannelsMetadata();
|
||||
|
||||
@Before
|
||||
public void init() {
|
||||
this.locator.setRestTemplate(this.restTemplate);
|
||||
this.metadata.setModule(new MessageBusProperties());
|
||||
this.metadata.setInputChannels(new HashSet<InputChannelSpec>());
|
||||
this.metadata.setOutputChannels(new HashSet<OutputChannelSpec>());
|
||||
Mockito.when(
|
||||
this.restTemplate.getForObject(Mockito.any(URI.class), anyChannels()))
|
||||
.thenReturn(this.metadata);
|
||||
Mockito.when(this.client.getInstances(Mockito.anyString())).thenReturn(
|
||||
Arrays.asList(new DefaultServiceInstance("service", "example.com", 888,
|
||||
false)));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void locateInputFromOutput() {
|
||||
OutputChannelSpec output = new OutputChannelSpec("output");
|
||||
output.setName("foo.0");
|
||||
this.metadata.getOutputChannels().add(output);
|
||||
assertEquals("foo.0", this.locator.locate("input"));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void locateOutputFromInput() {
|
||||
InputChannelSpec input = new InputChannelSpec("input");
|
||||
input.setName("foo.0");
|
||||
this.metadata.getInputChannels().add(input);
|
||||
assertEquals("foo.0", this.locator.locate("output"));
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked" })
|
||||
private Class<ChannelsMetadata> anyChannels() {
|
||||
return any(Class.class);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user