DATAREST-279 - Marshalling support for new geo types.

Added StringTo(Distance|Point)Converter implementations to be able to submit Distances and Points via URIs. See the JavaDoc of those classes for more details.

RepositoryRestMvcConfiguration now also registers the GeoModule provided by Spring Data Commons with the ObjectMapper registered for our custom HttpMessageConverters.
This commit is contained in:
Oliver Gierke
2014-03-21 18:11:38 +01:00
parent 6dd353f9c9
commit 174d3d0e8c
5 changed files with 294 additions and 0 deletions

View File

@@ -37,6 +37,9 @@ import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.context.support.ReloadableResourceBundleMessageSource;
import org.springframework.core.convert.support.ConfigurableConversionService;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.GeoModule;
import org.springframework.data.geo.Point;
import org.springframework.data.mapping.context.MappingContext;
import org.springframework.data.mapping.context.PersistentEntities;
import org.springframework.data.repository.support.DomainClassConverter;
@@ -60,6 +63,8 @@ import org.springframework.data.rest.webmvc.RepositoryRestController;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerAdapter;
import org.springframework.data.rest.webmvc.RepositoryRestHandlerMapping;
import org.springframework.data.rest.webmvc.ServerHttpRequestMethodArgumentResolver;
import org.springframework.data.rest.webmvc.convert.StringToDistanceConverter;
import org.springframework.data.rest.webmvc.convert.StringToPointConverter;
import org.springframework.data.rest.webmvc.convert.UriListHttpMessageConverter;
import org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper;
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module;
@@ -167,6 +172,15 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService();
conversionService.addConverter(UUIDConverter.INSTANCE);
configureConversionService(conversionService);
if (!conversionService.canConvert(String.class, Point.class)) {
conversionService.addConverter(StringToPointConverter.INSTANCE);
}
if (!conversionService.canConvert(String.class, Distance.class)) {
conversionService.addConverter(StringToDistanceConverter.INSTANCE);
}
return conversionService;
}
@@ -562,12 +576,15 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
peraResolver, backendIdHandlerMethodArgumentResolver());
}
@Autowired GeoModule geoModule;
private ObjectMapper basicObjectMapper() {
ObjectMapper objectMapper = new ObjectMapper();
objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true);
// Our special PersistentEntityResource Module
objectMapper.registerModule(persistentEntityJackson2Module());
objectMapper.registerModule(geoModule);
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
Jackson2DatatypeHelper.configureObjectMapper(objectMapper);
// Configure custom Modules

View File

@@ -0,0 +1,87 @@
/*
* Copyright 2014 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.data.rest.webmvc.convert;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Map.Entry;
import org.springframework.core.convert.converter.Converter;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metric;
import org.springframework.data.geo.Metrics;
import org.springframework.util.StringUtils;
/**
* Converter to create {@link Distance} instances from {@link String} representations. The supported format is a decimal
* followed by whitespace and a metric abbreviation. We currently support the following abbreviations:
* {@value #SUPPORTED_METRICS}.
*
* @author Oliver Gierke
*/
public enum StringToDistanceConverter implements Converter<String, Distance> {
INSTANCE;
private static final Map<String, Metric> SUPPORTED_METRICS;
private static final String INVALID_DISTANCE = "Expected double amount optionally followed by a metrics abbreviation (%s) but got '%s'!";
static {
Map<String, Metric> metrics = new LinkedHashMap<String, Metric>();
metrics.put("km", Metrics.KILOMETERS);
metrics.put("miles", Metrics.MILES);
metrics.put("mile", Metrics.MILES);
SUPPORTED_METRICS = Collections.unmodifiableMap(metrics);
}
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Distance convert(String source) {
source = source.trim();
for (Entry<String, Metric> metric : SUPPORTED_METRICS.entrySet()) {
if (source.endsWith(metric.getKey())) {
return fromString(source, metric);
}
}
try {
return new Distance(Double.parseDouble(source));
} catch (NumberFormatException o_O) {
throw new IllegalArgumentException(String.format(INVALID_DISTANCE,
StringUtils.collectionToCommaDelimitedString(SUPPORTED_METRICS.keySet()), source));
}
}
private Distance fromString(String source, Entry<String, Metric> metric) {
String amountString = source.substring(0, source.indexOf(metric.getKey()));
try {
return new Distance(Double.parseDouble(amountString), metric.getValue());
} catch (NumberFormatException o_O) {
throw new IllegalArgumentException(String.format(INVALID_DISTANCE,
StringUtils.collectionToCommaDelimitedString(SUPPORTED_METRICS.keySet()), source));
}
}
}

View File

@@ -0,0 +1,59 @@
/*
* Copyright 2014 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.data.rest.webmvc.convert;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.convert.converter.GenericConverter.ConvertiblePair;
import org.springframework.data.geo.Point;
/**
* Converter to parse two comma-separated doubles into a {@link Point}.
*
* @author Oliver Gierke
*/
public enum StringToPointConverter implements Converter<String, Point> {
INSTANCE;
public static final ConvertiblePair CONVERTIBLE = new ConvertiblePair(String.class, Point.class);
private static final String INVALID_FORMAT = "Expected two doubles separated by a semicolon but got '%s'!";
/*
* (non-Javadoc)
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
*/
@Override
public Point convert(String source) {
String[] parts = source.split(",");
if (parts.length != 2) {
throw new IllegalArgumentException(String.format(INVALID_FORMAT, source));
}
try {
double latitude = Double.parseDouble(parts[0]);
double longitude = Double.parseDouble(parts[1]);
return new Point(latitude, longitude);
} catch (NumberFormatException o_O) {
throw new IllegalArgumentException(String.format(INVALID_FORMAT, source), o_O);
}
}
}

View File

@@ -0,0 +1,62 @@
/*
* Copyright 2014 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.data.rest.webmvc.convert;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.rest.webmvc.convert.StringToDistanceConverter.*;
import org.junit.Test;
import org.springframework.data.geo.Distance;
import org.springframework.data.geo.Metrics;
/**
* Unit tests for {@link StringToDistanceConverter}.
*
* @author Oliver Gierke
*/
public class StringToDistanceConverterUnitTests {
/**
* @see DATAREST-279
*/
@Test
public void parsesDistanceFromString() {
Distance reference = new Distance(10.8, Metrics.KILOMETERS);
assertThat(INSTANCE.convert("10.8km"), is(reference));
assertThat(INSTANCE.convert(" 10.8km"), is(reference));
assertThat(INSTANCE.convert(" 10.8 km"), is(reference));
assertThat(INSTANCE.convert(" 10.8 km "), is(reference));
}
/**
* @see DATAREST-279
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsArbitraryNonsense() {
INSTANCE.convert("foo");
}
/**
* @see DATAREST-279
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsUnsupportedMetric() {
INSTANCE.convert("10.8cm");
}
}

View File

@@ -0,0 +1,69 @@
/*
* Copyright 2014 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.data.rest.webmvc.convert;
import static org.hamcrest.CoreMatchers.*;
import static org.junit.Assert.*;
import static org.springframework.data.rest.webmvc.convert.StringToPointConverter.*;
import org.junit.Test;
import org.springframework.data.geo.Point;
/**
* Unit tests for {@link StringToPointConverter}.
*
* @author Oliver Gierke
*/
public class StringToPointConverterUnitTests {
/**
* @see DATAREST-279
*/
@Test
public void parsesPointFromString() {
Point reference = new Point(10.8, 20.9);
assertThat(INSTANCE.convert("10.8,20.9"), is(reference));
assertThat(INSTANCE.convert(" 10.8,20.9 "), is(reference));
assertThat(INSTANCE.convert(" 10.8 ,20.9"), is(reference));
assertThat(INSTANCE.convert(" 10.8 , 20.9 "), is(reference));
}
/**
* @see DATAREST-279
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsArbitraryNonsense() {
INSTANCE.convert("foo");
}
/**
* @see DATAREST-279
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsMoreThanTwoCoordinates() {
INSTANCE.convert("10.8,20.9,30.10");
}
/**
* @see DATAREST-279
*/
@Test(expected = IllegalArgumentException.class)
public void rejectsInvalidCoordinate() {
INSTANCE.convert("10.8,foo");
}
}