Add CloudFoundryDiscoveryMvcEndpoint
Update Cloud Foundry support with a discovery endpoint that shows what endpoints are available. See gh-7108
This commit is contained in:
		
							parent
							
								
									7afb161fcf
								
							
						
					
					
						commit
						ab81d993e6
					
				|  | @ -79,7 +79,7 @@ public class EndpointWebMvcManagementContextConfiguration { | |||
| 	@Bean | ||||
| 	@ConditionalOnMissingBean | ||||
| 	public EndpointHandlerMapping endpointHandlerMapping() { | ||||
| 		Set<? extends MvcEndpoint> endpoints = mvcEndpoints().getEndpoints(); | ||||
| 		Set<MvcEndpoint> endpoints = mvcEndpoints().getEndpoints(); | ||||
| 		CorsConfiguration corsConfiguration = getCorsConfiguration(this.corsProperties); | ||||
| 		EndpointHandlerMapping mapping = new EndpointHandlerMapping(endpoints, | ||||
| 				corsConfiguration); | ||||
|  |  | |||
|  | @ -406,8 +406,7 @@ public class ManagementWebSecurityAutoConfiguration { | |||
| 			EndpointHandlerMapping endpointHandlerMapping = null; | ||||
| 			ApplicationContext context = this.contextResolver.getApplicationContext(); | ||||
| 			if (context.getBeanNamesForType(EndpointHandlerMapping.class).length > 0) { | ||||
| 				endpointHandlerMapping = context.getBean("endpointHandlerMapping", | ||||
| 						EndpointHandlerMapping.class); | ||||
| 				endpointHandlerMapping = context.getBean(EndpointHandlerMapping.class); | ||||
| 			} | ||||
| 			if (endpointHandlerMapping == null) { | ||||
| 				// Maybe there are actually no endpoints (e.g. management.port=-1) | ||||
|  |  | |||
|  | @ -0,0 +1,86 @@ | |||
| /* | ||||
|  * Copyright 2012-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.boot.actuate.cloudfoundry; | ||||
| 
 | ||||
| import java.util.Collections; | ||||
| import java.util.LinkedHashMap; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import javax.servlet.http.HttpServletRequest; | ||||
| 
 | ||||
| import org.springframework.boot.actuate.endpoint.mvc.AbstractMvcEndpoint; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; | ||||
| import org.springframework.http.MediaType; | ||||
| import org.springframework.web.bind.annotation.RequestMapping; | ||||
| import org.springframework.web.bind.annotation.ResponseBody; | ||||
| 
 | ||||
| /** | ||||
|  * {@link MvcEndpoint} to expose HAL-formatted JSON for Cloud Foundry specific actuator | ||||
|  * endpoints. | ||||
|  * | ||||
|  * @author Madhura Bhave | ||||
|  */ | ||||
| class CloudFoundryDiscoveryMvcEndpoint extends AbstractMvcEndpoint { | ||||
| 
 | ||||
| 	private final Set<NamedMvcEndpoint> endpoints; | ||||
| 
 | ||||
| 	CloudFoundryDiscoveryMvcEndpoint(Set<NamedMvcEndpoint> endpoints) { | ||||
| 		super("", false); | ||||
| 		this.endpoints = endpoints; | ||||
| 	} | ||||
| 
 | ||||
| 	@RequestMapping(produces = MediaType.APPLICATION_JSON_VALUE) | ||||
| 	@ResponseBody | ||||
| 	public Map<String, Map<String, Link>> links(HttpServletRequest request) { | ||||
| 		Map<String, Link> links = new LinkedHashMap<String, Link>(); | ||||
| 		String url = request.getRequestURL().toString(); | ||||
| 		if (url.endsWith("/")) { | ||||
| 			url = url.substring(0, url.length() - 1); | ||||
| 		} | ||||
| 		links.put("self", Link.withHref(url)); | ||||
| 		for (NamedMvcEndpoint endpoint : this.endpoints) { | ||||
| 			links.put(endpoint.getName(), Link.withHref(url + "/" + endpoint.getName())); | ||||
| 		} | ||||
| 		return Collections.singletonMap("_links", links); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Details for a link in the HAL response. | ||||
| 	 */ | ||||
| 	static class Link { | ||||
| 
 | ||||
| 		private String href; | ||||
| 
 | ||||
| 		public String getHref() { | ||||
| 			return this.href; | ||||
| 		} | ||||
| 
 | ||||
| 		public void setHref(String href) { | ||||
| 			this.href = href; | ||||
| 		} | ||||
| 
 | ||||
| 		static Link withHref(Object href) { | ||||
| 			Link link = new Link(); | ||||
| 			link.setHref(href.toString()); | ||||
| 			return link; | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -19,6 +19,7 @@ package org.springframework.boot.actuate.cloudfoundry; | |||
| import java.util.ArrayList; | ||||
| import java.util.Arrays; | ||||
| import java.util.Collection; | ||||
| import java.util.Iterator; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| 
 | ||||
|  | @ -26,7 +27,8 @@ import javax.servlet.http.HttpServletRequest; | |||
| import javax.servlet.http.HttpServletResponse; | ||||
| 
 | ||||
| import org.springframework.boot.actuate.endpoint.Endpoint; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.EndpointHandlerMapping; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.AbstractEndpointHandlerMapping; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.HalJsonMvcEndpoint; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.MvcEndpoint; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; | ||||
| import org.springframework.web.cors.CorsConfiguration; | ||||
|  | @ -40,7 +42,8 @@ import org.springframework.web.servlet.handler.HandlerInterceptorAdapter; | |||
|  * | ||||
|  * @author Madhura Bhave | ||||
|  */ | ||||
| class CloudFoundryEndpointHandlerMapping extends EndpointHandlerMapping { | ||||
| class CloudFoundryEndpointHandlerMapping | ||||
| 		extends AbstractEndpointHandlerMapping<NamedMvcEndpoint> { | ||||
| 
 | ||||
| 	CloudFoundryEndpointHandlerMapping(Collection<? extends NamedMvcEndpoint> endpoints) { | ||||
| 		super(endpoints); | ||||
|  | @ -51,6 +54,23 @@ class CloudFoundryEndpointHandlerMapping extends EndpointHandlerMapping { | |||
| 		super(endpoints, corsConfiguration); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected void postProcessEndpoints(Set<NamedMvcEndpoint> endpoints) { | ||||
| 		super.postProcessEndpoints(endpoints); | ||||
| 		Iterator<NamedMvcEndpoint> iterator = endpoints.iterator(); | ||||
| 		while (iterator.hasNext()) { | ||||
| 			if (iterator.next() instanceof HalJsonMvcEndpoint) { | ||||
| 				iterator.remove(); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void afterPropertiesSet() { | ||||
| 		super.afterPropertiesSet(); | ||||
| 		detectHandlerMethods(new CloudFoundryDiscoveryMvcEndpoint(getEndpoints())); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected String getPath(MvcEndpoint endpoint) { | ||||
| 		if (endpoint instanceof NamedMvcEndpoint) { | ||||
|  |  | |||
|  | @ -0,0 +1,235 @@ | |||
| /* | ||||
|  * 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.boot.actuate.endpoint.mvc; | ||||
| 
 | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import org.springframework.boot.actuate.endpoint.Endpoint; | ||||
| import org.springframework.context.ApplicationContext; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.ObjectUtils; | ||||
| import org.springframework.util.StringUtils; | ||||
| import org.springframework.web.cors.CorsConfiguration; | ||||
| import org.springframework.web.servlet.HandlerMapping; | ||||
| import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; | ||||
| import org.springframework.web.servlet.mvc.method.RequestMappingInfo; | ||||
| import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; | ||||
| 
 | ||||
| /** | ||||
|  * {@link HandlerMapping} to map {@link Endpoint}s to URLs via {@link Endpoint#getId()}. | ||||
|  * The semantics of {@code @RequestMapping} should be identical to a normal | ||||
|  * {@code @Controller}, but the endpoints should not be annotated as {@code @Controller} | ||||
|  * (otherwise they will be mapped by the normal MVC mechanisms). | ||||
|  * <p> | ||||
|  * One of the aims of the mapping is to support endpoints that work as HTTP endpoints but | ||||
|  * can still provide useful service interfaces when there is no HTTP server (and no Spring | ||||
|  * MVC on the classpath). Note that any endpoints having method signatures will break in a | ||||
|  * non-servlet environment. | ||||
|  * | ||||
|  * @param <E> The endpoint type | ||||
|  * @author Phillip Webb | ||||
|  * @author Christian Dupuis | ||||
|  * @author Dave Syer | ||||
|  * @author Madhura Bhave | ||||
|  */ | ||||
| public class AbstractEndpointHandlerMapping<E extends MvcEndpoint> | ||||
| 		extends RequestMappingHandlerMapping { | ||||
| 
 | ||||
| 	private final Set<E> endpoints; | ||||
| 
 | ||||
| 	private final CorsConfiguration corsConfiguration; | ||||
| 
 | ||||
| 	private String prefix = ""; | ||||
| 
 | ||||
| 	private boolean disabled = false; | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a new {@link AbstractEndpointHandlerMapping} instance. All {@link Endpoint}s | ||||
| 	 * will be detected from the {@link ApplicationContext}. The endpoints will not accept | ||||
| 	 * CORS requests. | ||||
| 	 * @param endpoints the endpoints | ||||
| 	 */ | ||||
| 	public AbstractEndpointHandlerMapping(Collection<? extends E> endpoints) { | ||||
| 		this(endpoints, null); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a new {@link AbstractEndpointHandlerMapping} instance. All {@link Endpoint}s | ||||
| 	 * will be detected from the {@link ApplicationContext}. The endpoints will accepts | ||||
| 	 * CORS requests based on the given {@code corsConfiguration}. | ||||
| 	 * @param endpoints the endpoints | ||||
| 	 * @param corsConfiguration the CORS configuration for the endpoints | ||||
| 	 * @since 1.3.0 | ||||
| 	 */ | ||||
| 	public AbstractEndpointHandlerMapping(Collection<? extends E> endpoints, | ||||
| 			CorsConfiguration corsConfiguration) { | ||||
| 		this.endpoints = new HashSet<E>(endpoints); | ||||
| 		postProcessEndpoints(this.endpoints); | ||||
| 		this.corsConfiguration = corsConfiguration; | ||||
| 		// By default the static resource handler mapping is LOWEST_PRECEDENCE - 1 | ||||
| 		// and the RequestMappingHandlerMapping is 0 (we ideally want to be before both) | ||||
| 		setOrder(-100); | ||||
| 		setUseSuffixPatternMatch(false); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Post process the endpoint setting before they are used. Subclasses can add or | ||||
| 	 * modify the endpoints as necessary. | ||||
| 	 * @param endpoints the endpoints to post process | ||||
| 	 */ | ||||
| 	protected void postProcessEndpoints(Set<E> endpoints) { | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void afterPropertiesSet() { | ||||
| 		super.afterPropertiesSet(); | ||||
| 		if (!this.disabled) { | ||||
| 			for (MvcEndpoint endpoint : this.endpoints) { | ||||
| 				detectHandlerMethods(endpoint); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Since all handler beans are passed into the constructor there is no need to detect | ||||
| 	 * anything here. | ||||
| 	 */ | ||||
| 	@Override | ||||
| 	protected boolean isHandler(Class<?> beanType) { | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	@Deprecated | ||||
| 	protected void registerHandlerMethod(Object handler, Method method, | ||||
| 			RequestMappingInfo mapping) { | ||||
| 		if (mapping == null) { | ||||
| 			return; | ||||
| 		} | ||||
| 		String[] patterns = getPatterns(handler, mapping); | ||||
| 		if (!ObjectUtils.isEmpty(patterns)) { | ||||
| 			super.registerHandlerMethod(handler, method, | ||||
| 					withNewPatterns(mapping, patterns)); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private String[] getPatterns(Object handler, RequestMappingInfo mapping) { | ||||
| 		if (handler instanceof String) { | ||||
| 			handler = getApplicationContext().getBean((String) handler); | ||||
| 		} | ||||
| 		Assert.state(handler instanceof MvcEndpoint, "Only MvcEndpoints are supported"); | ||||
| 		String path = getPath((MvcEndpoint) handler); | ||||
| 		return (path == null ? null : getEndpointPatterns(path, mapping)); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Return the path that should be used to map the given {@link MvcEndpoint}. | ||||
| 	 * @param endpoint the endpoint to map | ||||
| 	 * @return the path to use for the endpoint or {@code null} if no mapping is required | ||||
| 	 */ | ||||
| 	protected String getPath(MvcEndpoint endpoint) { | ||||
| 		return endpoint.getPath(); | ||||
| 	} | ||||
| 
 | ||||
| 	private String[] getEndpointPatterns(String path, RequestMappingInfo mapping) { | ||||
| 		String patternPrefix = StringUtils.hasText(this.prefix) ? this.prefix + path | ||||
| 				: path; | ||||
| 		Set<String> defaultPatterns = mapping.getPatternsCondition().getPatterns(); | ||||
| 		if (defaultPatterns.isEmpty()) { | ||||
| 			return new String[] { patternPrefix, patternPrefix + ".json" }; | ||||
| 		} | ||||
| 		List<String> patterns = new ArrayList<String>(defaultPatterns); | ||||
| 		for (int i = 0; i < patterns.size(); i++) { | ||||
| 			patterns.set(i, patternPrefix + patterns.get(i)); | ||||
| 		} | ||||
| 		return patterns.toArray(new String[patterns.size()]); | ||||
| 	} | ||||
| 
 | ||||
| 	private RequestMappingInfo withNewPatterns(RequestMappingInfo mapping, | ||||
| 			String[] patternStrings) { | ||||
| 		PatternsRequestCondition patterns = new PatternsRequestCondition(patternStrings, | ||||
| 				null, null, useSuffixPatternMatch(), useTrailingSlashMatch(), null); | ||||
| 		return new RequestMappingInfo(patterns, mapping.getMethodsCondition(), | ||||
| 				mapping.getParamsCondition(), mapping.getHeadersCondition(), | ||||
| 				mapping.getConsumesCondition(), mapping.getProducesCondition(), | ||||
| 				mapping.getCustomCondition()); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Set the prefix used in mappings. | ||||
| 	 * @param prefix the prefix | ||||
| 	 */ | ||||
| 	public void setPrefix(String prefix) { | ||||
| 		Assert.isTrue("".equals(prefix) || StringUtils.startsWithIgnoreCase(prefix, "/"), | ||||
| 				"prefix must start with '/'"); | ||||
| 		this.prefix = prefix; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Get the prefix used in mappings. | ||||
| 	 * @return the prefix | ||||
| 	 */ | ||||
| 	public String getPrefix() { | ||||
| 		return this.prefix; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Get the path of the endpoint. | ||||
| 	 * @param endpoint the endpoint | ||||
| 	 * @return the path used in mappings | ||||
| 	 */ | ||||
| 	public String getPath(String endpoint) { | ||||
| 		return this.prefix + endpoint; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Sets if this mapping is disabled. | ||||
| 	 * @param disabled if the mapping is disabled | ||||
| 	 */ | ||||
| 	public void setDisabled(boolean disabled) { | ||||
| 		this.disabled = disabled; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Returns if this mapping is disabled. | ||||
| 	 * @return if the mapping is disabled | ||||
| 	 */ | ||||
| 	public boolean isDisabled() { | ||||
| 		return this.disabled; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Return the endpoints. | ||||
| 	 * @return the endpoints | ||||
| 	 */ | ||||
| 	public Set<E> getEndpoints() { | ||||
| 		return Collections.unmodifiableSet(this.endpoints); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected CorsConfiguration initCorsConfiguration(Object handler, Method method, | ||||
| 			RequestMappingInfo mappingInfo) { | ||||
| 		return this.corsConfiguration; | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -16,24 +16,12 @@ | |||
| 
 | ||||
| package org.springframework.boot.actuate.endpoint.mvc; | ||||
| 
 | ||||
| import java.lang.reflect.Method; | ||||
| import java.util.ArrayList; | ||||
| import java.util.Collection; | ||||
| import java.util.Collections; | ||||
| import java.util.HashSet; | ||||
| import java.util.List; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import org.springframework.boot.actuate.endpoint.Endpoint; | ||||
| import org.springframework.context.ApplicationContext; | ||||
| import org.springframework.util.Assert; | ||||
| import org.springframework.util.ObjectUtils; | ||||
| import org.springframework.util.StringUtils; | ||||
| import org.springframework.web.cors.CorsConfiguration; | ||||
| import org.springframework.web.servlet.HandlerMapping; | ||||
| import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition; | ||||
| import org.springframework.web.servlet.mvc.method.RequestMappingInfo; | ||||
| import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; | ||||
| 
 | ||||
| /** | ||||
|  * {@link HandlerMapping} to map {@link Endpoint}s to URLs via {@link Endpoint#getId()}. | ||||
|  | @ -50,15 +38,7 @@ import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandl | |||
|  * @author Christian Dupuis | ||||
|  * @author Dave Syer | ||||
|  */ | ||||
| public class EndpointHandlerMapping extends RequestMappingHandlerMapping { | ||||
| 
 | ||||
| 	private final Set<MvcEndpoint> endpoints; | ||||
| 
 | ||||
| 	private final CorsConfiguration corsConfiguration; | ||||
| 
 | ||||
| 	private String prefix = ""; | ||||
| 
 | ||||
| 	private boolean disabled = false; | ||||
| public class EndpointHandlerMapping extends AbstractEndpointHandlerMapping<MvcEndpoint> { | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Create a new {@link EndpointHandlerMapping} instance. All {@link Endpoint}s will be | ||||
|  | @ -67,7 +47,7 @@ public class EndpointHandlerMapping extends RequestMappingHandlerMapping { | |||
| 	 * @param endpoints the endpoints | ||||
| 	 */ | ||||
| 	public EndpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints) { | ||||
| 		this(endpoints, null); | ||||
| 		super(endpoints); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
|  | @ -80,144 +60,7 @@ public class EndpointHandlerMapping extends RequestMappingHandlerMapping { | |||
| 	 */ | ||||
| 	public EndpointHandlerMapping(Collection<? extends MvcEndpoint> endpoints, | ||||
| 			CorsConfiguration corsConfiguration) { | ||||
| 		this.endpoints = new HashSet<MvcEndpoint>(endpoints); | ||||
| 		this.corsConfiguration = corsConfiguration; | ||||
| 		// By default the static resource handler mapping is LOWEST_PRECEDENCE - 1 | ||||
| 		// and the RequestMappingHandlerMapping is 0 (we ideally want to be before both) | ||||
| 		setOrder(-100); | ||||
| 		setUseSuffixPatternMatch(false); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	public void afterPropertiesSet() { | ||||
| 		super.afterPropertiesSet(); | ||||
| 		if (!this.disabled) { | ||||
| 			for (MvcEndpoint endpoint : this.endpoints) { | ||||
| 				detectHandlerMethods(endpoint); | ||||
| 			} | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Since all handler beans are passed into the constructor there is no need to detect | ||||
| 	 * anything here. | ||||
| 	 */ | ||||
| 	@Override | ||||
| 	protected boolean isHandler(Class<?> beanType) { | ||||
| 		return false; | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	@Deprecated | ||||
| 	protected void registerHandlerMethod(Object handler, Method method, | ||||
| 			RequestMappingInfo mapping) { | ||||
| 		if (mapping == null) { | ||||
| 			return; | ||||
| 		} | ||||
| 		String[] patterns = getPatterns(handler, mapping); | ||||
| 		if (!ObjectUtils.isEmpty(patterns)) { | ||||
| 			super.registerHandlerMethod(handler, method, | ||||
| 					withNewPatterns(mapping, patterns)); | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| 	private String[] getPatterns(Object handler, RequestMappingInfo mapping) { | ||||
| 		if (handler instanceof String) { | ||||
| 			handler = getApplicationContext().getBean((String) handler); | ||||
| 		} | ||||
| 		Assert.state(handler instanceof MvcEndpoint, "Only MvcEndpoints are supported"); | ||||
| 		String path = getPath((MvcEndpoint) handler); | ||||
| 		return (path == null ? null : getEndpointPatterns(path, mapping)); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Return the path that should be used to map the given {@link MvcEndpoint}. | ||||
| 	 * @param endpoint the endpoint to map | ||||
| 	 * @return the path to use for the endpoint or {@code null} if no mapping is required | ||||
| 	 */ | ||||
| 	protected String getPath(MvcEndpoint endpoint) { | ||||
| 		return endpoint.getPath(); | ||||
| 	} | ||||
| 
 | ||||
| 	private String[] getEndpointPatterns(String path, RequestMappingInfo mapping) { | ||||
| 		String patternPrefix = StringUtils.hasText(this.prefix) ? this.prefix + path | ||||
| 				: path; | ||||
| 		Set<String> defaultPatterns = mapping.getPatternsCondition().getPatterns(); | ||||
| 		if (defaultPatterns.isEmpty()) { | ||||
| 			return new String[] { patternPrefix, patternPrefix + ".json" }; | ||||
| 		} | ||||
| 		List<String> patterns = new ArrayList<String>(defaultPatterns); | ||||
| 		for (int i = 0; i < patterns.size(); i++) { | ||||
| 			patterns.set(i, patternPrefix + patterns.get(i)); | ||||
| 		} | ||||
| 		return patterns.toArray(new String[patterns.size()]); | ||||
| 	} | ||||
| 
 | ||||
| 	private RequestMappingInfo withNewPatterns(RequestMappingInfo mapping, | ||||
| 			String[] patternStrings) { | ||||
| 		PatternsRequestCondition patterns = new PatternsRequestCondition(patternStrings, | ||||
| 				null, null, useSuffixPatternMatch(), useTrailingSlashMatch(), null); | ||||
| 		return new RequestMappingInfo(patterns, mapping.getMethodsCondition(), | ||||
| 				mapping.getParamsCondition(), mapping.getHeadersCondition(), | ||||
| 				mapping.getConsumesCondition(), mapping.getProducesCondition(), | ||||
| 				mapping.getCustomCondition()); | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Set the prefix used in mappings. | ||||
| 	 * @param prefix the prefix | ||||
| 	 */ | ||||
| 	public void setPrefix(String prefix) { | ||||
| 		Assert.isTrue("".equals(prefix) || StringUtils.startsWithIgnoreCase(prefix, "/"), | ||||
| 				"prefix must start with '/'"); | ||||
| 		this.prefix = prefix; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Get the prefix used in mappings. | ||||
| 	 * @return the prefix | ||||
| 	 */ | ||||
| 	public String getPrefix() { | ||||
| 		return this.prefix; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Get the path of the endpoint. | ||||
| 	 * @param endpoint the endpoint | ||||
| 	 * @return the path used in mappings | ||||
| 	 */ | ||||
| 	public String getPath(String endpoint) { | ||||
| 		return this.prefix + endpoint; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Sets if this mapping is disabled. | ||||
| 	 * @param disabled if the mapping is disabled | ||||
| 	 */ | ||||
| 	public void setDisabled(boolean disabled) { | ||||
| 		this.disabled = disabled; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Returns if this mapping is disabled. | ||||
| 	 * @return if the mapping is disabled | ||||
| 	 */ | ||||
| 	public boolean isDisabled() { | ||||
| 		return this.disabled; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Return the endpoints. | ||||
| 	 * @return the endpoints | ||||
| 	 */ | ||||
| 	public Set<MvcEndpoint> getEndpoints() { | ||||
| 		return Collections.unmodifiableSet(this.endpoints); | ||||
| 	} | ||||
| 
 | ||||
| 	@Override | ||||
| 	protected CorsConfiguration initCorsConfiguration(Object handler, Method method, | ||||
| 			RequestMappingInfo mappingInfo) { | ||||
| 		return this.corsConfiguration; | ||||
| 		super(endpoints, corsConfiguration); | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
|  | @ -89,19 +89,20 @@ public class MvcEndpoints implements ApplicationContextAware, InitializingBean { | |||
| 		return types; | ||||
| 	} | ||||
| 
 | ||||
| 	public Set<? extends MvcEndpoint> getEndpoints() { | ||||
| 	public Set<MvcEndpoint> getEndpoints() { | ||||
| 		return this.endpoints; | ||||
| 	} | ||||
| 
 | ||||
| 	/** | ||||
| 	 * Return the endpoints of the specified type. | ||||
| 	 * @param <E>  the Class type of the endpoints to be returned | ||||
| 	 * @param <E> the Class type of the endpoints to be returned | ||||
| 	 * @param type the endpoint type | ||||
| 	 * @return the endpoints | ||||
| 	 */ | ||||
| 	@SuppressWarnings("unchecked") | ||||
| 	public <E extends MvcEndpoint> Set<E> getEndpoints(Class<E> type) { | ||||
| 		Set<E> result = new HashSet<E>(this.endpoints.size()); | ||||
| 		for (MvcEndpoint candidate: this.endpoints) { | ||||
| 		for (MvcEndpoint candidate : this.endpoints) { | ||||
| 			if (type.isInstance(candidate)) { | ||||
| 				result.add((E) candidate); | ||||
| 			} | ||||
|  |  | |||
|  | @ -0,0 +1,100 @@ | |||
| /* | ||||
|  * Copyright 2012-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.boot.actuate.cloudfoundry; | ||||
| 
 | ||||
| import java.util.LinkedHashSet; | ||||
| import java.util.Map; | ||||
| import java.util.Set; | ||||
| 
 | ||||
| import org.junit.Before; | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import org.springframework.boot.actuate.endpoint.AbstractEndpoint; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; | ||||
| import org.springframework.mock.web.MockHttpServletRequest; | ||||
| 
 | ||||
| import static org.assertj.core.api.Assertions.assertThat; | ||||
| 
 | ||||
| /** | ||||
|  * Tests for {@link CloudFoundryDiscoveryMvcEndpoint}. | ||||
|  * | ||||
|  * @author Madhura Bhave | ||||
|  */ | ||||
| public class CloudFoundryDiscoveryMvcEndpointTests { | ||||
| 
 | ||||
| 	private CloudFoundryDiscoveryMvcEndpoint endpoint; | ||||
| 
 | ||||
| 	@Before | ||||
| 	public void setup() { | ||||
| 		NamedMvcEndpoint testMvcEndpoint = new TestMvcEndpoint(new TestEndpoint("a")); | ||||
| 		Set<NamedMvcEndpoint> endpoints = new LinkedHashSet<NamedMvcEndpoint>(); | ||||
| 		endpoints.add(testMvcEndpoint); | ||||
| 		this.endpoint = new CloudFoundryDiscoveryMvcEndpoint(endpoints); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void cloudfoundryHalJsonEndpointHasEmptyPath() throws Exception { | ||||
| 		assertThat(this.endpoint.getPath()).isEmpty(); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void linksResponseWhenRequestUriHasNoTrailingSlash() throws Exception { | ||||
| 		MockHttpServletRequest request = new MockHttpServletRequest("GET", | ||||
| 				"/cloudfoundryapplication"); | ||||
| 		Map<String, CloudFoundryDiscoveryMvcEndpoint.Link> links = this.endpoint | ||||
| 				.links(request).get("_links"); | ||||
| 		assertThat(links.get("self").getHref()) | ||||
| 				.isEqualTo("http://localhost/cloudfoundryapplication"); | ||||
| 		assertThat(links.get("a").getHref()) | ||||
| 				.isEqualTo("http://localhost/cloudfoundryapplication/a"); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void linksResponseWhenRequestUriHasTrailingSlash() throws Exception { | ||||
| 		MockHttpServletRequest request = new MockHttpServletRequest("GET", | ||||
| 				"/cloudfoundryapplication/"); | ||||
| 		Map<String, CloudFoundryDiscoveryMvcEndpoint.Link> links = this.endpoint | ||||
| 				.links(request).get("_links"); | ||||
| 		assertThat(links.get("self").getHref()) | ||||
| 				.isEqualTo("http://localhost/cloudfoundryapplication"); | ||||
| 		assertThat(links.get("a").getHref()) | ||||
| 				.isEqualTo("http://localhost/cloudfoundryapplication/a"); | ||||
| 	} | ||||
| 
 | ||||
| 	private static class TestEndpoint extends AbstractEndpoint<Object> { | ||||
| 
 | ||||
| 		TestEndpoint(String id) { | ||||
| 			super(id); | ||||
| 		} | ||||
| 
 | ||||
| 		@Override | ||||
| 		public Object invoke() { | ||||
| 			return null; | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static class TestMvcEndpoint extends EndpointMvcAdapter { | ||||
| 
 | ||||
| 		TestMvcEndpoint(TestEndpoint delegate) { | ||||
| 			super(delegate); | ||||
| 		} | ||||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  | @ -17,12 +17,18 @@ | |||
| package org.springframework.boot.actuate.cloudfoundry; | ||||
| 
 | ||||
| import java.util.Arrays; | ||||
| import java.util.Collections; | ||||
| 
 | ||||
| import org.junit.Test; | ||||
| 
 | ||||
| import org.springframework.boot.actuate.endpoint.AbstractEndpoint; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.EndpointMvcAdapter; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.HalJsonMvcEndpoint; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.ManagementServletContext; | ||||
| import org.springframework.boot.actuate.endpoint.mvc.NamedMvcEndpoint; | ||||
| import org.springframework.context.support.StaticApplicationContext; | ||||
| import org.springframework.mock.web.MockHttpServletRequest; | ||||
| import org.springframework.web.method.HandlerMethod; | ||||
| import org.springframework.web.servlet.HandlerExecutionChain; | ||||
| import org.springframework.web.servlet.HandlerInterceptor; | ||||
| 
 | ||||
|  | @ -57,6 +63,28 @@ public class CloudFoundryEndpointHandlerMappingTests { | |||
| 		assertThat(handlerMapping.getPath(testMvcEndpoint)).isEqualTo("/a"); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void doesNotRegisterHalJsonMvcEndpoint() throws Exception { | ||||
| 		CloudFoundryEndpointHandlerMapping handlerMapping = new CloudFoundryEndpointHandlerMapping( | ||||
| 				Collections.<NamedMvcEndpoint>singleton(new TestHalJsonMvcEndpoint())); | ||||
| 		assertThat(handlerMapping.getEndpoints()).hasSize(0); | ||||
| 	} | ||||
| 
 | ||||
| 	@Test | ||||
| 	public void registersCloudFoundryDiscoveryEndpoint() throws Exception { | ||||
| 		StaticApplicationContext context = new StaticApplicationContext(); | ||||
| 		CloudFoundryEndpointHandlerMapping handlerMapping = new CloudFoundryEndpointHandlerMapping( | ||||
| 				Collections.<NamedMvcEndpoint>emptyList()); | ||||
| 		handlerMapping.setPrefix("/test"); | ||||
| 		handlerMapping.setApplicationContext(context); | ||||
| 		handlerMapping.afterPropertiesSet(); | ||||
| 		HandlerExecutionChain handler = handlerMapping | ||||
| 				.getHandler(new MockHttpServletRequest("GET", "/test")); | ||||
| 		HandlerMethod handlerMethod = (HandlerMethod) handler.getHandler(); | ||||
| 		assertThat(handlerMethod.getBean()) | ||||
| 				.isInstanceOf(CloudFoundryDiscoveryMvcEndpoint.class); | ||||
| 	} | ||||
| 
 | ||||
| 	private static class TestEndpoint extends AbstractEndpoint<Object> { | ||||
| 
 | ||||
| 		TestEndpoint(String id) { | ||||
|  | @ -78,4 +106,19 @@ public class CloudFoundryEndpointHandlerMappingTests { | |||
| 
 | ||||
| 	} | ||||
| 
 | ||||
| 	private static class TestHalJsonMvcEndpoint extends HalJsonMvcEndpoint { | ||||
| 
 | ||||
| 		TestHalJsonMvcEndpoint() { | ||||
| 			super(new ManagementServletContext() { | ||||
| 
 | ||||
| 				@Override | ||||
| 				public String getContextPath() { | ||||
| 					return ""; | ||||
| 				} | ||||
| 
 | ||||
| 			}); | ||||
| 
 | ||||
| 		} | ||||
| 	} | ||||
| 
 | ||||
| } | ||||
|  |  | |||
		Loading…
	
		Reference in New Issue