Add @EndpointServlet and migrate Jolokia

Add first class support for Servlet based endpoints and rework the
Jolokia endpoint to use it.

Fixes gh-10264
This commit is contained in:
Phillip Webb
2018-01-23 17:15:25 -08:00
parent f8cdc01474
commit 017efda6ec
32 changed files with 1344 additions and 258 deletions

View File

@@ -0,0 +1,119 @@
/*
* Copyright 2012-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.boot.actuate.endpoint.web;
import java.io.IOException;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import javax.servlet.GenericServlet;
import javax.servlet.Servlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.entry;
/**
* Tests for {@link EndpointServlet}.
*
* @author Phillip Webb
*/
public class EndpointServletTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Test
public void createWhenServletClassIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Servlet must not be null");
new EndpointServlet((Class<Servlet>) null);
}
@Test
public void createWhenServletIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("Servlet must not be null");
new EndpointServlet((Servlet) null);
}
@Test
public void createWithServletClassShouldCreateServletInstance() {
EndpointServlet endpointServlet = new EndpointServlet(TestServlet.class);
assertThat(endpointServlet.getServlet()).isInstanceOf(TestServlet.class);
}
@Test
public void getServletShouldGetServlet() {
TestServlet servlet = new TestServlet();
EndpointServlet endpointServlet = new EndpointServlet(servlet);
assertThat(endpointServlet.getServlet()).isEqualTo(servlet);
}
@Test
public void withInitParameterShouldReturnNewInstance() {
EndpointServlet endpointServlet = new EndpointServlet(TestServlet.class);
assertThat(endpointServlet.withInitParameter("spring", "boot"))
.isNotSameAs(endpointServlet);
}
@Test
public void withInitParameterWhenHasExistingShouldMergeParameters() {
EndpointServlet endpointServlet = new EndpointServlet(TestServlet.class)
.withInitParameter("a", "b").withInitParameter("c", "d");
assertThat(endpointServlet.withInitParameter("a", "b1")
.withInitParameter("e", "f").getInitParameters()).containsExactly(
entry("a", "b1"), entry("c", "d"), entry("e", "f"));
}
@Test
public void withInitParametersShouldCreateNewInstance() {
EndpointServlet endpointServlet = new EndpointServlet(TestServlet.class);
assertThat(endpointServlet
.withInitParameters(Collections.singletonMap("spring", "boot")))
.isNotSameAs(endpointServlet);
}
@Test
public void withInitParametersWhenHasExistingShouldMergeParameters() {
EndpointServlet endpointServlet = new EndpointServlet(TestServlet.class)
.withInitParameter("a", "b").withInitParameter("c", "d");
Map<String, String> extra = new LinkedHashMap<>();
extra.put("a", "b1");
extra.put("e", "f");
assertThat(endpointServlet.withInitParameters(extra).getInitParameters())
.containsExactly(entry("a", "b1"), entry("c", "d"), entry("e", "f"));
}
private static class TestServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
}
}
}

View File

@@ -0,0 +1,133 @@
/*
* Copyright 2012-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.boot.actuate.endpoint.web;
import java.io.IOException;
import java.util.Collections;
import javax.servlet.GenericServlet;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRegistration.Dynamic;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import static org.assertj.core.api.Assertions.assertThat;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.BDDMockito.given;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link ServletEndpointRegistrar}.
*
* @author Phillip Webb
*/
public class ServletEndpointRegistrarTests {
@Rule
public ExpectedException thrown = ExpectedException.none();
@Mock
private ServletContext servletContext;
@Mock
private Dynamic dynamic;
@Captor
private ArgumentCaptor<Servlet> servlet;
@Before
public void setup() {
MockitoAnnotations.initMocks(this);
given(this.servletContext.addServlet(any(String.class), any(Servlet.class)))
.willReturn(this.dynamic);
}
@Test
public void createWhenServletEndpointsIsNullShouldThrowException() {
this.thrown.expect(IllegalArgumentException.class);
this.thrown.expectMessage("ServletEndpoints must not be null");
new ServletEndpointRegistrar(null, null);
}
@Test
public void onStartupShouldRegisterServlets() throws Exception {
ExposableServletEndpoint endpoint = mockEndpoint(
new EndpointServlet(TestServlet.class));
ServletEndpointRegistrar registrar = new ServletEndpointRegistrar(null,
Collections.singleton(endpoint));
registrar.onStartup(this.servletContext);
verify(this.servletContext).addServlet(eq("test-actuator-endpoint"),
this.servlet.capture());
assertThat(this.servlet.getValue()).isInstanceOf(TestServlet.class);
verify(this.dynamic).addMapping("/test/*");
}
@Test
public void onStartupWhenHasBasePathShouldIncludeBasePath() throws Exception {
ExposableServletEndpoint endpoint = mockEndpoint(
new EndpointServlet(TestServlet.class));
ServletEndpointRegistrar registrar = new ServletEndpointRegistrar("/actuator",
Collections.singleton(endpoint));
registrar.onStartup(this.servletContext);
verify(this.servletContext).addServlet(eq("test-actuator-endpoint"),
this.servlet.capture());
assertThat(this.servlet.getValue()).isInstanceOf(TestServlet.class);
verify(this.dynamic).addMapping("/actuator/test/*");
}
@Test
public void onStartupWhenHasInitParametersShouldRegisterInitParameters()
throws Exception {
ExposableServletEndpoint endpoint = mockEndpoint(
new EndpointServlet(TestServlet.class).withInitParameter("a", "b"));
ServletEndpointRegistrar registrar = new ServletEndpointRegistrar("/actuator",
Collections.singleton(endpoint));
registrar.onStartup(this.servletContext);
verify(this.dynamic).setInitParameters(Collections.singletonMap("a", "b"));
}
private ExposableServletEndpoint mockEndpoint(EndpointServlet endpointServlet) {
ExposableServletEndpoint endpoint = mock(ExposableServletEndpoint.class);
given(endpoint.getId()).willReturn("test");
given(endpoint.getEndpointServlet()).willReturn(endpointServlet);
given(endpoint.getRootPath()).willReturn("test");
return endpoint;
}
public static class TestServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
}
}
}

View File

@@ -27,6 +27,7 @@ import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
@@ -61,6 +62,7 @@ public class ControllerEndpointDiscovererTests {
assertThat(endpoint.getId()).isEqualTo("testcontroller");
assertThat(endpoint.getController())
.isInstanceOf(TestControllerEndpoint.class);
assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class);
});
}

View File

@@ -0,0 +1,212 @@
/*
* Copyright 2012-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.boot.actuate.endpoint.web.annotation;
import java.io.IOException;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import javax.servlet.GenericServlet;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.ExpectedException;
import org.springframework.boot.actuate.endpoint.ExposableEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.DiscoveredEndpoint;
import org.springframework.boot.actuate.endpoint.annotation.Endpoint;
import org.springframework.boot.actuate.endpoint.annotation.ReadOperation;
import org.springframework.boot.actuate.endpoint.web.EndpointServlet;
import org.springframework.boot.actuate.endpoint.web.ExposableServletEndpoint;
import org.springframework.boot.actuate.endpoint.web.PathMapper;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import static org.assertj.core.api.Assertions.assertThat;
/**
* Tests for {@link ServletEndpointDiscoverer}.
*
* @author Phillip Webb
*/
public class ServletEndpointDiscovererTests {
@Rule
public final ExpectedException thrown = ExpectedException.none();
@Test
public void getEndpointsWhenNoEndpointBeansShouldReturnEmptyCollection() {
load(EmptyConfiguration.class,
(discoverer) -> assertThat(discoverer.getEndpoints()).isEmpty());
}
@Test
public void getEndpointsShouldIncludeServletEndpoints() {
load(TestServletEndpoint.class, (discoverer) -> {
Collection<ExposableServletEndpoint> endpoints = discoverer.getEndpoints();
assertThat(endpoints).hasSize(1);
ExposableServletEndpoint endpoint = endpoints.iterator().next();
assertThat(endpoint.getId()).isEqualTo("testservlet");
assertThat(endpoint.getEndpointServlet()).isNotNull();
assertThat(endpoint).isInstanceOf(DiscoveredEndpoint.class);
});
}
@Test
public void getEndpointsShouldNotDiscoverRegularEndpoints() {
load(WithRegularEndpointConfiguration.class, (discoverer) -> {
Collection<ExposableServletEndpoint> endpoints = discoverer.getEndpoints();
List<String> ids = endpoints.stream().map(ExposableEndpoint::getId)
.collect(Collectors.toList());
assertThat(ids).containsOnly("testservlet");
});
}
@Test
public void getEndpointWhenEndpointHasOperationsShouldThrowException() {
load(TestServletEndpointWithOperation.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("ServletEndpoints must not declare operations");
discoverer.getEndpoints();
});
}
@Test
public void getEndpointWhenEndpointNotASupplierShouldThrowException() {
load(TestServletEndpointNotASupplier.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("must be a supplier");
discoverer.getEndpoints();
});
}
@Test
public void getEndpointWhenEndpointSuppliesWrongTypeShouldThrowException() {
load(TestServletEndpointSupplierOfWrongType.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("must supply an EndpointServlet");
discoverer.getEndpoints();
});
}
@Test
public void getEndpointWhenEndpoinSuppliesNullShouldThrowException() {
load(TestServletEndpointSupplierOfNull.class, (discoverer) -> {
this.thrown.expect(IllegalStateException.class);
this.thrown.expectMessage("must not supply null");
discoverer.getEndpoints();
});
}
private void load(Class<?> configuration,
Consumer<ServletEndpointDiscoverer> consumer) {
AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(
configuration);
try {
ServletEndpointDiscoverer discoverer = new ServletEndpointDiscoverer(context,
PathMapper.useEndpointId(), Collections.emptyList());
consumer.accept(discoverer);
}
finally {
context.close();
}
}
@Configuration
static class EmptyConfiguration {
}
@Configuration
@Import({ TestEndpoint.class, TestServletEndpoint.class })
static class WithRegularEndpointConfiguration {
}
@ServletEndpoint(id = "testservlet")
static class TestServletEndpoint implements Supplier<EndpointServlet> {
@Override
public EndpointServlet get() {
return new EndpointServlet(TestServlet.class);
}
}
@Endpoint(id = "test")
static class TestEndpoint {
}
@ServletEndpoint(id = "testservlet")
static class TestServletEndpointWithOperation implements Supplier<EndpointServlet> {
@Override
public EndpointServlet get() {
return new EndpointServlet(TestServlet.class);
}
@ReadOperation
public String read() {
return "error";
}
}
private static class TestServlet extends GenericServlet {
@Override
public void service(ServletRequest req, ServletResponse res)
throws ServletException, IOException {
}
}
@ServletEndpoint(id = "testservlet")
static class TestServletEndpointNotASupplier {
}
@ServletEndpoint(id = "testservlet")
static class TestServletEndpointSupplierOfWrongType implements Supplier<String> {
@Override
public String get() {
return "error";
}
}
@ServletEndpoint(id = "testservlet")
static class TestServletEndpointSupplierOfNull implements Supplier<EndpointServlet> {
@Override
public EndpointServlet get() {
return null;
}
}
}