Drop support for Jersey until jersey-spring6 is available
Closes gh-28808
This commit is contained in:
@@ -1,340 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.jersey;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.Principal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import javax.ws.rs.HttpMethod;
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
import javax.ws.rs.core.MultivaluedMap;
|
||||
import javax.ws.rs.core.Response;
|
||||
import javax.ws.rs.core.Response.Status;
|
||||
|
||||
import org.glassfish.jersey.process.Inflector;
|
||||
import org.glassfish.jersey.server.ContainerRequest;
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
import org.glassfish.jersey.server.model.Resource.Builder;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.InvalidEndpointRequestException;
|
||||
import org.springframework.boot.actuate.endpoint.InvocationContext;
|
||||
import org.springframework.boot.actuate.endpoint.OperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.ProducibleOperationArgumentResolver;
|
||||
import org.springframework.boot.actuate.endpoint.SecurityContext;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.ExposableWebEndpoint;
|
||||
import org.springframework.boot.actuate.endpoint.web.Link;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebEndpointResponse;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.util.AntPathMatcher;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* A factory for creating Jersey {@link Resource Resources} for {@link WebOperation web
|
||||
* endpoint operations}.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @author Phillip Webb
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class JerseyEndpointResourceFactory {
|
||||
|
||||
/**
|
||||
* Creates {@link Resource Resources} for the operations of the given
|
||||
* {@code webEndpoints}.
|
||||
* @param endpointMapping the base mapping for all endpoints
|
||||
* @param endpoints the web endpoints
|
||||
* @param endpointMediaTypes media types consumed and produced by the endpoints
|
||||
* @param linksResolver resolver for determining links to available endpoints
|
||||
* @param shouldRegisterLinks should register links
|
||||
* @return the resources for the operations
|
||||
*/
|
||||
public Collection<Resource> createEndpointResources(EndpointMapping endpointMapping,
|
||||
Collection<ExposableWebEndpoint> endpoints, EndpointMediaTypes endpointMediaTypes,
|
||||
EndpointLinksResolver linksResolver, boolean shouldRegisterLinks) {
|
||||
List<Resource> resources = new ArrayList<>();
|
||||
endpoints.stream().flatMap((endpoint) -> endpoint.getOperations().stream())
|
||||
.map((operation) -> createResource(endpointMapping, operation)).forEach(resources::add);
|
||||
if (shouldRegisterLinks) {
|
||||
Resource resource = createEndpointLinksResource(endpointMapping.getPath(), endpointMediaTypes,
|
||||
linksResolver);
|
||||
resources.add(resource);
|
||||
}
|
||||
return resources;
|
||||
}
|
||||
|
||||
protected Resource createResource(EndpointMapping endpointMapping, WebOperation operation) {
|
||||
WebOperationRequestPredicate requestPredicate = operation.getRequestPredicate();
|
||||
String path = requestPredicate.getPath();
|
||||
String matchAllRemainingPathSegmentsVariable = requestPredicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
path = path.replace("{*" + matchAllRemainingPathSegmentsVariable + "}",
|
||||
"{" + matchAllRemainingPathSegmentsVariable + ": .*}");
|
||||
}
|
||||
return getResource(endpointMapping, operation, requestPredicate, path, null, null);
|
||||
}
|
||||
|
||||
protected Resource getResource(EndpointMapping endpointMapping, WebOperation operation,
|
||||
WebOperationRequestPredicate requestPredicate, String path, WebServerNamespace serverNamespace,
|
||||
JerseyRemainingPathSegmentProvider remainingPathSegmentProvider) {
|
||||
Builder resourceBuilder = Resource.builder().path(endpointMapping.getPath())
|
||||
.path(endpointMapping.createSubPath(path));
|
||||
resourceBuilder.addMethod(requestPredicate.getHttpMethod().name())
|
||||
.consumes(StringUtils.toStringArray(requestPredicate.getConsumes()))
|
||||
.produces(StringUtils.toStringArray(requestPredicate.getProduces()))
|
||||
.handledBy(new OperationInflector(operation, !requestPredicate.getConsumes().isEmpty(), serverNamespace,
|
||||
remainingPathSegmentProvider));
|
||||
return resourceBuilder.build();
|
||||
}
|
||||
|
||||
private Resource createEndpointLinksResource(String endpointPath, EndpointMediaTypes endpointMediaTypes,
|
||||
EndpointLinksResolver linksResolver) {
|
||||
Builder resourceBuilder = Resource.builder().path(endpointPath);
|
||||
resourceBuilder.addMethod("GET").produces(StringUtils.toStringArray(endpointMediaTypes.getProduced()))
|
||||
.handledBy(new EndpointLinksInflector(linksResolver));
|
||||
return resourceBuilder.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Inflector} to invoke the {@link WebOperation}.
|
||||
*/
|
||||
private static final class OperationInflector implements Inflector<ContainerRequestContext, Object> {
|
||||
|
||||
private static final String PATH_SEPARATOR = AntPathMatcher.DEFAULT_PATH_SEPARATOR;
|
||||
|
||||
private static final List<Function<Object, Object>> BODY_CONVERTERS;
|
||||
|
||||
static {
|
||||
List<Function<Object, Object>> converters = new ArrayList<>();
|
||||
converters.add(new ResourceBodyConverter());
|
||||
if (ClassUtils.isPresent("reactor.core.publisher.Mono", OperationInflector.class.getClassLoader())) {
|
||||
converters.add(new MonoBodyConverter());
|
||||
}
|
||||
BODY_CONVERTERS = Collections.unmodifiableList(converters);
|
||||
}
|
||||
|
||||
private final WebOperation operation;
|
||||
|
||||
private final boolean readBody;
|
||||
|
||||
private final WebServerNamespace serverNamespace;
|
||||
|
||||
private final JerseyRemainingPathSegmentProvider remainingPathSegmentProvider;
|
||||
|
||||
private OperationInflector(WebOperation operation, boolean readBody, WebServerNamespace serverNamespace,
|
||||
JerseyRemainingPathSegmentProvider remainingPathSegments) {
|
||||
this.operation = operation;
|
||||
this.readBody = readBody;
|
||||
this.serverNamespace = serverNamespace;
|
||||
this.remainingPathSegmentProvider = remainingPathSegments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response apply(ContainerRequestContext data) {
|
||||
Map<String, Object> arguments = new HashMap<>();
|
||||
if (this.readBody) {
|
||||
arguments.putAll(extractBodyArguments(data));
|
||||
}
|
||||
arguments.putAll(extractPathParameters(data));
|
||||
arguments.putAll(extractQueryParameters(data));
|
||||
try {
|
||||
JerseySecurityContext securityContext = new JerseySecurityContext(data.getSecurityContext());
|
||||
OperationArgumentResolver serverNamespaceArgumentResolver = OperationArgumentResolver
|
||||
.of(WebServerNamespace.class, () -> this.serverNamespace);
|
||||
InvocationContext invocationContext = new InvocationContext(securityContext, arguments,
|
||||
serverNamespaceArgumentResolver,
|
||||
new ProducibleOperationArgumentResolver(() -> data.getHeaders().get("Accept")));
|
||||
Object response = this.operation.invoke(invocationContext);
|
||||
return convertToJaxRsResponse(response, data.getRequest().getMethod());
|
||||
}
|
||||
catch (InvalidEndpointRequestException ex) {
|
||||
return Response.status(Status.BAD_REQUEST).build();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Map<String, Object> extractBodyArguments(ContainerRequestContext data) {
|
||||
Map<String, Object> entity = ((ContainerRequest) data).readEntity(Map.class);
|
||||
return (entity != null) ? entity : Collections.emptyMap();
|
||||
}
|
||||
|
||||
private Map<String, Object> extractPathParameters(ContainerRequestContext requestContext) {
|
||||
Map<String, Object> pathParameters = extract(requestContext.getUriInfo().getPathParameters());
|
||||
String matchAllRemainingPathSegmentsVariable = this.operation.getRequestPredicate()
|
||||
.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
String remainingPathSegments = getRemainingPathSegments(requestContext, pathParameters,
|
||||
matchAllRemainingPathSegmentsVariable);
|
||||
pathParameters.put(matchAllRemainingPathSegmentsVariable, tokenizePathSegments(remainingPathSegments));
|
||||
}
|
||||
return pathParameters;
|
||||
}
|
||||
|
||||
private String getRemainingPathSegments(ContainerRequestContext requestContext,
|
||||
Map<String, Object> pathParameters, String matchAllRemainingPathSegmentsVariable) {
|
||||
if (this.remainingPathSegmentProvider != null) {
|
||||
return this.remainingPathSegmentProvider.get(requestContext, matchAllRemainingPathSegmentsVariable);
|
||||
}
|
||||
return (String) pathParameters.get(matchAllRemainingPathSegmentsVariable);
|
||||
}
|
||||
|
||||
private String[] tokenizePathSegments(String path) {
|
||||
String[] segments = StringUtils.tokenizeToStringArray(path, PATH_SEPARATOR, false, true);
|
||||
for (int i = 0; i < segments.length; i++) {
|
||||
if (segments[i].contains("%")) {
|
||||
segments[i] = StringUtils.uriDecode(segments[i], StandardCharsets.UTF_8);
|
||||
}
|
||||
}
|
||||
return segments;
|
||||
}
|
||||
|
||||
private Map<String, Object> extractQueryParameters(ContainerRequestContext requestContext) {
|
||||
return extract(requestContext.getUriInfo().getQueryParameters());
|
||||
}
|
||||
|
||||
private Map<String, Object> extract(MultivaluedMap<String, String> multivaluedMap) {
|
||||
Map<String, Object> result = new HashMap<>();
|
||||
multivaluedMap.forEach((name, values) -> {
|
||||
if (!CollectionUtils.isEmpty(values)) {
|
||||
result.put(name, (values.size() != 1) ? values : values.get(0));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private Response convertToJaxRsResponse(Object response, String httpMethod) {
|
||||
if (response == null) {
|
||||
boolean isGet = HttpMethod.GET.equals(httpMethod);
|
||||
Status status = isGet ? Status.NOT_FOUND : Status.NO_CONTENT;
|
||||
return Response.status(status).build();
|
||||
}
|
||||
try {
|
||||
if (!(response instanceof WebEndpointResponse)) {
|
||||
return Response.status(Status.OK).entity(convertIfNecessary(response)).build();
|
||||
}
|
||||
WebEndpointResponse<?> webEndpointResponse = (WebEndpointResponse<?>) response;
|
||||
return Response.status(webEndpointResponse.getStatus())
|
||||
.header("Content-Type", webEndpointResponse.getContentType())
|
||||
.entity(convertIfNecessary(webEndpointResponse.getBody())).build();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
return Response.status(Status.INTERNAL_SERVER_ERROR).build();
|
||||
}
|
||||
}
|
||||
|
||||
private Object convertIfNecessary(Object body) throws IOException {
|
||||
for (Function<Object, Object> converter : BODY_CONVERTERS) {
|
||||
body = converter.apply(body);
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Body converter from {@link org.springframework.core.io.Resource} to
|
||||
* {@link InputStream}.
|
||||
*/
|
||||
private static final class ResourceBodyConverter implements Function<Object, Object> {
|
||||
|
||||
@Override
|
||||
public Object apply(Object body) {
|
||||
if (body instanceof org.springframework.core.io.Resource) {
|
||||
try {
|
||||
return ((org.springframework.core.io.Resource) body).getInputStream();
|
||||
}
|
||||
catch (IOException ex) {
|
||||
throw new IllegalStateException();
|
||||
}
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Body converter from {@link Mono} to {@link Mono#block()}.
|
||||
*/
|
||||
private static final class MonoBodyConverter implements Function<Object, Object> {
|
||||
|
||||
@Override
|
||||
public Object apply(Object body) {
|
||||
if (body instanceof Mono) {
|
||||
return ((Mono<?>) body).block();
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Inflector} to for endpoint links.
|
||||
*/
|
||||
private static final class EndpointLinksInflector implements Inflector<ContainerRequestContext, Response> {
|
||||
|
||||
private final EndpointLinksResolver linksResolver;
|
||||
|
||||
private EndpointLinksInflector(EndpointLinksResolver linksResolver) {
|
||||
this.linksResolver = linksResolver;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Response apply(ContainerRequestContext request) {
|
||||
Map<String, Link> links = this.linksResolver
|
||||
.resolveLinks(request.getUriInfo().getAbsolutePath().toString());
|
||||
return Response.ok(Collections.singletonMap("_links", links)).build();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class JerseySecurityContext implements SecurityContext {
|
||||
|
||||
private final javax.ws.rs.core.SecurityContext securityContext;
|
||||
|
||||
private JerseySecurityContext(javax.ws.rs.core.SecurityContext securityContext) {
|
||||
this.securityContext = securityContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Principal getPrincipal() {
|
||||
return this.securityContext.getUserPrincipal();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isUserInRole(String role) {
|
||||
return this.securityContext.isUserInRole(role);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,66 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.jersey;
|
||||
|
||||
import java.util.Set;
|
||||
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperation;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebOperationRequestPredicate;
|
||||
import org.springframework.boot.actuate.endpoint.web.WebServerNamespace;
|
||||
import org.springframework.boot.actuate.health.AdditionalHealthEndpointPath;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroup;
|
||||
import org.springframework.boot.actuate.health.HealthEndpointGroups;
|
||||
|
||||
/**
|
||||
* A factory for creating Jersey {@link Resource Resources} for health groups with
|
||||
* additional path.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
* @since 2.6.0
|
||||
*/
|
||||
public class JerseyHealthEndpointAdditionalPathResourceFactory extends JerseyEndpointResourceFactory {
|
||||
|
||||
private final Set<HealthEndpointGroup> groups;
|
||||
|
||||
private final WebServerNamespace serverNamespace;
|
||||
|
||||
public JerseyHealthEndpointAdditionalPathResourceFactory(WebServerNamespace serverNamespace,
|
||||
HealthEndpointGroups groups) {
|
||||
this.serverNamespace = serverNamespace;
|
||||
this.groups = groups.getAllWithAdditionalPath(serverNamespace);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Resource createResource(EndpointMapping endpointMapping, WebOperation operation) {
|
||||
WebOperationRequestPredicate requestPredicate = operation.getRequestPredicate();
|
||||
String matchAllRemainingPathSegmentsVariable = requestPredicate.getMatchAllRemainingPathSegmentsVariable();
|
||||
if (matchAllRemainingPathSegmentsVariable != null) {
|
||||
for (HealthEndpointGroup group : this.groups) {
|
||||
AdditionalHealthEndpointPath additionalPath = group.getAdditionalPath();
|
||||
if (additionalPath != null) {
|
||||
return getResource(endpointMapping, operation, requestPredicate, additionalPath.getValue(),
|
||||
this.serverNamespace, (data, pathSegmentsVariable) -> data.getUriInfo().getPath());
|
||||
}
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2021 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
|
||||
*
|
||||
* https://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.jersey;
|
||||
|
||||
import javax.ws.rs.container.ContainerRequestContext;
|
||||
|
||||
/**
|
||||
* Strategy interface used to provide the remaining path segments for a Jersey actuator
|
||||
* endpoint.
|
||||
*
|
||||
* @author Madhura Bhave
|
||||
*/
|
||||
interface JerseyRemainingPathSegmentProvider {
|
||||
|
||||
String get(ContainerRequestContext requestContext, String matchAllRemainingPathSegmentsVariable);
|
||||
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Jersey support for actuator endpoints.
|
||||
*/
|
||||
package org.springframework.boot.actuate.endpoint.web.jersey;
|
||||
@@ -1,164 +0,0 @@
|
||||
/*
|
||||
* Copyright 2012-2019 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
|
||||
*
|
||||
* https://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.jersey;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
|
||||
import javax.servlet.Filter;
|
||||
import javax.servlet.FilterChain;
|
||||
import javax.servlet.ServletException;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import javax.ws.rs.ext.ContextResolver;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.glassfish.jersey.jackson.JacksonFeature;
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
import org.glassfish.jersey.servlet.ServletContainer;
|
||||
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.AbstractWebEndpointIntegrationTests;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
|
||||
import org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory;
|
||||
import org.springframework.boot.web.servlet.ServletRegistrationBean;
|
||||
import org.springframework.boot.web.servlet.context.AnnotationConfigServletWebServerApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||
import org.springframework.security.core.authority.SimpleGrantedAuthority;
|
||||
import org.springframework.security.core.context.SecurityContext;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestWrapper;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.filter.OncePerRequestFilter;
|
||||
|
||||
/**
|
||||
* Integration tests for web endpoints exposed using Jersey.
|
||||
*
|
||||
* @author Andy Wilkinson
|
||||
* @see JerseyEndpointResourceFactory
|
||||
*/
|
||||
public class JerseyWebEndpointIntegrationTests
|
||||
extends AbstractWebEndpointIntegrationTests<AnnotationConfigServletWebServerApplicationContext> {
|
||||
|
||||
public JerseyWebEndpointIntegrationTests() {
|
||||
super(JerseyWebEndpointIntegrationTests::createApplicationContext,
|
||||
JerseyWebEndpointIntegrationTests::applyAuthenticatedConfiguration);
|
||||
}
|
||||
|
||||
private static AnnotationConfigServletWebServerApplicationContext createApplicationContext() {
|
||||
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
context.register(JerseyConfiguration.class);
|
||||
return context;
|
||||
}
|
||||
|
||||
private static void applyAuthenticatedConfiguration(AnnotationConfigServletWebServerApplicationContext context) {
|
||||
context.register(AuthenticatedConfiguration.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected int getPort(AnnotationConfigServletWebServerApplicationContext context) {
|
||||
return context.getWebServer().getPort();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void validateErrorBody(WebTestClient.BodyContentSpec body, HttpStatus status, String path,
|
||||
String message) {
|
||||
// Jersey doesn't support the general error page handling
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class JerseyConfiguration {
|
||||
|
||||
@Bean
|
||||
TomcatServletWebServerFactory tomcat() {
|
||||
return new TomcatServletWebServerFactory(0);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ServletRegistrationBean<ServletContainer> servletContainer(ResourceConfig resourceConfig) {
|
||||
return new ServletRegistrationBean<>(new ServletContainer(resourceConfig), "/*");
|
||||
}
|
||||
|
||||
@Bean
|
||||
ResourceConfig resourceConfig(Environment environment, WebEndpointDiscoverer endpointDiscoverer,
|
||||
EndpointMediaTypes endpointMediaTypes) {
|
||||
ResourceConfig resourceConfig = new ResourceConfig();
|
||||
String endpointPath = environment.getProperty("endpointPath");
|
||||
Collection<Resource> resources = new JerseyEndpointResourceFactory().createEndpointResources(
|
||||
new EndpointMapping(endpointPath), endpointDiscoverer.getEndpoints(), endpointMediaTypes,
|
||||
new EndpointLinksResolver(endpointDiscoverer.getEndpoints()), StringUtils.hasText(endpointPath));
|
||||
resourceConfig.registerResources(new HashSet<>(resources));
|
||||
resourceConfig.register(JacksonFeature.class);
|
||||
resourceConfig.register(new ObjectMapperContextResolver(new ObjectMapper()), ContextResolver.class);
|
||||
return resourceConfig;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
static class AuthenticatedConfiguration {
|
||||
|
||||
@Bean
|
||||
Filter securityFilter() {
|
||||
return new OncePerRequestFilter() {
|
||||
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
|
||||
FilterChain filterChain) throws ServletException, IOException {
|
||||
SecurityContext context = SecurityContextHolder.createEmptyContext();
|
||||
context.setAuthentication(new UsernamePasswordAuthenticationToken("Alice", "secret",
|
||||
Arrays.asList(new SimpleGrantedAuthority("ROLE_ACTUATOR"))));
|
||||
SecurityContextHolder.setContext(context);
|
||||
try {
|
||||
filterChain.doFilter(new SecurityContextHolderAwareRequestWrapper(request, "ROLE_"), response);
|
||||
}
|
||||
finally {
|
||||
SecurityContextHolder.clearContext();
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static final class ObjectMapperContextResolver implements ContextResolver<ObjectMapper> {
|
||||
|
||||
private final ObjectMapper objectMapper;
|
||||
|
||||
private ObjectMapperContextResolver(ObjectMapper objectMapper) {
|
||||
this.objectMapper = objectMapper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public ObjectMapper getContext(Class<?> type) {
|
||||
return this.objectMapper;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@@ -17,16 +17,12 @@
|
||||
package org.springframework.boot.actuate.endpoint.web.test;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.glassfish.jersey.server.ResourceConfig;
|
||||
import org.glassfish.jersey.server.model.Resource;
|
||||
import org.junit.jupiter.api.extension.AfterEachCallback;
|
||||
import org.junit.jupiter.api.extension.BeforeEachCallback;
|
||||
import org.junit.jupiter.api.extension.Extension;
|
||||
@@ -41,14 +37,11 @@ import org.springframework.boot.actuate.endpoint.web.EndpointLinksResolver;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.EndpointMediaTypes;
|
||||
import org.springframework.boot.actuate.endpoint.web.annotation.WebEndpointDiscoverer;
|
||||
import org.springframework.boot.actuate.endpoint.web.jersey.JerseyEndpointResourceFactory;
|
||||
import org.springframework.boot.actuate.endpoint.web.reactive.WebFluxEndpointHandlerMapping;
|
||||
import org.springframework.boot.actuate.endpoint.web.servlet.WebMvcEndpointHandlerMapping;
|
||||
import org.springframework.boot.autoconfigure.ImportAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.http.HttpMessageConvertersAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jackson.JacksonAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jersey.JerseyAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.jersey.ResourceConfigCustomizer;
|
||||
import org.springframework.boot.autoconfigure.web.reactive.WebFluxAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.DispatcherServletAutoConfiguration;
|
||||
import org.springframework.boot.autoconfigure.web.servlet.WebMvcAutoConfiguration;
|
||||
@@ -91,22 +84,12 @@ class WebEndpointTestInvocationContextProvider implements TestTemplateInvocation
|
||||
public Stream<TestTemplateInvocationContext> provideTestTemplateInvocationContexts(
|
||||
ExtensionContext extensionContext) {
|
||||
return Stream.of(
|
||||
new WebEndpointsInvocationContext("Jersey",
|
||||
WebEndpointTestInvocationContextProvider::createJerseyContext),
|
||||
new WebEndpointsInvocationContext("WebMvc",
|
||||
WebEndpointTestInvocationContextProvider::createWebMvcContext),
|
||||
new WebEndpointsInvocationContext("WebFlux",
|
||||
WebEndpointTestInvocationContextProvider::createWebFluxContext));
|
||||
}
|
||||
|
||||
private static ConfigurableApplicationContext createJerseyContext(List<Class<?>> classes) {
|
||||
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
classes.add(JerseyEndpointConfiguration.class);
|
||||
context.register(ClassUtils.toClassArray(classes));
|
||||
context.refresh();
|
||||
return context;
|
||||
}
|
||||
|
||||
private static ConfigurableApplicationContext createWebMvcContext(List<Class<?>> classes) {
|
||||
AnnotationConfigServletWebServerApplicationContext context = new AnnotationConfigServletWebServerApplicationContext();
|
||||
classes.add(WebMvcEndpointConfiguration.class);
|
||||
@@ -208,44 +191,6 @@ class WebEndpointTestInvocationContextProvider implements TestTemplateInvocation
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ImportAutoConfiguration({ JacksonAutoConfiguration.class, JerseyAutoConfiguration.class })
|
||||
static class JerseyEndpointConfiguration {
|
||||
|
||||
private final ApplicationContext applicationContext;
|
||||
|
||||
JerseyEndpointConfiguration(ApplicationContext applicationContext) {
|
||||
this.applicationContext = applicationContext;
|
||||
}
|
||||
|
||||
@Bean
|
||||
TomcatServletWebServerFactory tomcat() {
|
||||
return new TomcatServletWebServerFactory(0);
|
||||
}
|
||||
|
||||
@Bean
|
||||
ResourceConfig resourceConfig() {
|
||||
return new ResourceConfig();
|
||||
}
|
||||
|
||||
@Bean
|
||||
ResourceConfigCustomizer webEndpointRegistrar() {
|
||||
return this::customize;
|
||||
}
|
||||
|
||||
private void customize(ResourceConfig config) {
|
||||
EndpointMediaTypes endpointMediaTypes = EndpointMediaTypes.DEFAULT;
|
||||
WebEndpointDiscoverer discoverer = new WebEndpointDiscoverer(this.applicationContext,
|
||||
new ConversionServiceParameterValueMapper(), endpointMediaTypes, null, Collections.emptyList(),
|
||||
Collections.emptyList());
|
||||
Collection<Resource> resources = new JerseyEndpointResourceFactory().createEndpointResources(
|
||||
new EndpointMapping("/actuator"), discoverer.getEndpoints(), endpointMediaTypes,
|
||||
new EndpointLinksResolver(discoverer.getEndpoints()), true);
|
||||
config.registerResources(new HashSet<>(resources));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ImportAutoConfiguration({ JacksonAutoConfiguration.class, WebFluxAutoConfiguration.class })
|
||||
static class WebFluxEndpointConfiguration implements ApplicationListener<WebServerInitializedEvent> {
|
||||
|
||||
Reference in New Issue
Block a user