DATAREST-293 - Added new module to easily add a HAL browser to Spring Data REST apps.

Added a module that repackages the webjar version of the HAL browser. The repackaging is necessary to be able to control the exposure of the browser dynamically and prevent Spring Boot's auto-exposure of webjars from kicking in.

Tweaked BasePathAwareHandlerMapping to default the Accept header to the one defined in the configuration if none is present in the request or */* is given. This will make sure we default to a JSON dialect in case no header is set.
This commit is contained in:
Oliver Gierke
2015-04-14 16:35:21 +02:00
parent 9a77f20bf4
commit 85ced098c6
7 changed files with 417 additions and 0 deletions

View File

@@ -21,8 +21,10 @@ import java.io.UnsupportedEncodingException;
import java.lang.reflect.Method;
import java.net.URI;
import java.security.Principal;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Enumeration;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;
@@ -37,12 +39,17 @@ import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import javax.servlet.http.Part;
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.method.HandlerMethod;
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;
@@ -72,6 +79,36 @@ public class BasePathAwareHandlerMapping extends RequestMappingHandlerMapping {
this.configuration = configuration;
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.handler.AbstractHandlerMethodMapping#lookupHandlerMethod(java.lang.String, javax.servlet.http.HttpServletRequest)
*/
@Override
protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest request) throws Exception {
List<MediaType> mediaTypes = new ArrayList<MediaType>();
boolean defaultFound = false;
for (MediaType mediaType : MediaType.parseMediaTypes(request.getHeader(HttpHeaders.ACCEPT))) {
MediaType rawtype = mediaType.removeQualityValue();
if (rawtype.equals(configuration.getDefaultMediaType())) {
defaultFound = true;
}
if (!rawtype.equals(MediaType.ALL)) {
mediaTypes.add(mediaType);
}
}
if (!defaultFound) {
mediaTypes.add(configuration.getDefaultMediaType());
}
return super.lookupHandlerMethod(lookupPath, new CustomAcceptHeaderHttpServletRequest(request, mediaTypes));
}
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping#getMappingForMethod(java.lang.reflect.Method, java.lang.Class)
@@ -495,4 +532,44 @@ public class BasePathAwareHandlerMapping extends RequestMappingHandlerMapping {
throw new UnsupportedOperationException();
}
}
/**
* {@link HttpServletRequest} that exposes the given media types for the {@code Accept} header.
*
* @author Oliver Gierke
*/
static class CustomAcceptHeaderHttpServletRequest extends HttpServletRequestWrapper {
private final List<MediaType> acceptMediaTypes;
/**
* Creates a new {@link CustomAcceptHeaderHttpServletRequest} for the given delegate {@link HttpServletRequest} and
* the list of {@link MediaType}.
*
* @param request must not be {@literal null}.
* @param acceptMediaTypes must not be {@literal null} or empty.
*/
public CustomAcceptHeaderHttpServletRequest(HttpServletRequest request, List<MediaType> acceptMediaTypes) {
super(request);
Assert.notEmpty(acceptMediaTypes, "MediaTypes must not be empty!");
this.acceptMediaTypes = acceptMediaTypes;
}
/*
* (non-Javadoc)
* @see javax.servlet.http.HttpServletRequestWrapper#getHeader(java.lang.String)
*/
@Override
public String getHeader(String name) {
if (HttpHeaders.ACCEPT.equalsIgnoreCase(name) && acceptMediaTypes != null) {
return StringUtils.collectionToCommaDelimitedString(acceptMediaTypes);
}
return super.getHeader(name);
}
}
}

View File

@@ -119,6 +119,7 @@ import org.springframework.util.ClassUtils;
import org.springframework.web.bind.support.ConfigurableWebBindingInitializer;
import org.springframework.web.method.support.HandlerMethodArgumentResolver;
import org.springframework.web.servlet.HandlerMapping;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter;
@@ -723,6 +724,28 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
return new AlpsResourceProcessor(config());
}
//
// HAL Browser
//
/*
* (non-Javadoc)
* @see org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter#addResourceHandlers(org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry)
*/
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
// Register HAL browser if present
if (ClassUtils.isPresent("org.springframework.data.rest.webmvc.halbrowser.HalBrowser", getClass().getClassLoader())) {
String basePath = config().getBasePath().toString().concat("/browser");
String rootLocation = "classpath:META-INF/spring-data-rest/hal-browser/";
registry.addResourceHandler(basePath.concat("/**")).addResourceLocations(rootLocation);
}
}
private static class ResourceSupportHttpMessageConverter extends TypeConstrainedMappingJackson2HttpMessageConverter
implements Ordered {