From 5501adc862fdee48f6322a579f98427d4b1431ac Mon Sep 17 00:00:00 2001 From: Dave Syer Date: Wed, 1 May 2013 09:14:01 +0100 Subject: [PATCH] [bs-35] Add @ConfigurationProperties EndpointsProperties and bind to application.yml [Fixes #48245695] --- ...rtiesServiceBootstrapApplicationTests.java | 138 ++++++++++++++++++ .../service/ServiceAutoConfiguration.java | 9 ++ .../properties/EndpointsProperties.java | 83 +++++++++++ 3 files changed, 230 insertions(+) create mode 100644 spring-bootstrap-samples/spring-bootstrap-service-sample/src/test/java/org/springframework/bootstrap/sample/service/EndpointsPropertiesServiceBootstrapApplicationTests.java create mode 100644 spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/properties/EndpointsProperties.java diff --git a/spring-bootstrap-samples/spring-bootstrap-service-sample/src/test/java/org/springframework/bootstrap/sample/service/EndpointsPropertiesServiceBootstrapApplicationTests.java b/spring-bootstrap-samples/spring-bootstrap-service-sample/src/test/java/org/springframework/bootstrap/sample/service/EndpointsPropertiesServiceBootstrapApplicationTests.java new file mode 100644 index 00000000000..aabfcdc1a70 --- /dev/null +++ b/spring-bootstrap-samples/spring-bootstrap-service-sample/src/test/java/org/springframework/bootstrap/sample/service/EndpointsPropertiesServiceBootstrapApplicationTests.java @@ -0,0 +1,138 @@ +package org.springframework.bootstrap.sample.service; + +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.concurrent.Callable; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; +import java.util.concurrent.TimeUnit; + +import org.junit.After; +import org.junit.Test; +import org.springframework.bootstrap.SpringApplication; +import org.springframework.bootstrap.service.properties.EndpointsProperties; +import org.springframework.context.ConfigurableApplicationContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.http.HttpRequest; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.http.client.ClientHttpRequestExecution; +import org.springframework.http.client.ClientHttpRequestInterceptor; +import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.client.InterceptingClientHttpRequestFactory; +import org.springframework.http.client.SimpleClientHttpRequestFactory; +import org.springframework.security.crypto.codec.Base64; +import org.springframework.web.client.DefaultResponseErrorHandler; +import org.springframework.web.client.RestTemplate; + +import static org.junit.Assert.assertEquals; + +/** + * Integration tests for endpoints configuration. + * + * @author Dave Syer + * + */ +public class EndpointsPropertiesServiceBootstrapApplicationTests { + + private ConfigurableApplicationContext context; + + private void start(final Class configuration, final String... args) + throws Exception { + Future future = Executors + .newSingleThreadExecutor().submit( + new Callable() { + @Override + public ConfigurableApplicationContext call() throws Exception { + return (ConfigurableApplicationContext) SpringApplication + .run(configuration, args); + } + }); + this.context = future.get(10, TimeUnit.SECONDS); + } + + @After + public void stop() { + if (this.context != null) { + this.context.close(); + } + } + + @Test + public void testCustomErrorPath() throws Exception { + start(ServiceBootstrapApplication.class, "--endpoints.error.path=/oops"); + testError(); + } + + @Test + public void testCustomEndpointsProperties() throws Exception { + start(CustomServiceBootstrapApplication.class, "--endpoints.error.path=/oops"); + testError(); + } + + private void testError() { + @SuppressWarnings("rawtypes") + ResponseEntity entity = getRestTemplate("user", "password").getForEntity( + "http://localhost:8080/oops", Map.class); + assertEquals(HttpStatus.OK, entity.getStatusCode()); + @SuppressWarnings("unchecked") + Map body = entity.getBody(); + assertEquals("None", body.get("error")); + assertEquals(999, body.get("status")); + } + + @Configuration + @Import(ServiceBootstrapApplication.class) + public static class CustomServiceBootstrapApplication { + @Bean + CustomEndpointsProperties endpointsProperties() { + return new CustomEndpointsProperties(); + } + } + + public static class CustomEndpointsProperties extends EndpointsProperties { + @Override + public Endpoint getError() { + return new Endpoint("/oops"); + } + } + + private RestTemplate getRestTemplate(final String username, final String password) { + + List interceptors = new ArrayList(); + + if (username != null) { + + interceptors.add(new ClientHttpRequestInterceptor() { + + @Override + public ClientHttpResponse intercept(HttpRequest request, byte[] body, + ClientHttpRequestExecution execution) throws IOException { + request.getHeaders().add( + "Authorization", + "Basic " + + new String(Base64 + .encode((username + ":" + password) + .getBytes()))); + return execution.execute(request, body); + } + }); + } + + RestTemplate restTemplate = new RestTemplate( + new InterceptingClientHttpRequestFactory( + new SimpleClientHttpRequestFactory(), interceptors)); + restTemplate.setErrorHandler(new DefaultResponseErrorHandler() { + @Override + public void handleError(ClientHttpResponse response) throws IOException { + } + }); + return restTemplate; + + } + +} diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/ServiceAutoConfiguration.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/ServiceAutoConfiguration.java index 41fabf0d60f..495348256ba 100644 --- a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/ServiceAutoConfiguration.java +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/autoconfigure/service/ServiceAutoConfiguration.java @@ -18,10 +18,13 @@ package org.springframework.bootstrap.autoconfigure.service; import java.util.List; +import org.springframework.bootstrap.context.annotation.ConditionalOnMissingBean; import org.springframework.bootstrap.context.annotation.EnableAutoConfiguration; import org.springframework.bootstrap.service.annotation.EnableConfigurationProperties; +import org.springframework.bootstrap.service.properties.EndpointsProperties; import org.springframework.bootstrap.service.properties.ManagementServerProperties; import org.springframework.bootstrap.service.properties.ServerProperties; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Import; import org.springframework.http.converter.HttpMessageConverter; @@ -62,6 +65,12 @@ public class ServiceAutoConfiguration extends WebMvcConfigurationSupport { @EnableConfigurationProperties({ ServerProperties.class, ManagementServerProperties.class }) public static class ServerPropertiesConfiguration { + + @Bean + @ConditionalOnMissingBean(EndpointsProperties.class) + public EndpointsProperties endpointsProperties() { + return new EndpointsProperties(); + } } } diff --git a/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/properties/EndpointsProperties.java b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/properties/EndpointsProperties.java new file mode 100644 index 00000000000..2eab907dab2 --- /dev/null +++ b/spring-bootstrap-service/src/main/java/org/springframework/bootstrap/service/properties/EndpointsProperties.java @@ -0,0 +1,83 @@ +/* + * Copyright 2012-2013 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.bootstrap.service.properties; + +import javax.validation.constraints.NotNull; + +import org.springframework.bootstrap.context.annotation.ConfigurationProperties; + +/** + * Externalized configuration for endpoints (e.g. paths) + * + * @author Dave Syer + * + */ +@ConfigurationProperties(name = "endpoints", ignoreUnknownFields = false) +public class EndpointsProperties { + + private Endpoint varz = new Endpoint("/varz"); + + private Endpoint healthz = new Endpoint("/healthz"); + + private Endpoint error = new Endpoint("/error"); + + private Endpoint shutdown = new Endpoint("/shutdown"); + + private Endpoint trace = new Endpoint("/trace"); + + public Endpoint getVarz() { + return this.varz; + } + + public Endpoint getHealthz() { + return this.healthz; + } + + public Endpoint getError() { + return this.error; + } + + public Endpoint getShutdown() { + return this.shutdown; + } + + public Endpoint getTrace() { + return this.trace; + } + + public static class Endpoint { + + @NotNull + private String path; + + public Endpoint() { + } + + public Endpoint(String path) { + super(); + this.path = path; + } + + public String getPath() { + return this.path; + } + + public void setPath(String path) { + this.path = path; + } + } + +}