Skip to content

Commit

Permalink
Add custom content negotiation for certain paths
Browse files Browse the repository at this point in the history
  • Loading branch information
tbarsballe committed Apr 5, 2017
1 parent 5fef79c commit b45daf4
Show file tree
Hide file tree
Showing 5 changed files with 145 additions and 33 deletions.
Expand Up @@ -19,6 +19,7 @@
import org.geoserver.importer.rest.converters.ImportContextJSONConverterReader;
import org.geoserver.importer.rest.converters.ImportContextJSONConverterWriter;
import org.geoserver.importer.transform.TransformChain;
import org.geoserver.rest.PutIgnoringExtensionContentNegotiationStrategy;
import org.geoserver.rest.RequestInfo;
import org.geoserver.rest.RestBaseController;
import org.geoserver.rest.RestException;
Expand All @@ -28,9 +29,12 @@
import org.opengis.referencing.NoSuchAuthorityCodeException;
import org.opengis.referencing.crs.CoordinateReferenceSystem;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;

import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
Expand All @@ -39,6 +43,7 @@
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;
Expand Down Expand Up @@ -143,9 +148,19 @@ public ImportWrapper taskPut(@PathVariable Long id, @PathVariable Integer taskId
return (writer, converter) -> handleTaskPut(id, taskId, request, response, converter);
}

//TODO: produces all to support filename in path.
//TODO: Need to find a way to exclude just this method from content negotiation via path other than "produces = {MediaType.ALL_VALUE}"
@PutMapping(path = "/{taskId:.+}", produces = {MediaType.ALL_VALUE})
/**
* Workaround to support regular response content type when file extension is in path
*/
@Configuration
static class ImportTaskControllerConfiguration {
@Bean
PutIgnoringExtensionContentNegotiationStrategy importTaskPutContentNegotiationStrategy() {
return new PutIgnoringExtensionContentNegotiationStrategy(
new PatternsRequestCondition("/imports/{id}/tasks/{taskId:.+}"),
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.TEXT_HTML));
}
}
@PutMapping(path = "/{taskId:.+}")
public Object taskPutFile(@PathVariable Long id, @PathVariable Object taskId, HttpServletRequest request, HttpServletResponse response) {
ImportContext context = context(id);
//TODO: Task id is the file name here. This functionality is completely undocumented
Expand Down
Expand Up @@ -7,12 +7,16 @@
import org.geoserver.platform.GeoServerExtensions;
import org.geoserver.rest.converters.*;
import org.geotools.util.Version;
import org.springframework.beans.factory.BeanInitializationException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.accept.ContentNegotiationManager;
import org.springframework.web.accept.ContentNegotiationManagerFactoryBean;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.config.annotation.ContentNegotiationConfigurer;
Expand All @@ -35,33 +39,47 @@
* Configure various aspects of Spring MVC, in particular message converters
*/
@Configuration
public class MVCConfiguration extends WebMvcConfigurationSupport {
public class RestConfiguration extends WebMvcConfigurationSupport {

private final class DefaultContentNegotiation implements ContentNegotiationStrategy {
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest)
throws HttpMediaTypeNotAcceptableException {
private ContentNegotiationManager contentNegotiationManager;

@Autowired
private ApplicationContext applicationContext;

/**
* Return a {@link ContentNegotiationManager} instance to use to determine
* requested {@linkplain MediaType media types} in a given request.
*/
@Override
@Bean
public ContentNegotiationManager mvcContentNegotiationManager() {
if (this.contentNegotiationManager == null) {
this.contentNegotiationManager = super.mvcContentNegotiationManager();
this.contentNegotiationManager.getStrategies().add(0, new DelegatingContentNegotiationStrategy());
}
return this.contentNegotiationManager;
}

Object request = webRequest.getNativeRequest();
List<MediaType> list = new ArrayList<>();
if( request instanceof HttpServletRequest){
HttpServletRequest httpRequest = (HttpServletRequest) request;
String path = httpRequest.getPathInfo();
if( path != null && path.contains("templates") && path.endsWith(".ftl")){
list.add( MediaType.TEXT_PLAIN);
/**
* Allows extension point configuration of {@link ContentNegotiationStrategy}s
*/
private static class DelegatingContentNegotiationStrategy implements ContentNegotiationStrategy {
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
List<ContentNegotiationStrategy> strategies = GeoServerExtensions.extensions(ContentNegotiationStrategy.class);
List<MediaType> mediaTypes;
for (ContentNegotiationStrategy strategy : strategies) {
if (!(strategy instanceof ContentNegotiationManager || strategy instanceof DelegatingContentNegotiationStrategy)) {
mediaTypes = strategy.resolveMediaTypes(webRequest);
if (mediaTypes.size() > 0) {
return mediaTypes;
}
}
}
if( list.isEmpty()){
list.add(MediaType.TEXT_HTML);
}
return list;
return new ArrayList<>();
}
}

@Autowired
private ApplicationContext applicationContext;

@Override
protected void configureMessageConverters(List<HttpMessageConverter<?>> converters) {
Catalog catalog = (Catalog) applicationContext.getBean("catalog");
Expand Down Expand Up @@ -117,9 +135,6 @@ public void configureContentNegotiation(ContentNegotiationConfigurer configurer)
for (MediaTypeCallback callback : callbacks) {
callback.configure(configurer);
}

// configurer.defaultContentTypeStrategy( new DefaultContentNegotiation());

// configurer.favorPathExtension(true);
//todo properties files are only supported for test cases. should try to find a way to
//support them without polluting prod code with handling
Expand Down
Expand Up @@ -14,6 +14,7 @@
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.UUID;
import java.util.logging.Logger;
Expand All @@ -35,6 +36,7 @@
import org.geoserver.catalog.Styles;
import org.geoserver.config.GeoServerDataDirectory;
import org.geoserver.platform.resource.Resource;
import org.geoserver.rest.PutIgnoringExtensionContentNegotiationStrategy;
import org.geoserver.rest.RestBaseController;
import org.geoserver.rest.util.IOUtils;
import org.geoserver.rest.ResourceNotFoundException;
Expand All @@ -48,6 +50,8 @@
import org.geotools.util.logging.Logging;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
Expand All @@ -64,6 +68,7 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;
import org.springframework.web.util.UriComponents;
import org.springframework.web.util.UriComponentsBuilder;
import org.xml.sax.EntityResolver;
Expand Down Expand Up @@ -384,13 +389,21 @@ public void putStyleZip(
putZipInternal(is, workspaceName, name, styleName);
}

@PutMapping(
value = {
"/styles/{styleName}",
"/workspaces/{workspaceName}/styles/{styleName}"
},
consumes = {MediaType.ALL_VALUE}
)
/**
* Workaround to support regular response content type when extension is in path
*/
@Configuration
static class StyleControllerConfiguration {
@Bean
PutIgnoringExtensionContentNegotiationStrategy stylePutContentNegotiationStrategy() {
return new PutIgnoringExtensionContentNegotiationStrategy(
new PatternsRequestCondition("/styles/{styleName}", "/workspaces/{workspaceName}/styles/{styleName}"),
Arrays.asList(MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML, MediaType.TEXT_HTML));
}
}

@PutMapping( value = {"/styles/{styleName}", "/workspaces/{workspaceName}/styles/{styleName}"},
consumes = {MediaType.ALL_VALUE})
public void putStyle(
@PathVariable String styleName,
@PathVariable(required = false) String workspaceName,
Expand Down Expand Up @@ -686,5 +699,4 @@ private void putZipInternal(InputStream is, String workspace, String name, Strin
private boolean existsStyleInCatalog(String workspaceName, String name) {
return (catalog.getStyleByName(workspaceName, name ) != null);
}

}
@@ -0,0 +1,53 @@
package org.geoserver.rest;

import org.springframework.http.MediaType;
import org.springframework.web.HttpMediaTypeNotAcceptableException;
import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.servlet.mvc.condition.PatternsRequestCondition;

import javax.servlet.http.HttpServletRequest;
import java.util.ArrayList;
import java.util.List;

/**
* When doing a POST or PUT to endpoints accepting some file, the URL will contain the extension of the POSTed file,
* while the response should be a standard XML or JSON response, depending on the accepts header.
*
* The default ContentNegotiationStrategies will favor the extension when determining the response type.
* For example, when posting an sld file, "Ponds.sld", to "/rest/styles/Ponds.sld", the default content negotiation
* assumes this to mean you expect an .sld response.
*
* This strategy overrides this behavior for specified paths
*/
public class PutIgnoringExtensionContentNegotiationStrategy implements ContentNegotiationStrategy {

PatternsRequestCondition pathMatcher;
List<MediaType> mediaTypes;

/**
* Construct a new strategy. This should be instantiated as a bean for it to get picked up by the {@link RestConfiguration}
* @param pathMatcher The {@link PatternsRequestCondition} used to determine if the request path matches
* @param mediaTypes The list of {@link MediaType}s to return when the path matches
*/
public PutIgnoringExtensionContentNegotiationStrategy(PatternsRequestCondition pathMatcher, List<MediaType> mediaTypes) {
this.pathMatcher = pathMatcher;
this.mediaTypes = mediaTypes;
}

/**
* Determine the list of supported media types
*
* @param webRequest
* @return {@link #mediaTypes}, as long as the request is a PUT or POST, and the path provided by webRequest
* matches. Otherwise returns an empty list (never null).
*/
@Override
public List<MediaType> resolveMediaTypes(NativeWebRequest webRequest) throws HttpMediaTypeNotAcceptableException {
HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class);
if (pathMatcher.getMatchingCondition(request) != null && ("PUT".equals(request.getMethod()) || "POST".equals(request.getMethod()))) {
return mediaTypes;
}
return new ArrayList<>();
}
}
Expand Up @@ -355,6 +355,23 @@ public void testPutAsSLD() throws Exception {
xml = new String(out.toByteArray());
assertTrue(xml.contains("<sld:Name>foo</sld:Name>"));
}

@Test
public void testPutAsSLDWithExtension() throws Exception {
String xml = newSLDXML();

MockHttpServletResponse response =
putAsServletResponse( RestBaseController.ROOT_PATH + "/styles/Ponds.sld", xml, SLDHandler.MIMETYPE_10);
assertEquals( 200, response.getStatus() );

Style s = catalog.getStyleByName( "Ponds" ).getStyle();
ByteArrayOutputStream out = new ByteArrayOutputStream();

SLDHandler handler = new SLDHandler();
handler.encode(Styles.sld(s), SLDHandler.VERSION_10, false, out);
xml = new String(out.toByteArray());
assertTrue(xml.contains("<sld:Name>foo</sld:Name>"));
}

@Test
public void testRawPutAsSLD() throws Exception {
Expand Down

0 comments on commit b45daf4

Please sign in to comment.