DATAREST-654 - Added suport for internationalization of enum values.
RepositoryRestConfiguration now exposes an option to enable enum value serialization and a nested configuration object to tweak the details. If enabled, a Jackson serializer and deserializer is registered trying to resolve the enum values from the Spring Data REST resource bundle using the fully-qualified enum value name as key. If no explicitly configured value is configured a default translation is triggered that capitalizes the lowercased value name replacing the underscores with spaces (e.g. PAYMENT_EXPECTED -> Payment expected). This can be opted out of, of course. On the parsing side the deserializer will also consult the resourcebundle and default translation but also accepting the enum name as is (also opt-outable). Deprecated non-bean-style accessors for projection and metadata configuration on RepositoryRestConfiguration to make these options tweakable via Spring Boot application properties by default.
This commit is contained in:
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright 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.data.rest.core.config;
|
||||
|
||||
/**
|
||||
* Configuration options for enum value translation.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.4
|
||||
* @soundtrack Wallis Bird - Measuring Cities (Yeah! Wallis Bird live 2007-2014)
|
||||
*/
|
||||
public interface EnumTranslationConfiguration {
|
||||
|
||||
/**
|
||||
* Configures whether the default translation of enum names shall be applied. Defaults to {@literal true}. This means
|
||||
* the configuration will turn enum names into human friendly {@link String}s and also parse them if - only if - no
|
||||
* explicit translation is available.
|
||||
*
|
||||
* @param enableDefaultTranslation whether to enable the default translation of enum names.
|
||||
*/
|
||||
void setEnableDefaultTranslation(boolean enableDefaultTranslation);
|
||||
|
||||
/**
|
||||
* Configures whether to always accept the raw enum name when parsing. This is useful if clients were used to send the
|
||||
* Java enum names shall not be broken even if on the serialization side enum translation is activated.
|
||||
*
|
||||
* @param parseEnumNameAsFallback whether to parse the raw enum value as fallback, even if an explicit translation is
|
||||
* available.
|
||||
*/
|
||||
void setParseEnumNameAsFallback(boolean parseEnumNameAsFallback);
|
||||
}
|
||||
@@ -55,12 +55,8 @@ public class RepositoryRestConfiguration {
|
||||
private final ProjectionDefinitionConfiguration projectionConfiguration;
|
||||
private final MetadataConfiguration metadataConfiguration;
|
||||
|
||||
/**
|
||||
* Creates a new default {@link RepositoryRestConfiguration}.
|
||||
*/
|
||||
public RepositoryRestConfiguration() {
|
||||
this(new ProjectionDefinitionConfiguration(), new MetadataConfiguration());
|
||||
}
|
||||
private final EnumTranslationConfiguration enumSerializationConfiguration;
|
||||
private boolean enableEnumTranslation = false;
|
||||
|
||||
/**
|
||||
* Creates a new {@link RepositoryRestConfiguration} with the given {@link ProjectionDefinitionConfiguration}.
|
||||
@@ -69,13 +65,15 @@ public class RepositoryRestConfiguration {
|
||||
* @param metadataConfiguration must not be {@literal null}.
|
||||
*/
|
||||
public RepositoryRestConfiguration(ProjectionDefinitionConfiguration projectionConfiguration,
|
||||
MetadataConfiguration metadataConfiguration) {
|
||||
MetadataConfiguration metadataConfiguration, EnumTranslationConfiguration enumTranslationConfiguration) {
|
||||
|
||||
Assert.notNull(projectionConfiguration, "ProjectionDefinitionConfiguration must not be null!");
|
||||
Assert.notNull(metadataConfiguration, "MetadataConfiguration must not be null!");
|
||||
Assert.notNull(enumTranslationConfiguration, " must not be null!");
|
||||
|
||||
this.projectionConfiguration = projectionConfiguration;
|
||||
this.metadataConfiguration = metadataConfiguration;
|
||||
this.enumSerializationConfiguration = enumTranslationConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -449,8 +447,19 @@ public class RepositoryRestConfiguration {
|
||||
* Returns the {@link ProjectionDefinitionConfiguration} to register addition projections.
|
||||
*
|
||||
* @return
|
||||
* @deprecated since 2.4, use {@link #getProjectionConfiguration()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public ProjectionDefinitionConfiguration projectionConfiguration() {
|
||||
return getProjectionConfiguration();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link ProjectionDefinitionConfiguration} to register addition projections.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public ProjectionDefinitionConfiguration getProjectionConfiguration() {
|
||||
return projectionConfiguration;
|
||||
}
|
||||
|
||||
@@ -458,8 +467,49 @@ public class RepositoryRestConfiguration {
|
||||
* Returns the {@link MetadataConfiguration} to customize metadata exposure.
|
||||
*
|
||||
* @return
|
||||
* @deprecated since 2.4, use {@link #getMetadataConfiguration()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public MetadataConfiguration metadataConfiguration() {
|
||||
return metadataConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link MetadataConfiguration} to customize metadata exposure.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public MetadataConfiguration getMetadataConfiguration() {
|
||||
return metadataConfiguration;
|
||||
}
|
||||
|
||||
/**
|
||||
* Configures whether to enable enum value translation via the Spring Data REST default resource bundle. Defaults to
|
||||
* {@literal false} for backwards compatibility reasons. Will use the fully qualified enum name as key. For further
|
||||
* details see {@link EnumTranslator}.
|
||||
*
|
||||
* @param enableEnumTranslation
|
||||
* @see #getEnumSerializationConfiguration()
|
||||
*/
|
||||
public void setEnableEnumTranslation(boolean enableEnumTranslation) {
|
||||
this.enableEnumTranslation = enableEnumTranslation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether enum value translation is enabled.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isEnableEnumTranslation() {
|
||||
return this.enableEnumTranslation;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link EnumTranslator} for
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public EnumTranslationConfiguration getEnumSerializationConfiguration() {
|
||||
return this.enumSerializationConfiguration;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,8 +17,13 @@ package org.springframework.data.rest.core;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.rest.core.config.EnumTranslationConfiguration;
|
||||
import org.springframework.data.rest.core.config.MetadataConfiguration;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.http.MediaType;
|
||||
|
||||
@@ -30,14 +35,21 @@ import org.springframework.http.MediaType;
|
||||
*/
|
||||
public class RepositoryRestConfigurationUnitTests {
|
||||
|
||||
RepositoryRestConfiguration configuration;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
this.configuration = new RepositoryRestConfiguration(new ProjectionDefinitionConfiguration(),
|
||||
new MetadataConfiguration(), mock(EnumTranslationConfiguration.class));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-34
|
||||
*/
|
||||
@Test
|
||||
public void returnsBodiesIfAcceptHeaderPresentByDefault() {
|
||||
|
||||
RepositoryRestConfiguration configuration = new RepositoryRestConfiguration();
|
||||
|
||||
assertThat(configuration.returnBodyOnCreate(MediaType.APPLICATION_JSON_VALUE), is(true));
|
||||
assertThat(configuration.returnBodyOnUpdate(MediaType.APPLICATION_JSON_VALUE), is(true));
|
||||
}
|
||||
@@ -48,8 +60,6 @@ public class RepositoryRestConfigurationUnitTests {
|
||||
@Test
|
||||
public void doesNotReturnBodiesIfNoAcceptHeaderPresentByDefault() {
|
||||
|
||||
RepositoryRestConfiguration configuration = new RepositoryRestConfiguration();
|
||||
|
||||
assertThat(configuration.returnBodyOnCreate(null), is(false));
|
||||
assertThat(configuration.returnBodyOnUpdate(null), is(false));
|
||||
}
|
||||
@@ -60,8 +70,6 @@ public class RepositoryRestConfigurationUnitTests {
|
||||
@Test
|
||||
public void doesNotReturnBodiesIfEmptyAcceptHeaderPresentByDefault() {
|
||||
|
||||
RepositoryRestConfiguration configuration = new RepositoryRestConfiguration();
|
||||
|
||||
assertThat(configuration.returnBodyOnCreate(""), is(false));
|
||||
assertThat(configuration.returnBodyOnUpdate(""), is(false));
|
||||
}
|
||||
@@ -72,7 +80,6 @@ public class RepositoryRestConfigurationUnitTests {
|
||||
@Test
|
||||
public void doesNotReturnBodyForUpdateIfExplicitlyDeactivated() {
|
||||
|
||||
RepositoryRestConfiguration configuration = new RepositoryRestConfiguration();
|
||||
configuration.setReturnBodyOnUpdate(false);
|
||||
|
||||
assertThat(configuration.returnBodyOnUpdate(null), is(false));
|
||||
@@ -86,7 +93,6 @@ public class RepositoryRestConfigurationUnitTests {
|
||||
@Test
|
||||
public void doesNotReturnBodyForCreateIfExplicitlyDeactivated() {
|
||||
|
||||
RepositoryRestConfiguration configuration = new RepositoryRestConfiguration();
|
||||
configuration.setReturnBodyOnCreate(false);
|
||||
|
||||
assertThat(configuration.returnBodyOnCreate(null), is(false));
|
||||
@@ -100,7 +106,6 @@ public class RepositoryRestConfigurationUnitTests {
|
||||
@Test
|
||||
public void returnsBodyForUpdateIfExplicitlyActivated() {
|
||||
|
||||
RepositoryRestConfiguration configuration = new RepositoryRestConfiguration();
|
||||
configuration.setReturnBodyOnUpdate(true);
|
||||
|
||||
assertThat(configuration.returnBodyOnUpdate(null), is(true));
|
||||
@@ -114,7 +119,6 @@ public class RepositoryRestConfigurationUnitTests {
|
||||
@Test
|
||||
public void returnsBodyForCreateIfExplicitlyActivated() {
|
||||
|
||||
RepositoryRestConfiguration configuration = new RepositoryRestConfiguration();
|
||||
configuration.setReturnBodyOnCreate(true);
|
||||
|
||||
assertThat(configuration.returnBodyOnCreate(null), is(true));
|
||||
|
||||
@@ -15,6 +15,8 @@
|
||||
*/
|
||||
package org.springframework.data.rest.core;
|
||||
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
@@ -26,6 +28,9 @@ import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.context.PersistentEntities;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.config.EnumTranslationConfiguration;
|
||||
import org.springframework.data.rest.core.config.MetadataConfiguration;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.domain.jpa.ConfiguredPersonRepository;
|
||||
import org.springframework.data.rest.core.domain.jpa.JpaRepositoryConfig;
|
||||
@@ -52,7 +57,8 @@ public class RepositoryTestsConfig {
|
||||
@SuppressWarnings("deprecation")
|
||||
@Bean
|
||||
public RepositoryRestConfiguration config() {
|
||||
RepositoryRestConfiguration config = new RepositoryRestConfiguration();
|
||||
RepositoryRestConfiguration config = new RepositoryRestConfiguration(new ProjectionDefinitionConfiguration(),
|
||||
new MetadataConfiguration(), mock(EnumTranslationConfiguration.class));
|
||||
|
||||
config.setResourceMappingForDomainType(Person.class).setRel("person");
|
||||
|
||||
|
||||
@@ -17,10 +17,15 @@ package org.springframework.data.rest.webmvc.halbrowser;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.rest.core.config.EnumTranslationConfiguration;
|
||||
import org.springframework.data.rest.core.config.MetadataConfiguration;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
@@ -43,7 +48,9 @@ public class HalBrowserUnitTests {
|
||||
@Test
|
||||
public void createsContextRelativeRedirectForBrowser() throws Exception {
|
||||
|
||||
View view = new HalBrowser(new RepositoryRestConfiguration()).browser();
|
||||
RepositoryRestConfiguration configuration = new RepositoryRestConfiguration(new ProjectionDefinitionConfiguration(),
|
||||
new MetadataConfiguration(), mock(EnumTranslationConfiguration.class));
|
||||
View view = new HalBrowser(configuration).browser();
|
||||
|
||||
assertThat(view, is(instanceOf(RedirectView.class)));
|
||||
|
||||
@@ -53,6 +60,6 @@ public class HalBrowserUnitTests {
|
||||
|
||||
((AbstractView) view).render(Collections.<String, Object> emptyMap(), request, response);
|
||||
|
||||
assertThat(response.getHeader(HttpHeaders.LOCATION), startsWith("/context"));
|
||||
assertThat(response.getHeader(HttpHeaders.LOCATION), Matchers.startsWith("/context"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -96,7 +96,7 @@ public class AlpsController {
|
||||
|
||||
private void verifyAlpsEnabled() {
|
||||
|
||||
if (!configuration.metadataConfiguration().alpsEnabled()) {
|
||||
if (!configuration.getMetadataConfiguration().alpsEnabled()) {
|
||||
throw new ResourceNotFoundException();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -184,7 +184,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
|
||||
*/
|
||||
private Descriptor buildProjectionDescriptor(ResourceMetadata metadata) {
|
||||
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = configuration.projectionConfiguration();
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = configuration.getProjectionConfiguration();
|
||||
String projectionParameterName = projectionConfiguration.getParameterName();
|
||||
|
||||
Map<String, Class<?>> projections = projectionConfiguration.getProjectionsFor(metadata.getDomainType());
|
||||
@@ -221,10 +221,10 @@ public class RootResourceInformationToAlpsDescriptorConverter {
|
||||
|
||||
AnnotatedMethod getter = definition.getGetter();
|
||||
Description description = getter.getAnnotation(Description.class);
|
||||
ResourceDescription fallback = SimpleResourceDescription.defaultFor(String.format("%s.%s", name,
|
||||
definition.getName()));
|
||||
ResourceDescription resourceDescription = description == null ? null : new AnnotationBasedResourceDescription(
|
||||
description, fallback);
|
||||
ResourceDescription fallback = SimpleResourceDescription
|
||||
.defaultFor(String.format("%s.%s", name, definition.getName()));
|
||||
ResourceDescription resourceDescription = description == null ? null
|
||||
: new AnnotationBasedResourceDescription(description, fallback);
|
||||
|
||||
descriptors.add(//
|
||||
descriptor().//
|
||||
@@ -259,10 +259,11 @@ public class RootResourceInformationToAlpsDescriptorConverter {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = configuration.projectionConfiguration();
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = configuration.getProjectionConfiguration();
|
||||
|
||||
return projectionConfiguration.hasProjectionFor(type) ? Arrays.asList(buildProjectionDescriptor(mappings
|
||||
.getMetadataFor(type))) : Collections.<Descriptor> emptyList();
|
||||
return projectionConfiguration.hasProjectionFor(type)
|
||||
? Arrays.asList(buildProjectionDescriptor(mappings.getMetadataFor(type)))
|
||||
: Collections.<Descriptor> emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -283,7 +284,7 @@ public class RootResourceInformationToAlpsDescriptorConverter {
|
||||
List<TemplateVariable> variables = linkToCollectionResource.getVariables();
|
||||
List<Descriptor> descriptors = new ArrayList<Descriptor>(variables.size());
|
||||
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = configuration.projectionConfiguration();
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = configuration.getProjectionConfiguration();
|
||||
|
||||
for (TemplateVariable variable : variables) {
|
||||
|
||||
@@ -355,8 +356,8 @@ public class RootResourceInformationToAlpsDescriptorConverter {
|
||||
|
||||
ResourceMetadata targetTypeMetadata = mappings.getMetadataFor(property.getActualType());
|
||||
|
||||
String href = ProfileController.getPath(configuration, targetTypeMetadata) +
|
||||
"#" + getRepresentationDescriptorId(targetTypeMetadata);
|
||||
String href = ProfileController.getPath(configuration, targetTypeMetadata) + "#"
|
||||
+ getRepresentationDescriptorId(targetTypeMetadata);
|
||||
|
||||
Link link = new Link(href).withSelfRel();
|
||||
|
||||
@@ -419,7 +420,8 @@ public class RootResourceInformationToAlpsDescriptorConverter {
|
||||
try {
|
||||
return messageSource.getMessage(description);
|
||||
} catch (NoSuchMessageException o_O) {
|
||||
return configuration.metadataConfiguration().omitUnresolvableDescriptionKeys() ? null : description.getMessage();
|
||||
return configuration.getMetadataConfiguration().omitUnresolvableDescriptionKeys() ? null
|
||||
: description.getMessage();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,7 +84,9 @@ import org.springframework.data.rest.webmvc.alps.AlpsJsonHttpMessageConverter;
|
||||
import org.springframework.data.rest.webmvc.alps.RootResourceInformationToAlpsDescriptorConverter;
|
||||
import org.springframework.data.rest.webmvc.convert.UriListHttpMessageConverter;
|
||||
import org.springframework.data.rest.webmvc.json.DomainObjectReader;
|
||||
import org.springframework.data.rest.webmvc.json.EnumTranslator;
|
||||
import org.springframework.data.rest.webmvc.json.Jackson2DatatypeHelper;
|
||||
import org.springframework.data.rest.webmvc.json.JacksonSerializers;
|
||||
import org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module;
|
||||
import org.springframework.data.rest.webmvc.json.PersistentEntityToJsonSchemaConverter;
|
||||
import org.springframework.data.rest.webmvc.spi.BackendIdConverter;
|
||||
@@ -249,7 +251,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
configuration.addProjection(projection);
|
||||
}
|
||||
|
||||
RepositoryRestConfiguration config = new RepositoryRestConfiguration(configuration, metadataConfiguration());
|
||||
RepositoryRestConfiguration config = new RepositoryRestConfiguration(configuration, metadataConfiguration(),
|
||||
enumTranslator());
|
||||
configurerDelegate.configureRepositoryRestConfiguration(config);
|
||||
configureRepositoryRestConfiguration(config);
|
||||
|
||||
@@ -528,7 +531,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
handlerAdapter.setWebBindingInitializer(initializer);
|
||||
handlerAdapter.setMessageConverters(defaultMessageConverters());
|
||||
|
||||
if (config().metadataConfiguration().alpsEnabled()) {
|
||||
if (config().getMetadataConfiguration().alpsEnabled()) {
|
||||
handlerAdapter.setResponseBodyAdvice(Arrays.<ResponseBodyAdvice<?>> asList(alpsJsonHttpMessageConverter()));
|
||||
}
|
||||
|
||||
@@ -623,7 +626,7 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
|
||||
List<HttpMessageConverter<?>> messageConverters = new ArrayList<HttpMessageConverter<?>>();
|
||||
|
||||
if (config().metadataConfiguration().alpsEnabled()) {
|
||||
if (config().getMetadataConfiguration().alpsEnabled()) {
|
||||
messageConverters.add(alpsJsonHttpMessageConverter());
|
||||
}
|
||||
|
||||
@@ -705,7 +708,8 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
projectionFactory.setResourceLoader(applicationContext);
|
||||
|
||||
PersistentEntityResourceAssemblerArgumentResolver peraResolver = new PersistentEntityResourceAssemblerArgumentResolver(
|
||||
persistentEntities(), entityLinks(), config().projectionConfiguration(), projectionFactory, resourceMappings());
|
||||
persistentEntities(), entityLinks(), config().getProjectionConfiguration(), projectionFactory,
|
||||
resourceMappings());
|
||||
|
||||
HateoasPageableHandlerMethodArgumentResolver pageableResolver = pageableResolver();
|
||||
HandlerMethodArgumentResolver defaultedPageableResolver = new DefaultedPageableHandlerMethodArgumentResolver(
|
||||
@@ -728,6 +732,10 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
|
||||
objectMapper.registerModule(geoModule);
|
||||
|
||||
if (config().isEnableEnumTranslation()) {
|
||||
objectMapper.registerModule(new JacksonSerializers(enumTranslator()));
|
||||
}
|
||||
|
||||
Jackson2DatatypeHelper.configureObjectMapper(objectMapper);
|
||||
// Configure custom Modules
|
||||
configurerDelegate.configureJacksonObjectMapper(objectMapper);
|
||||
@@ -736,6 +744,11 @@ public class RepositoryRestMvcConfiguration extends HateoasAwareSpringDataWebCon
|
||||
return objectMapper;
|
||||
}
|
||||
|
||||
@Bean
|
||||
public EnumTranslator enumTranslator() {
|
||||
return new EnumTranslator(resourceDescriptionMessageSourceAccessor());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Set<Class<?>> getProjections(Repositories repositories) {
|
||||
|
||||
|
||||
@@ -0,0 +1,180 @@
|
||||
/*
|
||||
* Copyright 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.data.rest.webmvc.json;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
import org.springframework.context.NoSuchMessageException;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.data.rest.core.config.EnumTranslationConfiguration;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Configuration to tweak enum serialization.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class EnumTranslator implements EnumTranslationConfiguration {
|
||||
|
||||
private final MessageSourceAccessor messageSourceAccessor;
|
||||
|
||||
private boolean enableDefaultTranslation;
|
||||
private boolean parseEnumNameAsFallback;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EnumTranslator} using the given {@link MessageSourceAccessor}.
|
||||
*
|
||||
* @param messageSourceAccessor must not be {@literal null}.
|
||||
*/
|
||||
public EnumTranslator(MessageSourceAccessor messageSourceAccessor) {
|
||||
|
||||
Assert.notNull(messageSourceAccessor, "MessageSourceAccessor must not be null!");
|
||||
|
||||
this.messageSourceAccessor = messageSourceAccessor;
|
||||
this.enableDefaultTranslation = true;
|
||||
this.parseEnumNameAsFallback = true;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.config.EnumTranslationConfiguration#setEnableDefaultTranslation(boolean)
|
||||
*/
|
||||
@Override
|
||||
public void setEnableDefaultTranslation(boolean enableDefaultTranslation) {
|
||||
this.enableDefaultTranslation = enableDefaultTranslation;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.core.config.EnumTranslationConfiguration#setParseEnumNameAsFallback(boolean)
|
||||
*/
|
||||
@Override
|
||||
public void setParseEnumNameAsFallback(boolean parseEnumNameAsFallback) {
|
||||
this.parseEnumNameAsFallback = parseEnumNameAsFallback;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the given enum value into a {@link String} consulting the configured {@link MessageSourceAccessor}
|
||||
* potentially falling back to the default translation if configured. Returning the plain enum name if no resolution
|
||||
* applies.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public String asText(Enum<?> value) {
|
||||
|
||||
Assert.notNull(value, "Enum value must not be null!");
|
||||
|
||||
String code = String.format("%s.%s", value.getDeclaringClass().getName(), value.name());
|
||||
|
||||
try {
|
||||
return messageSourceAccessor.getMessage(code);
|
||||
} catch (NoSuchMessageException o_O) {
|
||||
return enableDefaultTranslation ? toDefault(value) : value.name();
|
||||
}
|
||||
}
|
||||
|
||||
public List<String> getValues(Class<? extends Enum<?>> type) {
|
||||
|
||||
List<String> result = new ArrayList<String>();
|
||||
|
||||
for (Enum<?> value : type.getEnumConstants()) {
|
||||
result.add(asText(value));
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses the given source text into the corresponding enum value using the configured {@link MessageSourceAccessor}
|
||||
* potentially falling back to the default translation or the plain enum name if configured.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param text can be {@literal null}
|
||||
* @return the resolved enum or {@literal null} if the resolution failed.
|
||||
*/
|
||||
public <T extends Enum<?>> T fromText(Class<T> type, String text) {
|
||||
|
||||
if (!StringUtils.hasText(text)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Assert.notNull(type, "Enum type must not be null!");
|
||||
|
||||
T value = resolveEnum(type, text, true);
|
||||
|
||||
if (value != null) {
|
||||
return value;
|
||||
}
|
||||
|
||||
value = fromDefault(type, text);
|
||||
|
||||
// Only parse default translation if no explicit translation is available
|
||||
if (value != null && enableDefaultTranslation && asText(value).equals(text)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return parseEnumNameAsFallback ? resolveEnum(type, text, false) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolves the given {@link String} text into an enum value of the given type potentially trying to resolve it
|
||||
* through the configured {@link MessageSourceAccessor}.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param text must not be {@literal null} or empty.
|
||||
* @param resolve whether to resolve the source {@link String} through the message source.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T extends Enum<?>> T resolveEnum(Class<T> type, String text, boolean resolve) {
|
||||
|
||||
for (Enum<?> value : type.getEnumConstants()) {
|
||||
|
||||
String resolved = resolve ? asText(value) : value.name();
|
||||
|
||||
if (resolved != null && resolved.equals(text)) {
|
||||
return (T) value;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Renders a default translation for the given enum (capitalized, lower case, underscores replaced by spaces).
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
private String toDefault(Enum<?> value) {
|
||||
return StringUtils.capitalize(value.name().toLowerCase(Locale.US).replaceAll("_", " "));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to obtain an enum value assuming the given text is a default translation of the enum name.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param text must not be {@literal null} or empty.
|
||||
* @return
|
||||
*/
|
||||
private <T extends Enum<?>> T fromDefault(Class<T> type, String text) {
|
||||
return resolveEnum(type, text.toUpperCase(Locale.US).replaceAll(" ", "_"), true);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
* Copyright 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.data.rest.webmvc.json;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.rest.webmvc.json.JsonSchema.EnumProperty;
|
||||
import org.springframework.data.rest.webmvc.json.JsonSchema.JsonSchemaProperty;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.core.JsonParser;
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.databind.DeserializationContext;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.deser.std.StdDeserializer;
|
||||
import com.fasterxml.jackson.databind.module.SimpleDeserializers;
|
||||
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||
import com.fasterxml.jackson.databind.module.SimpleSerializers;
|
||||
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
|
||||
/**
|
||||
* Custom Spring Data REST Jackson serializers.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @since 2.4
|
||||
* @soundtrack Wallis Bird - I Could Be Your Man (Yeah! Wallis Bird Live 2007-2014)
|
||||
*/
|
||||
public class JacksonSerializers extends SimpleModule {
|
||||
|
||||
private static final long serialVersionUID = 4396776390917947147L;
|
||||
|
||||
/**
|
||||
* Creates a new {@link JacksonSerializers} with the given {@link EnumTranslator}.
|
||||
*
|
||||
* @param translator must not be {@literal null}.
|
||||
*/
|
||||
public JacksonSerializers(EnumTranslator translator) {
|
||||
|
||||
Assert.notNull(translator, "EnumTranslator must not be null!");
|
||||
|
||||
SimpleSerializers serializers = new SimpleSerializers();
|
||||
serializers.addSerializer(Enum.class, new EnumTranslatingSerializer(translator));
|
||||
setSerializers(serializers);
|
||||
|
||||
SimpleDeserializers deserializers = new SimpleDeserializers();
|
||||
deserializers.addDeserializer(Enum.class, new EnumTranslatingDeserializer(translator));
|
||||
setDeserializers(deserializers);
|
||||
}
|
||||
|
||||
/**
|
||||
* An enum serializer to translate raw enum values into values resolved through a resource bundle.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static class EnumTranslatingSerializer extends StdSerializer<Enum>implements JsonSchemaPropertyCustomizer {
|
||||
|
||||
private static final long serialVersionUID = -6706924011396258646L;
|
||||
|
||||
private final EnumTranslator translator;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EnumTranslatingSerializer} using the given {@link EnumTranslator}.
|
||||
*
|
||||
* @param translator must not be {@literal null}.
|
||||
*/
|
||||
public EnumTranslatingSerializer(EnumTranslator translator) {
|
||||
|
||||
super(Enum.class);
|
||||
|
||||
Assert.notNull(translator, "EnumTranslator must not be null!");
|
||||
|
||||
this.translator = translator;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.ser.std.StdSerializer#serialize(java.lang.Object, com.fasterxml.jackson.core.JsonGenerator, com.fasterxml.jackson.databind.SerializerProvider)
|
||||
*/
|
||||
@Override
|
||||
public void serialize(Enum value, JsonGenerator gen, SerializerProvider provider) throws IOException {
|
||||
gen.writeString(translator.asText(value));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.rest.webmvc.json.JsonSchemaPropertyCustomizer#customize(org.springframework.data.rest.webmvc.json.JsonSchema.JsonSchemaProperty, org.springframework.data.util.TypeInformation)
|
||||
*/
|
||||
@Override
|
||||
public JsonSchemaProperty customize(JsonSchemaProperty property, TypeInformation<?> type) {
|
||||
|
||||
List<String> values = new ArrayList<String>();
|
||||
|
||||
for (Object value : type.getType().getEnumConstants()) {
|
||||
values.add(translator.asText((Enum<?>) value));
|
||||
}
|
||||
|
||||
return ((EnumProperty) property).withValues(values);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Enum deserializer that uses a resource bundle to resolve enum values.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
@SuppressWarnings("rawtypes")
|
||||
public static class EnumTranslatingDeserializer extends StdDeserializer<Enum> {
|
||||
|
||||
private static final long serialVersionUID = 5305284644923180079L;
|
||||
|
||||
private final EnumTranslator translator;
|
||||
|
||||
/**
|
||||
* Creates a new {@link EnumTranslatingDeserializer} using the given {@link EnumTranslator}.
|
||||
*
|
||||
* @param translator must not be {@literal null}.
|
||||
*/
|
||||
public EnumTranslatingDeserializer(EnumTranslator translator) {
|
||||
|
||||
super(Enum.class);
|
||||
|
||||
Assert.notNull(translator, "EnumTranslator must not be null!");
|
||||
this.translator = translator;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see com.fasterxml.jackson.databind.JsonDeserializer#deserialize(com.fasterxml.jackson.core.JsonParser, com.fasterxml.jackson.databind.DeserializationContext)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public Enum deserialize(JsonParser p, DeserializationContext ctxt) throws IOException, JsonProcessingException {
|
||||
return translator.fromText((Class<? extends Enum<?>>) ctxt.getContextualType().getRawClass(), p.getText());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -445,24 +445,50 @@ public class JsonSchema {
|
||||
* @author Oliver Gierke
|
||||
* @since 2.3
|
||||
*/
|
||||
static class EnumProperty extends JsonSchemaProperty {
|
||||
public static class EnumProperty extends JsonSchemaProperty {
|
||||
|
||||
private final List<String> values;
|
||||
private List<String> values;
|
||||
|
||||
public EnumProperty(String name, String title, Class<?> type, String description, boolean required) {
|
||||
|
||||
this(name, title, toValues(type), description, required);
|
||||
}
|
||||
|
||||
public EnumProperty(String name, String title, List<String> values, String description, boolean required) {
|
||||
|
||||
super(name, title, description, required);
|
||||
|
||||
this.values = new ArrayList<String>();
|
||||
|
||||
for (Object value : type.getEnumConstants()) {
|
||||
this.values.add(value.toString());
|
||||
}
|
||||
this.values = Collections.unmodifiableList(values);
|
||||
}
|
||||
|
||||
@JsonProperty("enum")
|
||||
public List<String> getValues() {
|
||||
return values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the current {@link EnumProperty} exposing the given values.
|
||||
*
|
||||
* @param values must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public EnumProperty withValues(List<String> values) {
|
||||
|
||||
Assert.notNull(values, "Values must not be null!");
|
||||
|
||||
this.values = Collections.unmodifiableList(values);
|
||||
return this;
|
||||
}
|
||||
|
||||
private static List<String> toValues(Class<?> type) {
|
||||
|
||||
List<String> values = new ArrayList<String>();
|
||||
|
||||
for (Object value : type.getEnumConstants()) {
|
||||
values.add(value.toString());
|
||||
}
|
||||
|
||||
return values;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -191,7 +191,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
|
||||
TypeInformation<?> actualPropertyType = propertyType.getActualType();
|
||||
Class<?> rawPropertyType = propertyType.getType();
|
||||
|
||||
JsonSchemaFormat format = configuration.metadataConfiguration().getSchemaFormatFor(rawPropertyType);
|
||||
JsonSchemaFormat format = configuration.getMetadataConfiguration().getSchemaFormatFor(rawPropertyType);
|
||||
ResourceDescription description = persistentProperty == null
|
||||
? jackson.getFallbackDescription(metadata, definition) : getDescriptionFor(persistentProperty, metadata);
|
||||
JsonSchemaProperty property = getSchemaProperty(definition, propertyType, description);
|
||||
@@ -211,7 +211,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
|
||||
continue;
|
||||
}
|
||||
|
||||
Pattern pattern = configuration.metadataConfiguration().getPatternFor(rawPropertyType);
|
||||
Pattern pattern = configuration.getMetadataConfiguration().getPatternFor(rawPropertyType);
|
||||
|
||||
if (pattern != null) {
|
||||
registrar.register(property.withPattern(pattern), actualPropertyType);
|
||||
@@ -261,6 +261,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
|
||||
return getPropertiesFor(property.getActualType(), mappings.getMetadataFor(property.getActualType()), descriptors);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private JsonSchemaProperty getSchemaProperty(BeanPropertyDefinition definition, TypeInformation<?> type,
|
||||
ResourceDescription description) {
|
||||
|
||||
@@ -301,7 +302,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric
|
||||
return accessor.getMessage(resolvable);
|
||||
} catch (NoSuchMessageException o_O) {
|
||||
|
||||
if (configuration.metadataConfiguration().omitUnresolvableDescriptionKeys()) {
|
||||
if (configuration.getMetadataConfiguration().omitUnresolvableDescriptionKeys()) {
|
||||
return null;
|
||||
} else {
|
||||
throw o_O;
|
||||
|
||||
@@ -75,9 +75,8 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
|
||||
* @param idConverters must not be {@literal null}.
|
||||
*/
|
||||
@Autowired
|
||||
public RepositoryEntityLinks(Repositories repositories, ResourceMappings mappings,
|
||||
RepositoryRestConfiguration config, PagingAndSortingTemplateVariables templateVariables,
|
||||
PluginRegistry<BackendIdConverter, Class<?>> idConverters) {
|
||||
public RepositoryEntityLinks(Repositories repositories, ResourceMappings mappings, RepositoryRestConfiguration config,
|
||||
PagingAndSortingTemplateVariables templateVariables, PluginRegistry<BackendIdConverter, Class<?>> idConverters) {
|
||||
|
||||
Assert.notNull(repositories, "Repositories must not be null!");
|
||||
Assert.notNull(mappings, "ResourceMappings must not be null!");
|
||||
@@ -346,7 +345,7 @@ public class RepositoryEntityLinks extends AbstractEntityLinks {
|
||||
*/
|
||||
private TemplateVariables getProjectionVariable(Class<?> type) {
|
||||
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = config.projectionConfiguration();
|
||||
ProjectionDefinitionConfiguration projectionConfiguration = config.getProjectionConfiguration();
|
||||
|
||||
if (projectionConfiguration.hasProjectionFor(type)) {
|
||||
return new TemplateVariables(new TemplateVariable(projectionConfiguration.getParameterName(), REQUEST_PARAM));
|
||||
|
||||
@@ -17,13 +17,18 @@ package org.springframework.data.rest.webmvc;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import org.hamcrest.Matchers;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.data.rest.core.config.EnumTranslationConfiguration;
|
||||
import org.springframework.data.rest.core.config.MetadataConfiguration;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -50,7 +55,8 @@ public class AugmentingHandlerMappingUnitTests {
|
||||
@Test
|
||||
public void augmentsRequestMappingsWithBaseUriFromConfiguration() {
|
||||
|
||||
RepositoryRestConfiguration configuration = new RepositoryRestConfiguration();
|
||||
RepositoryRestConfiguration configuration = new RepositoryRestConfiguration(new ProjectionDefinitionConfiguration(),
|
||||
new MetadataConfiguration(), mock(EnumTranslationConfiguration.class));
|
||||
configuration.setBasePath("api");
|
||||
|
||||
BasePathAwareHandlerMapping mapping = new BasePathAwareHandlerMapping(configuration);
|
||||
@@ -60,7 +66,7 @@ public class AugmentingHandlerMappingUnitTests {
|
||||
Map<RequestMappingInfo, HandlerMethod> handlerMethods = mapping.getHandlerMethods();
|
||||
|
||||
for (RequestMappingInfo info : handlerMethods.keySet()) {
|
||||
assertThat(info.getPatternsCondition().getPatterns(), hasItem(startsWith("/api")));
|
||||
assertThat(info.getPatternsCondition().getPatterns(), hasItem(Matchers.startsWith("/api")));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,6 +27,9 @@ import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.rest.core.config.EnumTranslationConfiguration;
|
||||
import org.springframework.data.rest.core.config.MetadataConfiguration;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.mapping.ResourceMappings;
|
||||
import org.springframework.data.rest.webmvc.config.RepositoryRestMvcConfiguration;
|
||||
@@ -63,7 +66,8 @@ public class RepositoryRestHandlerMappingUnitTests {
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
|
||||
configuration = new RepositoryRestConfiguration();
|
||||
configuration = new RepositoryRestConfiguration(new ProjectionDefinitionConfiguration(),
|
||||
new MetadataConfiguration(), mock(EnumTranslationConfiguration.class));
|
||||
|
||||
handlerMapping = new RepositoryRestHandlerMapping(mappings, configuration);
|
||||
handlerMapping.setApplicationContext(CONTEXT);
|
||||
|
||||
@@ -0,0 +1,185 @@
|
||||
/*
|
||||
* Copyright 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.data.rest.webmvc.json;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import java.util.Locale;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.context.i18n.LocaleContextHolder;
|
||||
import org.springframework.context.support.MessageSourceAccessor;
|
||||
import org.springframework.context.support.StaticMessageSource;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link EnumTranslator}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
*/
|
||||
public class EnumTranslatorUnitTests {
|
||||
|
||||
StaticMessageSource messageSource;
|
||||
EnumTranslator configuration;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
LocaleContextHolder.setLocale(Locale.US);
|
||||
|
||||
this.messageSource = new StaticMessageSource();
|
||||
this.messageSource.addMessage(MyEnum.class.getName().concat(".").concat(MyEnum.FIRST_VALUE.name()), Locale.US,
|
||||
"Translated");
|
||||
this.configuration = new EnumTranslator(new MessageSourceAccessor(messageSource));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-654
|
||||
*/
|
||||
@Test(expected = IllegalArgumentException.class)
|
||||
public void rejectsNullMessageSourceAccessor() {
|
||||
new EnumTranslator(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-654
|
||||
*/
|
||||
@Test
|
||||
public void parsesNullForNullSource() {
|
||||
assertThat(configuration.fromText(MyEnum.class, null), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-654
|
||||
*/
|
||||
@Test
|
||||
public void parsesNullForEmptySource() {
|
||||
assertThat(configuration.fromText(MyEnum.class, null), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-654
|
||||
*/
|
||||
@Test
|
||||
public void parsesNullForUnknownValue() {
|
||||
assertThat(configuration.fromText(MyEnum.class, "Foobar"), is(nullValue()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-654
|
||||
*/
|
||||
@Test
|
||||
public void returnsEnumNameIfDefaultTranslationIsDisabled() {
|
||||
|
||||
configuration.setEnableDefaultTranslation(false);
|
||||
|
||||
assertThat(configuration.asText(MyEnum.SECOND_VALUE), is(MyEnum.SECOND_VALUE.name()));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-654
|
||||
*/
|
||||
@Test
|
||||
public void returnsDefaultTranslationByDefault() {
|
||||
|
||||
assertThat(configuration.asText(MyEnum.SECOND_VALUE), is("Second value"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-654
|
||||
*/
|
||||
@Test
|
||||
public void parsesEnumNameIfDefaultTranslationIsDisabled() {
|
||||
|
||||
configuration.setEnableDefaultTranslation(false);
|
||||
|
||||
assertThat(configuration.fromText(MyEnum.class, "FIRST_VALUE"), is(MyEnum.FIRST_VALUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-654
|
||||
*/
|
||||
@Test
|
||||
public void parsesStandardTranslationAndEnumNameByDefault() {
|
||||
|
||||
assertThat(configuration.fromText(MyEnum.class, "FIRST_VALUE"), is(MyEnum.FIRST_VALUE));
|
||||
assertThat(configuration.fromText(MyEnum.class, "Second value"), is(MyEnum.SECOND_VALUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-654
|
||||
*/
|
||||
@Test
|
||||
public void translatesEnumName() {
|
||||
|
||||
LocaleContextHolder.setLocale(Locale.US);
|
||||
|
||||
messageSource.addMessage(MyEnum.class.getName().concat(".").concat(MyEnum.FIRST_VALUE.name()), Locale.US,
|
||||
"Translated");
|
||||
|
||||
assertThat(configuration.asText(MyEnum.FIRST_VALUE), is("Translated"));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-654
|
||||
*/
|
||||
@Test
|
||||
public void parsesEnumNameByDefaultEvenIfMessageDefined() {
|
||||
|
||||
// Parses resolved message and enum name
|
||||
assertThat(configuration.fromText(MyEnum.class, "Translated"), is(MyEnum.FIRST_VALUE));
|
||||
assertThat(configuration.fromText(MyEnum.class, "FIRST_VALUE"), is(MyEnum.FIRST_VALUE));
|
||||
|
||||
// Does not parse default translation as explicit translation is available
|
||||
assertThat(configuration.fromText(MyEnum.class, "First value"), is(nullValue()));
|
||||
|
||||
// Parses default translation as no explicit translation is available
|
||||
assertThat(configuration.fromText(MyEnum.class, "Second value"), is(MyEnum.SECOND_VALUE));
|
||||
assertThat(configuration.fromText(MyEnum.class, "SECOND_VALUE"), is(MyEnum.SECOND_VALUE));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-654
|
||||
*/
|
||||
@Test
|
||||
public void parsesEnumWithDefaultTranslationDisabled() {
|
||||
|
||||
configuration.setEnableDefaultTranslation(false);
|
||||
|
||||
// Parses default translation as no explicit translation is available
|
||||
assertThat(configuration.fromText(MyEnum.class, "Second value"), is(nullValue()));
|
||||
assertThat(configuration.fromText(MyEnum.class, "SECOND_VALUE"), is(MyEnum.SECOND_VALUE));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void doesNotResolveEnumNameAsFallbackIfConfigured() {
|
||||
|
||||
configuration.setParseEnumNameAsFallback(false);
|
||||
|
||||
// Parses resolved message and enum name
|
||||
assertThat(configuration.fromText(MyEnum.class, "Translated"), is(MyEnum.FIRST_VALUE));
|
||||
assertThat(configuration.fromText(MyEnum.class, "FIRST_VALUE"), is(nullValue()));
|
||||
|
||||
// Parses default translation as no explicit translation is available
|
||||
assertThat(configuration.fromText(MyEnum.class, "Second value"), is(MyEnum.SECOND_VALUE));
|
||||
assertThat(configuration.fromText(MyEnum.class, "SECOND_VALUE"), is(nullValue()));
|
||||
}
|
||||
|
||||
static enum MyEnum {
|
||||
FIRST_VALUE, SECOND_VALUE;
|
||||
}
|
||||
}
|
||||
@@ -20,15 +20,18 @@ import static org.junit.Assert.*;
|
||||
|
||||
import java.io.IOException;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.data.mapping.PersistentEntity;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentEntity;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoPersistentProperty;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty.Access;
|
||||
import com.fasterxml.jackson.core.JsonGenerator;
|
||||
import com.fasterxml.jackson.databind.JsonSerializer;
|
||||
import com.fasterxml.jackson.databind.MapperFeature;
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
@@ -38,29 +41,51 @@ import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||
* Unit tests for {@link JacksonMetadata}.
|
||||
*
|
||||
* @author Oliver Gierke
|
||||
* @soundtrack Four Sided Cube - Bad Day's Rememberance (Bunch of Sides)
|
||||
* @soundtrack Four Sided Cube - Bad Day's Remembrance (Bunch of Sides)
|
||||
*/
|
||||
public class JacksonMetadataUnitTests {
|
||||
|
||||
MappingContext<?, ?> context;
|
||||
ObjectMapper mapper;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
|
||||
this.context = new MongoMappingContext();
|
||||
|
||||
this.mapper = new ObjectMapper();
|
||||
this.mapper.disable(MapperFeature.INFER_PROPERTY_MUTATORS);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-644
|
||||
*/
|
||||
@Test
|
||||
public void testname() {
|
||||
|
||||
MongoMappingContext context = new MongoMappingContext();
|
||||
MongoPersistentEntity<?> entity = context.getPersistentEntity(User.class);
|
||||
|
||||
ObjectMapper mapper = new ObjectMapper();
|
||||
public void detectsReadOnlyProperty() {
|
||||
|
||||
JacksonMetadata metadata = new JacksonMetadata(mapper, User.class);
|
||||
|
||||
MongoPersistentProperty property = entity.getPersistentProperty("username");
|
||||
PersistentEntity<?, ?> entity = context.getPersistentEntity(User.class);
|
||||
PersistentProperty<?> property = entity.getPersistentProperty("username");
|
||||
|
||||
assertThat(metadata.isExported(property), is(true));
|
||||
assertThat(metadata.isReadOnly(property), is(true));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-644
|
||||
*/
|
||||
@Test
|
||||
public void reportsConstructorArgumentAsJacksonWritable() {
|
||||
|
||||
JacksonMetadata metadata = new JacksonMetadata(mapper, Value.class);
|
||||
|
||||
PersistentEntity<?, ?> entity = context.getPersistentEntity(Value.class);
|
||||
PersistentProperty<?> property = entity.getPersistentProperty("value");
|
||||
|
||||
assertThat(metadata.isReadOnly(property), is(false));
|
||||
}
|
||||
|
||||
/**
|
||||
* @see DATAREST-644
|
||||
*/
|
||||
@@ -77,12 +102,25 @@ public class JacksonMetadataUnitTests {
|
||||
|
||||
private String username;
|
||||
|
||||
@JsonProperty(access = Access.READ_ONLY)
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
}
|
||||
|
||||
static class Value {
|
||||
|
||||
private String value;
|
||||
|
||||
@JsonCreator
|
||||
public Value(@JsonProperty("value") String value) {
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerialize(using = SomeBeanSerializer.class)
|
||||
static class SomeBean {}
|
||||
|
||||
|
||||
@@ -17,6 +17,7 @@ package org.springframework.data.rest.webmvc.json;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.*;
|
||||
import static org.junit.Assert.*;
|
||||
import static org.mockito.Mockito.*;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
@@ -27,6 +28,9 @@ import org.mockito.Mock;
|
||||
import org.mockito.runners.MockitoJUnitRunner;
|
||||
import org.springframework.data.mapping.context.PersistentEntities;
|
||||
import org.springframework.data.mongodb.core.mapping.MongoMappingContext;
|
||||
import org.springframework.data.rest.core.config.EnumTranslationConfiguration;
|
||||
import org.springframework.data.rest.core.config.MetadataConfiguration;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.webmvc.mapping.AssociationLinks;
|
||||
|
||||
@@ -58,7 +62,8 @@ public class PersistentEntityJackson2ModuleUnitTests {
|
||||
|
||||
SimpleModule module = new SimpleModule();
|
||||
module.setSerializerModifier(new PersistentEntityJackson2Module.AssociationOmittingSerializerModifier(
|
||||
persistentEntities, associationLinks, new RepositoryRestConfiguration()));
|
||||
persistentEntities, associationLinks, new RepositoryRestConfiguration(new ProjectionDefinitionConfiguration(),
|
||||
new MetadataConfiguration(), mock(EnumTranslationConfiguration.class))));
|
||||
|
||||
this.mapper = new ObjectMapper();
|
||||
this.mapper.registerModule(module);
|
||||
|
||||
@@ -71,8 +71,8 @@ public class PersistentEntityToJsonSchemaConverterUnitTests {
|
||||
@Override
|
||||
public void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) {
|
||||
|
||||
config.metadataConfiguration().registerJsonSchemaFormat(JsonSchemaFormat.EMAIL, EmailAddress.class);
|
||||
config.metadataConfiguration().registerFormattingPatternFor("[A-Z]+", TypeWithPattern.class);
|
||||
config.getMetadataConfiguration().registerJsonSchemaFormat(JsonSchemaFormat.EMAIL, EmailAddress.class);
|
||||
config.getMetadataConfiguration().registerFormattingPatternFor("[A-Z]+", TypeWithPattern.class);
|
||||
|
||||
config.exposeIdsFor(Profile.class);
|
||||
}
|
||||
|
||||
@@ -31,6 +31,9 @@ import org.springframework.data.mapping.context.PersistentEntities;
|
||||
import org.springframework.data.repository.support.DomainClassConverter;
|
||||
import org.springframework.data.repository.support.Repositories;
|
||||
import org.springframework.data.rest.core.UriToEntityConverter;
|
||||
import org.springframework.data.rest.core.config.EnumTranslationConfiguration;
|
||||
import org.springframework.data.rest.core.config.MetadataConfiguration;
|
||||
import org.springframework.data.rest.core.config.ProjectionDefinitionConfiguration;
|
||||
import org.springframework.data.rest.core.config.RepositoryRestConfiguration;
|
||||
import org.springframework.data.rest.core.mapping.RepositoryResourceMappings;
|
||||
import org.springframework.data.rest.webmvc.jpa.JpaRepositoryConfig;
|
||||
@@ -74,7 +77,8 @@ public class RepositoryTestsConfig {
|
||||
|
||||
@Bean
|
||||
public RepositoryRestConfiguration config() {
|
||||
RepositoryRestConfiguration config = new RepositoryRestConfiguration();
|
||||
RepositoryRestConfiguration config = new RepositoryRestConfiguration(new ProjectionDefinitionConfiguration(),
|
||||
new MetadataConfiguration(), mock(EnumTranslationConfiguration.class));
|
||||
|
||||
config.setResourceMappingForDomainType(Person.class).setRel("person");
|
||||
|
||||
|
||||
@@ -73,7 +73,7 @@ public class RepositoryEntityLinksIntegrationTests extends AbstractControllerInt
|
||||
Link link = entityLinks.linkToSingleResource(Order.class, 1);
|
||||
|
||||
assertThat(link.isTemplated(), is(true));
|
||||
assertThat(link.getVariableNames(), hasItem(configuration.projectionConfiguration().getParameterName()));
|
||||
assertThat(link.getVariableNames(), hasItem(configuration.getProjectionConfiguration().getParameterName()));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user