Merge pull request #76 from olivergierke/hypermedia
Added hypermedia support in the form of a DiscoveredResource abstraction
This commit is contained in:
@@ -48,6 +48,16 @@
|
||||
<artifactId>spring-integration-jmx</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-hateoas</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.jayway.jsonpath</groupId>
|
||||
<artifactId>json-path</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.projectlombok</groupId>
|
||||
<artifactId>lombok</artifactId>
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
/*
|
||||
* 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.client.hypermedia;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.Collections;
|
||||
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.ConfigurationProperties;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration.CloudHypermediaProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/**
|
||||
* Registers a default {@link RemoteResourceRefresher} if at least one {@link RemoteResource} is declared in the system
|
||||
* and applies verification timings defined in the application properties.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Configuration
|
||||
@ConditionalOnBean(type = "org.springframework.cloud.client.hypermedia.RemoteResource")
|
||||
@EnableConfigurationProperties(CloudHypermediaProperties.class)
|
||||
public class CloudHypermediaAutoConfiguration {
|
||||
|
||||
@Autowired(required = false) List<RemoteResource> discoveredResources = Collections.emptyList();
|
||||
@Autowired CloudHypermediaProperties properties;
|
||||
|
||||
@Bean
|
||||
@ConditionalOnMissingBean
|
||||
public RemoteResourceRefresher discoveredResourceRefresher() {
|
||||
return new RemoteResourceRefresher(discoveredResources, properties.getRefresh().getFixedDelay(),
|
||||
properties.getRefresh().getInitialDelay());
|
||||
}
|
||||
|
||||
@Data
|
||||
@ConfigurationProperties(prefix = "spring.cloud.hypermedia")
|
||||
public static class CloudHypermediaProperties {
|
||||
|
||||
private Refresh refresh = new Refresh();
|
||||
|
||||
@Data
|
||||
public static class Refresh {
|
||||
|
||||
private int fixedDelay = 5000;
|
||||
private int initialDelay = 10000;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
/*
|
||||
* 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.client.hypermedia;
|
||||
|
||||
import lombok.Getter;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import java.net.URI;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.MediaTypes;
|
||||
import org.springframework.hateoas.client.Traverson;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* A REST resource that is defined by a service reference and a traversal operation within that service.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@Slf4j
|
||||
@RequiredArgsConstructor
|
||||
public class DiscoveredResource implements RemoteResource {
|
||||
|
||||
private final ServiceInstanceProvider provider;
|
||||
private final TraversalDefinition traversal;
|
||||
|
||||
private RestOperations restOperations = new RestTemplate();
|
||||
private @Getter Link link = null;
|
||||
|
||||
/**
|
||||
* Configures the {@link RestOperations} to use to execute the traversal and verifying HEAD calls.
|
||||
*
|
||||
* @param restOperations can be {@literal null}, resorting to a default {@link RestTemplate} in that case.
|
||||
*/
|
||||
public void setRestOperations(RestOperations restOperations) {
|
||||
this.restOperations = restOperations == null ? new RestTemplate() : restOperations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the link to the current
|
||||
*/
|
||||
public void verifyOrDiscover() {
|
||||
this.link = link == null ? discoverLink() : verify(link);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifies the given {@link Link} by issuing an HTTP HEAD request to the resource.
|
||||
*
|
||||
* @param link must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private Link verify(Link link) {
|
||||
|
||||
Assert.notNull(link, "Link must not be null!");
|
||||
|
||||
try {
|
||||
|
||||
String uri = link.expand().getHref();
|
||||
|
||||
log.debug("Verifying link pointing to {}…", uri);
|
||||
restOperations.headForHeaders(uri);
|
||||
log.debug("Successfully verified link!");
|
||||
|
||||
return link;
|
||||
|
||||
} catch (RestClientException o_O) {
|
||||
|
||||
log.debug("Verification failed, marking as outdated!");
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private Link discoverLink() {
|
||||
|
||||
try {
|
||||
|
||||
ServiceInstance service = provider.getServiceInstance();
|
||||
|
||||
if (service == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
URI uri = service.getUri();
|
||||
String serviceId = service.getServiceId();
|
||||
|
||||
log.debug("Discovered {} system at {}. Discovering resource…", serviceId, uri);
|
||||
|
||||
Traverson traverson = new Traverson(uri, MediaTypes.HAL_JSON);
|
||||
Link link = traversal.buildTraversal(traverson).asTemplatedLink();
|
||||
|
||||
log.debug("Found link pointing to {}.", link.getHref());
|
||||
|
||||
return link;
|
||||
|
||||
} catch (RuntimeException o_O) {
|
||||
|
||||
this.link = null;
|
||||
log.debug("Target system unavailable. Got: ", o_O.getMessage());
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* 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.client.hypermedia;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
|
||||
/**
|
||||
* {@link ServiceInstanceProvider} to work with a {@link DiscoveryClient} to lookup a service by name. Will pick the
|
||||
* first one returned by the configured {@link DiscoveryClient}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class DynamicServiceInstanceProvider implements ServiceInstanceProvider {
|
||||
|
||||
private final DiscoveryClient client;
|
||||
private final String serviceName;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see example.customers.integration.ServiceInstanceProvider#getServiceInstance()
|
||||
*/
|
||||
@Override
|
||||
public ServiceInstance getServiceInstance() {
|
||||
|
||||
try {
|
||||
|
||||
List<ServiceInstance> instances = client.getInstances(serviceName);
|
||||
return instances.isEmpty() ? null : instances.get(0);
|
||||
|
||||
} catch (RuntimeException o_O) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.client.hypermedia;
|
||||
|
||||
import org.springframework.hateoas.Link;
|
||||
|
||||
/**
|
||||
* A REST resource that can be discovered and can be either gone or available.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface RemoteResource {
|
||||
|
||||
/**
|
||||
* Returns the {@link Link} to the resource in case it is available or {@literal null} in case it's gone, i.e. either
|
||||
* generally unavailable or can't be discovered.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Link getLink();
|
||||
|
||||
/**
|
||||
* Discovers the the resource in case it hasn't been yet or became unavailable. In case a link has been discovered
|
||||
* previously, it is verified and either confirmed or the link is removed to indicate it's not available anymore.
|
||||
*/
|
||||
void verifyOrDiscover();
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* 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.client.hypermedia;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.scheduling.config.ContextLifecycleScheduledTaskRegistrar;
|
||||
import org.springframework.scheduling.config.IntervalTask;
|
||||
import org.springframework.scheduling.config.ScheduledTaskRegistrar;
|
||||
|
||||
/**
|
||||
* A {@link ScheduledTaskRegistrar} that verifies all {@link DiscoveredResource} instances in the system based
|
||||
* on the given timing configuration.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class RemoteResourceRefresher extends ContextLifecycleScheduledTaskRegistrar {
|
||||
|
||||
private final List<RemoteResource> discoveredResources;
|
||||
private final int fixedDelay, initialDelay;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.scheduling.config.ContextLifecycleScheduledTaskRegistrar#afterPropertiesSet()
|
||||
*/
|
||||
@Override
|
||||
public void afterPropertiesSet() {
|
||||
|
||||
for (final RemoteResource resource : discoveredResources) {
|
||||
addFixedDelayTask(new IntervalTask(new Runnable() {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
resource.verifyOrDiscover();
|
||||
}
|
||||
}, fixedDelay, initialDelay));
|
||||
}
|
||||
|
||||
super.afterPropertiesSet();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* 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.client.hypermedia;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
|
||||
/**
|
||||
* A component that will provide a {@link ServiceInstance} or can express the absence of one by returning
|
||||
* {@literal null}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface ServiceInstanceProvider {
|
||||
|
||||
/**
|
||||
* Returns the service instance or {@literal null} in case the service is currently unavailable.
|
||||
*
|
||||
* @return the service instance or {@literal null} in case the service is currently unavailable.
|
||||
*/
|
||||
ServiceInstance getServiceInstance();
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.client.hypermedia;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
|
||||
/**
|
||||
* A {@link ServiceInstanceProvider} that will always return the configured {@link ServiceInstance}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RequiredArgsConstructor
|
||||
public class StaticServiceInstanceProvider implements ServiceInstanceProvider {
|
||||
|
||||
private final ServiceInstance instance;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see example.customers.integration.ServiceInstanceProvider#getServiceInstance()
|
||||
*/
|
||||
@Override
|
||||
public ServiceInstance getServiceInstance() {
|
||||
return instance;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* 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.client.hypermedia;
|
||||
|
||||
import org.springframework.hateoas.client.Traverson;
|
||||
import org.springframework.hateoas.client.Traverson.TraversalBuilder;
|
||||
|
||||
/**
|
||||
* Callback to define the traversal to a resource.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public interface TraversalDefinition {
|
||||
|
||||
/**
|
||||
* @param traverson the Traverson instance to run the traversal on.
|
||||
* @return
|
||||
*/
|
||||
TraversalBuilder buildTraversal(Traverson traverson);
|
||||
}
|
||||
@@ -2,6 +2,7 @@
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
org.springframework.cloud.client.CommonsClientAutoConfiguration,\
|
||||
org.springframework.cloud.client.discovery.noop.NoopDiscoveryClientAutoConfiguration,\
|
||||
org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration,\
|
||||
org.springframework.cloud.client.loadbalancer.LoadBalancerAutoConfiguration,\
|
||||
org.springframework.cloud.util.UtilAutoConfiguration
|
||||
|
||||
|
||||
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright 2015-2016 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.client.hypermedia;
|
||||
|
||||
import static org.hamcrest.Matchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.cloud.client.hypermedia.CloudHypermediaAutoConfiguration.CloudHypermediaProperties;
|
||||
import org.springframework.context.ConfigurableApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.hateoas.client.Traverson;
|
||||
import org.springframework.hateoas.client.Traverson.TraversalBuilder;
|
||||
|
||||
/**
|
||||
* Integration tests for {@link CloudHypermediaAutoConfiguration}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class CloudHypermediaAutoConfigurationIntegrationTests {
|
||||
|
||||
@Test
|
||||
public void picksUpHypermediaProperties() {
|
||||
|
||||
try (ConfigurableApplicationContext context = getApplicationContext(ConfigWithRemoteResource.class)) {
|
||||
|
||||
CloudHypermediaProperties properties = context.getBean(CloudHypermediaProperties.class);
|
||||
|
||||
assertThat(properties.getRefresh().getInitialDelay(), is(50000));
|
||||
assertThat(properties.getRefresh().getFixedDelay(), is(10000));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotCreateCloudHypermediaPropertiesifNotActive() {
|
||||
|
||||
try (ConfigurableApplicationContext context = getApplicationContext(Config.class)) {
|
||||
assertThat(context.getBeanNamesForType(CloudHypermediaProperties.class), is(arrayWithSize(0)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotRegisterResourceRefresherIfNoDiscoveredResourceIsDefined() {
|
||||
|
||||
try (ConfigurableApplicationContext context = getApplicationContext(Config.class)) {
|
||||
|
||||
assertThat(context.getBeansOfType(RemoteResource.class).values(), hasSize(0));
|
||||
assertThat(context.getBeanNamesForType(RemoteResourceRefresher.class), is(arrayWithSize(0)));
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void registersResourceRefresherIfDiscoverredResourceIsDefined() {
|
||||
|
||||
try (ConfigurableApplicationContext context = getApplicationContext(ConfigWithRemoteResource.class)) {
|
||||
|
||||
assertThat(context.getBeansOfType(RemoteResource.class).values(), hasSize(1));
|
||||
assertThat(context.getBean(RemoteResourceRefresher.class), is(notNullValue()));
|
||||
}
|
||||
}
|
||||
|
||||
private static ConfigurableApplicationContext getApplicationContext(Class<?> configuration) {
|
||||
return SpringApplication.run(configuration, new String[0]);
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
static class Config {}
|
||||
|
||||
@Configuration
|
||||
@EnableAutoConfiguration
|
||||
static class ConfigWithRemoteResource {
|
||||
|
||||
@Bean
|
||||
public RemoteResource resource() {
|
||||
|
||||
ServiceInstanceProvider provider = new StaticServiceInstanceProvider(
|
||||
new DefaultServiceInstance("service", "localhost", 80, false));
|
||||
return new DiscoveredResource(provider, new TraversalDefinition() {
|
||||
|
||||
@Override
|
||||
public TraversalBuilder buildTraversal(Traverson traverson) {
|
||||
return traverson.follow("rel");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* 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.client.hypermedia;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Matchers;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.cloud.client.DefaultServiceInstance;
|
||||
import org.springframework.hateoas.Link;
|
||||
import org.springframework.hateoas.client.Traverson;
|
||||
import org.springframework.hateoas.client.Traverson.TraversalBuilder;
|
||||
import org.springframework.web.client.RestClientException;
|
||||
import org.springframework.web.client.RestOperations;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DiscoveredResourceUnitTests {
|
||||
|
||||
@Mock ServiceInstanceProvider provider;
|
||||
@Mock TraversalDefinition traversal;
|
||||
@Mock TraversalBuilder builder;
|
||||
@Mock RestOperations operations;
|
||||
|
||||
DiscoveredResource resource;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
when(traversal.buildTraversal(Matchers.any(Traverson.class))).thenReturn(builder);
|
||||
|
||||
this.resource = new DiscoveredResource(provider, traversal);
|
||||
this.resource.setRestOperations(operations);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void isUndiscoveredByDefault() {
|
||||
assertThat(resource.getLink(), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verificationTriggersDiscovery() {
|
||||
|
||||
Link link = new Link("target", "rel");
|
||||
|
||||
when(provider.getServiceInstance()).thenReturn(new DefaultServiceInstance("service", "localhost", 8080, false));
|
||||
when(builder.asTemplatedLink()).thenReturn(link);
|
||||
|
||||
resource.verifyOrDiscover();
|
||||
|
||||
assertThat(resource.getLink(), is(link));
|
||||
verify(provider, times(1)).getServiceInstance();
|
||||
verify(traversal, times(1)).buildTraversal(Matchers.any(Traverson.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void triggersVerificationOnSubsequentCall() {
|
||||
|
||||
verificationTriggersDiscovery();
|
||||
|
||||
resource.verifyOrDiscover();
|
||||
|
||||
assertThat(resource.getLink(), is(notNullValue()));
|
||||
verify(operations, times(1)).headForHeaders(anyString());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void resetsLinkOnFailedVerification() {
|
||||
|
||||
verificationTriggersDiscovery();
|
||||
|
||||
doThrow(RestClientException.class).when(operations).headForHeaders(anyString());
|
||||
resource.verifyOrDiscover();
|
||||
|
||||
assertThat(resource.getLink(), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void failedDiscoveryTraversalCausesLinkToStayNull() {
|
||||
|
||||
doThrow(RuntimeException.class).when(provider).getServiceInstance();
|
||||
|
||||
resource.verifyOrDiscover();
|
||||
|
||||
assertThat(resource.getLink(), is(nullValue()));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
* 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.client.hypermedia;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Matchers.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.cloud.client.ServiceInstance;
|
||||
import org.springframework.cloud.client.discovery.DiscoveryClient;
|
||||
|
||||
/**
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@RunWith(MockitoJUnitRunner.class)
|
||||
public class DynamicServiceInstanceProviderUnitTests {
|
||||
|
||||
@Mock DiscoveryClient client;
|
||||
|
||||
@Test
|
||||
public void returnsNoServiceInCaseNoneIsAvailable() {
|
||||
assertThat(new DynamicServiceInstanceProvider(client, "service").getServiceInstance(), is(nullValue()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void returnsFirstServiceInCaseMultipleOnesAreAvailable() {
|
||||
|
||||
ServiceInstance first = mock(ServiceInstance.class);
|
||||
ServiceInstance second = mock(ServiceInstance.class);
|
||||
|
||||
when(client.getInstances(anyString())).thenReturn(Arrays.asList(first, second));
|
||||
|
||||
assertThat(new DynamicServiceInstanceProvider(client, "service").getServiceInstance(), is(first));
|
||||
}
|
||||
}
|
||||
@@ -2,4 +2,7 @@ message: Hello scope!
|
||||
delay: 0
|
||||
debug: true
|
||||
#logging.level.org.springframework.web: DEBUG
|
||||
#logging.level.org.springframework.context.annotation: DEBUG
|
||||
#logging.level.org.springframework.context.annotation: DEBUG
|
||||
|
||||
spring.cloud.hypermedia.refresh.initial-delay=50000
|
||||
spring.cloud.hypermedia.refresh.fixed-delay=10000
|
||||
|
||||
Reference in New Issue
Block a user