From de80e462f4acf5b89bcd9cc81611a4611e75b5ec Mon Sep 17 00:00:00 2001 From: Jon Brisbin Date: Sat, 2 Mar 2013 11:54:57 -0600 Subject: [PATCH] Fix bugs found when using this with the RESTBucks application. --- .../json/Jackson2DatatypeHelper.java | 51 ++ .../json/PersistentEntityJackson2Module.java | 22 +- .../support/DomainObjectMerger.java | 49 +- .../repository/RepositoryTestsConfig.java | 27 + .../PersistentEntitySerializationTests.java | 61 ++ .../webmvc/RepositoryRestHandlerAdapter.java | 3 +- .../webmvc/RepositoryRestHandlerMapping.java | 3 + .../RepositoryRestMvcConfiguration.java | 703 +++++++++--------- 8 files changed, 493 insertions(+), 426 deletions(-) create mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/json/Jackson2DatatypeHelper.java create mode 100644 spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/json/PersistentEntitySerializationTests.java diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/json/Jackson2DatatypeHelper.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/json/Jackson2DatatypeHelper.java new file mode 100644 index 000000000..50e58a222 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/json/Jackson2DatatypeHelper.java @@ -0,0 +1,51 @@ +package org.springframework.data.rest.repository.json; + +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.ObjectMapper; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.util.ClassUtils; + +/** + * Helper class to register datatype modules based on their presence in the classpath. + * + * @author Jon Brisbin + */ +public class Jackson2DatatypeHelper { + + private static final Logger LOG = LoggerFactory.getLogger(Jackson2DatatypeHelper.class); + private static final boolean IS_HIBERNATE4_MODULE_AVAILABLE = ClassUtils.isPresent( + "com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module", + Jackson2DatatypeHelper.class.getClassLoader() + ); + private static final boolean IS_JODA_MODULE_AVAILABLE = ClassUtils.isPresent( + "com.fasterxml.jackson.datatype.joda.JodaModule", + Jackson2DatatypeHelper.class.getClassLoader() + ); + + public static void configureObjectMapper(ObjectMapper mapper) { + // Hibernate types + if(IS_HIBERNATE4_MODULE_AVAILABLE) { + try { + mapper.registerModule((Module)Class.forName("com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module") + .newInstance()); + } catch(Throwable t) { + if(LOG.isDebugEnabled()) { + LOG.debug(t.getMessage(), t); + } + } + } + // JODA time + if(IS_JODA_MODULE_AVAILABLE) { + try { + mapper.registerModule((Module)Class.forName("com.fasterxml.jackson.datatype.joda.JodaModule") + .newInstance()); + } catch(Throwable t) { + if(LOG.isDebugEnabled()) { + LOG.debug(t.getMessage(), t); + } + } + } + } + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/json/PersistentEntityJackson2Module.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/json/PersistentEntityJackson2Module.java index 497bec0e6..dbd4e8ee2 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/json/PersistentEntityJackson2Module.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/json/PersistentEntityJackson2Module.java @@ -116,24 +116,11 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init private class ResourceDeserializer extends StdDeserializer { private final PersistentEntity persistentEntity; - private final Object defaultObject; - private final Map defaultValues = new HashMap(); @SuppressWarnings({"unchecked"}) - private ResourceDeserializer(PersistentEntity persistentEntity) { + private ResourceDeserializer(final PersistentEntity persistentEntity) { super(persistentEntity.getType()); this.persistentEntity = persistentEntity; - this.defaultObject = instantiateClass(getValueClass()); - - final BeanWrapper wrapper = BeanWrapper.create(defaultObject, conversionService); - persistentEntity.doWithProperties(new PropertyHandler() { - @Override public void doWithPersistentProperty(PersistentProperty prop) { - Object defaultValue = wrapper.getProperty(prop); - if(null != defaultValue) { - defaultValues.put(prop.getName(), defaultValue); - } - } - }); } @SuppressWarnings({"unchecked"}) @@ -253,12 +240,7 @@ public class PersistentEntityJackson2Module extends SimpleModule implements Init } } - if(null != val) { - Object defaultValue = defaultValues.get(persistentProperty.getName()); - if(null == defaultValue || defaultValue != val) { - wrapper.setProperty(persistentProperty, val, false); - } - } + wrapper.setProperty(persistentProperty, val, false); break; } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/DomainObjectMerger.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/DomainObjectMerger.java index 24dbed1e7..b090bccb7 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/DomainObjectMerger.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/DomainObjectMerger.java @@ -1,10 +1,5 @@ package org.springframework.data.rest.repository.support; -import static org.springframework.beans.BeanUtils.*; - -import java.util.Map; -import java.util.concurrent.ConcurrentHashMap; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.ConversionService; import org.springframework.data.mapping.Association; @@ -20,8 +15,6 @@ import org.springframework.data.repository.support.Repositories; */ public class DomainObjectMerger { - private final Map, PersistentEntity> entities = new ConcurrentHashMap, PersistentEntity>(); - private final Map defaultValues = new ConcurrentHashMap(); private final Repositories repositories; private final ConversionService conversionService; @@ -40,61 +33,25 @@ public class DomainObjectMerger { final BeanWrapper fromWrapper = BeanWrapper.create(from, conversionService); final BeanWrapper targetWrapper = BeanWrapper.create(target, conversionService); - PersistentEntity entity = getPerisistentEntity(target.getClass()); - Class clazz = entity.getType(); - final String clazzName = clazz.getSimpleName(); - + PersistentEntity entity = repositories.getPersistentEntity(target.getClass()); entity.doWithProperties(new PropertyHandler() { @Override public void doWithPersistentProperty(PersistentProperty persistentProperty) { - String mapKey = clazzName + "." + persistentProperty.getName(); Object fromVal = fromWrapper.getProperty(persistentProperty); - Object defaultVal = defaultValues.get(mapKey); - if(null != fromVal && !fromVal.equals(defaultVal)) { + if(null != fromVal && !fromVal.equals(targetWrapper.getProperty(persistentProperty))) { targetWrapper.setProperty(persistentProperty, fromVal); } } }); - entity.doWithAssociations(new AssociationHandler() { @Override public void doWithAssociation(Association association) { PersistentProperty persistentProperty = association.getInverse(); - String mapKey = clazzName + "." + persistentProperty.getName(); Object fromVal = fromWrapper.getProperty(persistentProperty); - Object defaultVal = defaultValues.get(mapKey); - if(null != fromVal && !fromVal.equals(defaultVal)) { + if(null != fromVal && !fromVal.equals(targetWrapper.getProperty(persistentProperty))) { targetWrapper.setProperty(persistentProperty, fromVal); } } }); } - @SuppressWarnings({"unchecked"}) - private PersistentEntity getPerisistentEntity(Class clazz) { - PersistentEntity entity = entities.get(clazz); - if(null == entity) { - entity = repositories.getPersistentEntity(clazz); - final String clazzName = clazz.getSimpleName(); - final BeanWrapper wrapper = BeanWrapper.create(instantiateClass(clazz), conversionService); - entity.doWithProperties(new PropertyHandler() { - @Override public void doWithPersistentProperty(PersistentProperty persistentProperty) { - Object val = wrapper.getProperty(persistentProperty); - if(null != val) { - defaultValues.put(clazzName + "." + persistentProperty.getName(), val); - } - } - }); - entity.doWithAssociations(new AssociationHandler() { - @Override public void doWithAssociation(Association association) { - PersistentProperty persistentProperty = association.getInverse(); - Object val = wrapper.getProperty(persistentProperty); - if(null != val) { - defaultValues.put(clazzName + "." + persistentProperty.getName(), val); - } - } - }); - entities.put(clazz, entity); - } - return entity; - } } diff --git a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/RepositoryTestsConfig.java b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/RepositoryTestsConfig.java index 63cbf4cf0..2bb4abb5b 100644 --- a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/RepositoryTestsConfig.java +++ b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/RepositoryTestsConfig.java @@ -1,16 +1,21 @@ package org.springframework.data.rest.repository; +import com.fasterxml.jackson.databind.Module; +import com.fasterxml.jackson.databind.ObjectMapper; 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.context.annotation.Import; +import org.springframework.data.repository.support.DomainClassConverter; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.config.RepositoryRestConfiguration; import org.springframework.data.rest.repository.domain.jpa.ConfiguredPersonRepository; import org.springframework.data.rest.repository.domain.jpa.JpaRepositoryConfig; import org.springframework.data.rest.repository.domain.jpa.Person; import org.springframework.data.rest.repository.domain.jpa.PersonRepository; +import org.springframework.data.rest.repository.json.PersistentEntityJackson2Module; +import org.springframework.format.support.DefaultFormattingConversionService; /** * @author Jon Brisbin @@ -47,4 +52,26 @@ public class RepositoryTestsConfig { return config; } + @Bean public DefaultFormattingConversionService defaultConversionService() { + return new DefaultFormattingConversionService(); + } + + @Bean public DomainClassConverter domainClassConverter() { + return new DomainClassConverter(defaultConversionService()); + } + + @Bean public UriDomainClassConverter uriDomainClassConverter() { + return new UriDomainClassConverter(); + } + + @Bean public Module persistentEntityModule() { + return new PersistentEntityJackson2Module(defaultConversionService()); + } + + @Bean public ObjectMapper objectMapper() { + ObjectMapper mapper = new ObjectMapper(); + mapper.registerModule(persistentEntityModule()); + return mapper; + } + } diff --git a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/json/PersistentEntitySerializationTests.java b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/json/PersistentEntitySerializationTests.java new file mode 100644 index 000000000..a4e6332fe --- /dev/null +++ b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/repository/json/PersistentEntitySerializationTests.java @@ -0,0 +1,61 @@ +package org.springframework.data.rest.repository.json; + +import static junit.framework.Assert.*; +import static org.hamcrest.MatcherAssert.*; +import static org.hamcrest.Matchers.*; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.net.URI; +import java.util.Collections; +import java.util.regex.Pattern; + +import com.fasterxml.jackson.databind.ObjectMapper; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.data.repository.support.Repositories; +import org.springframework.data.rest.repository.PersistentEntityResource; +import org.springframework.data.rest.repository.RepositoryTestsConfig; +import org.springframework.data.rest.repository.domain.jpa.Person; +import org.springframework.data.rest.repository.domain.jpa.PersonRepository; +import org.springframework.test.context.ContextConfiguration; +import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; + +/** + * @author Jon Brisbin + */ +@RunWith(SpringJUnit4ClassRunner.class) +@ContextConfiguration(classes = RepositoryTestsConfig.class) +public class PersistentEntitySerializationTests { + + private static final String PERSON_JSON_IN = "{\"firstName\": \"John\",\"lastName\": \"Doe\"}"; + private static final Pattern PERSON_JSON_OUT = Pattern.compile( + "\\{\"lastName\":\"Doe\",\"created\":([0-9]+),\"firstName\":\"John\",\"links\":\\[\\{\"rel\":\"people.person.siblings\",\"href\":\"http://localhost:8080/people/2/siblings\"}]}"); + @Autowired + private ObjectMapper mapper; + @Autowired + private Repositories repositories; + @Autowired + private PersonRepository people; + + @Test + public void deserializesPersonEntity() throws IOException { + Person p = mapper.readValue(PERSON_JSON_IN, Person.class); + assertThat(p.getFirstName(), is("John")); + assertThat(p.getLastName(), is("Doe")); + assertThat(p.getSiblings(), is(Collections.EMPTY_LIST)); + } + + @Test + public void serializesPersonEntity() throws IOException { + ByteArrayOutputStream out = new ByteArrayOutputStream(); + mapper.writeValue(out, PersistentEntityResource.wrap(repositories.getPersistentEntity(Person.class), + people.save(new Person("John", "Doe")), + URI.create("http://localhost:8080"))); + out.flush(); + String s = new String(out.toByteArray()); + assertTrue("Matches pre-serialized version", PERSON_JSON_OUT.matcher(s).matches()); + } + +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java index 67713a800..03e251bf9 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerAdapter.java @@ -32,8 +32,7 @@ public class RepositoryRestHandlerAdapter extends ResourceProcessorInvokingHandl @Override protected boolean supportsInternal(HandlerMethod handlerMethod) { Class controllerType = handlerMethod.getBeanType(); - return super.supportsInternal(handlerMethod) - && (RepositoryController.class.isAssignableFrom(controllerType) + return (RepositoryController.class.isAssignableFrom(controllerType) || RepositoryEntityController.class.isAssignableFrom(controllerType) || RepositoryPropertyReferenceController.class.isAssignableFrom(controllerType) || RepositorySearchController.class.isAssignableFrom(controllerType)); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java index 4e8cfcdea..a98c86b09 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestHandlerMapping.java @@ -52,6 +52,9 @@ public class RepositoryRestHandlerMapping extends RequestMappingHandlerMapping { protected HandlerMethod lookupHandlerMethod(String lookupPath, HttpServletRequest origRequest) throws Exception { String acceptType = origRequest.getHeader("Accept"); + if(null == acceptType) { + acceptType = config.getDefaultMediaType().toString(); + } List acceptHeaderTypes = MediaType.parseMediaTypes(acceptType); List acceptableTypes = new ArrayList(); for(MediaType mt : acceptHeaderTypes) { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java index d2848d3b3..1c6f3fc2a 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/config/RepositoryRestMvcConfiguration.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.List; +import com.fasterxml.jackson.databind.Module; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.SerializationFeature; import org.springframework.context.annotation.Bean; @@ -18,6 +19,7 @@ import org.springframework.data.rest.repository.UriDomainClassConverter; import org.springframework.data.rest.repository.context.AnnotatedHandlerBeanPostProcessor; import org.springframework.data.rest.repository.context.RepositoriesFactoryBean; import org.springframework.data.rest.repository.context.ValidatingRepositoryEventListener; +import org.springframework.data.rest.repository.json.Jackson2DatatypeHelper; import org.springframework.data.rest.repository.json.PersistentEntityJackson2Module; import org.springframework.data.rest.repository.json.PersistentEntityToJsonSchemaConverter; import org.springframework.data.rest.repository.support.DomainObjectMerger; @@ -41,9 +43,10 @@ import org.springframework.http.MediaType; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; import org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor; -import org.springframework.util.ClassUtils; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExceptionResolver; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter; +import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping; /** * Main application configuration for Spring Data REST. To customize how the exporter works, subclass this and override @@ -58,405 +61,389 @@ import org.springframework.web.servlet.mvc.method.annotation.ExceptionHandlerExc @ImportResource("classpath*:META-INF/spring-data-rest/**/*.xml") public class RepositoryRestMvcConfiguration { - private static final boolean IS_HIBERNATE4_MODULE_AVAILABLE = ClassUtils.isPresent( - "com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module", - RepositoryRestMvcConfiguration.class.getClassLoader() - ); - private static final boolean IS_JODA_MODULE_AVAILABLE = ClassUtils.isPresent( - "com.fasterxml.jackson.datatype.joda.JodaModule", - RepositoryRestMvcConfiguration.class.getClassLoader() - ); + @Bean public RepositoriesFactoryBean repositories() { + return new RepositoriesFactoryBean(); + } - @Bean public RepositoriesFactoryBean repositories() { - return new RepositoriesFactoryBean(); - } + @Bean public DefaultFormattingConversionService defaultConversionService() { + DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); + conversionService.addConverter(UUIDConverter.INSTANCE); + conversionService.addConverter(ISO8601DateConverter.INSTANCE); + configureConversionService(conversionService); + return conversionService; + } - @Bean public DefaultFormattingConversionService defaultConversionService() { - DefaultFormattingConversionService conversionService = new DefaultFormattingConversionService(); - conversionService.addConverter(UUIDConverter.INSTANCE); - conversionService.addConverter(ISO8601DateConverter.INSTANCE); - configureConversionService(conversionService); - return conversionService; - } + @Bean public DomainClassConverter domainClassConverter() { + return new DomainClassConverter(defaultConversionService()); + } - @Bean public DomainClassConverter domainClassConverter() { - return new DomainClassConverter(defaultConversionService()); - } + @Bean public UriDomainClassConverter uriDomainClassConverter() { + return new UriDomainClassConverter(); + } - @Bean public UriDomainClassConverter uriDomainClassConverter() { - return new UriDomainClassConverter(); - } + /** + * {@link org.springframework.context.ApplicationListener} implementation for invoking {@link + * org.springframework.validation.Validator} instances assigned to specific domain types. + */ + @Bean public ValidatingRepositoryEventListener validatingRepositoryEventListener() { + ValidatingRepositoryEventListener listener = new ValidatingRepositoryEventListener(); + configureValidatingRepositoryEventListener(listener); + return listener; + } - /** - * {@link org.springframework.context.ApplicationListener} implementation for invoking {@link - * org.springframework.validation.Validator} instances assigned to specific domain types. - */ - @Bean public ValidatingRepositoryEventListener validatingRepositoryEventListener() { - ValidatingRepositoryEventListener listener = new ValidatingRepositoryEventListener(); - configureValidatingRepositoryEventListener(listener); - return listener; - } + /** + * Main configuration for the REST exporter. + */ + @Bean public RepositoryRestConfiguration config() { + RepositoryRestConfiguration config = new RepositoryRestConfiguration(); + configureRepositoryRestConfiguration(config); + return config; + } - /** - * Main configuration for the REST exporter. - */ - @Bean public RepositoryRestConfiguration config() { - RepositoryRestConfiguration config = new RepositoryRestConfiguration(); - configureRepositoryRestConfiguration(config); - return config; - } + /** + * For getting access to the {@link javax.persistence.EntityManagerFactory}. + * + * @return + */ + @Bean public PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor() { + return new PersistenceAnnotationBeanPostProcessor(); + } - /** - * For getting access to the {@link javax.persistence.EntityManagerFactory}. - * - * @return - */ - @Bean public PersistenceAnnotationBeanPostProcessor persistenceAnnotationBeanPostProcessor() { - return new PersistenceAnnotationBeanPostProcessor(); - } + /** + * {@link org.springframework.beans.factory.config.BeanPostProcessor} to turn beans annotated as {@link + * org.springframework.data.rest.repository.annotation.RepositoryEventHandler}s. + * + * @return + */ + @Bean public AnnotatedHandlerBeanPostProcessor annotatedHandlerBeanPostProcessor() { + return new AnnotatedHandlerBeanPostProcessor(); + } - /** - * {@link org.springframework.beans.factory.config.BeanPostProcessor} to turn beans annotated as {@link - * org.springframework.data.rest.repository.annotation.RepositoryEventHandler}s. - * - * @return - */ - @Bean public AnnotatedHandlerBeanPostProcessor annotatedHandlerBeanPostProcessor() { - return new AnnotatedHandlerBeanPostProcessor(); - } + /** + * For merging incoming objects materialized from JSON with existing domain objects loaded from the repository. + * + * @return + * + * @throws Exception + */ + @Bean public DomainObjectMerger domainObjectMerger() throws Exception { + return new DomainObjectMerger( + repositories().getObject(), + defaultConversionService() + ); + } - /** - * For merging incoming objects materialized from JSON with existing domain objects loaded from the repository. - * - * @return - * - * @throws Exception - */ - @Bean public DomainObjectMerger domainObjectMerger() throws Exception { - return new DomainObjectMerger( - repositories().getObject(), - defaultConversionService() - ); - } + /** + * The controller that handles top-level requests for listing what repositories are available. + * + * @return + * + * @throws Exception + */ + @Bean public RepositoryController repositoryController() throws Exception { + return new RepositoryController( + repositories().getObject(), + config(), + domainClassConverter(), + defaultConversionService() + ); + } - /** - * The controller that handles top-level requests for listing what repositories are available. - * - * @return - * - * @throws Exception - */ - @Bean public RepositoryController repositoryController() throws Exception { - return new RepositoryController( - repositories().getObject(), - config(), - domainClassConverter(), - defaultConversionService() - ); - } + /** + * The controller responsible for handling requests to display or those that modify an entity. + * + * @return + * + * @throws Exception + */ + @Bean public RepositoryEntityController repositoryEntityController() throws Exception { + return new RepositoryEntityController( + repositories().getObject(), + config(), + domainClassConverter(), + defaultConversionService() + ); + } - /** - * The controller responsible for handling requests to display or those that modify an entity. - * - * @return - * - * @throws Exception - */ - @Bean public RepositoryEntityController repositoryEntityController() throws Exception { - return new RepositoryEntityController( - repositories().getObject(), - config(), - domainClassConverter(), - defaultConversionService() - ); - } + /** + * The controller responsible for managing links of property references. + * + * @return + * + * @throws Exception + */ + @Bean public RepositoryPropertyReferenceController propertyReferenceController() throws Exception { + return new RepositoryPropertyReferenceController( + repositories().getObject(), + config(), + domainClassConverter(), + defaultConversionService() + ); + } - /** - * The controller responsible for managing links of property references. - * - * @return - * - * @throws Exception - */ - @Bean public RepositoryPropertyReferenceController propertyReferenceController() throws Exception { - return new RepositoryPropertyReferenceController( - repositories().getObject(), - config(), - domainClassConverter(), - defaultConversionService() - ); - } + /** + * The controller responsible for performing searches. + * + * @return + * + * @throws Exception + */ + @Bean public RepositorySearchController repositorySearchController() throws Exception { + return new RepositorySearchController( + repositories().getObject(), + config(), + domainClassConverter(), + defaultConversionService() + ); + } - /** - * The controller responsible for performing searches. - * - * @return - * - * @throws Exception - */ - @Bean public RepositorySearchController repositorySearchController() throws Exception { - return new RepositorySearchController( - repositories().getObject(), - config(), - domainClassConverter(), - defaultConversionService() - ); - } + /** + * Resolves the base {@link java.net.URI} under which this application is configured. + * + * @return + */ + @Bean public BaseUriMethodArgumentResolver baseUriMethodArgumentResolver() { + return new BaseUriMethodArgumentResolver(); + } - /** - * Resolves the base {@link java.net.URI} under which this application is configured. - * - * @return - */ - @Bean public BaseUriMethodArgumentResolver baseUriMethodArgumentResolver() { - return new BaseUriMethodArgumentResolver(); - } + /** + * Resolves the paging and sorting information from the query parameters based on the current configuration settings. + * + * @return + */ + @Bean public PagingAndSortingMethodArgumentResolver pagingAndSortingMethodArgumentResolver() { + return new PagingAndSortingMethodArgumentResolver(); + } - /** - * Resolves the paging and sorting information from the query parameters based on the current configuration settings. - * - * @return - */ - @Bean public PagingAndSortingMethodArgumentResolver pagingAndSortingMethodArgumentResolver() { - return new PagingAndSortingMethodArgumentResolver(); - } + /** + * Turns an {@link javax.servlet.http.HttpServletRequest} into a {@link org.springframework.http.server.ServerHttpRequest}. + * + * @return + */ + @Bean public ServerHttpRequestMethodArgumentResolver serverHttpRequestMethodArgumentResolver() { + return new ServerHttpRequestMethodArgumentResolver(); + } - /** - * Turns an {@link javax.servlet.http.HttpServletRequest} into a {@link org.springframework.http.server.ServerHttpRequest}. - * - * @return - */ - @Bean public ServerHttpRequestMethodArgumentResolver serverHttpRequestMethodArgumentResolver() { - return new ServerHttpRequestMethodArgumentResolver(); - } + /** + * Resolves the {@link org.springframework.data.repository.core.RepositoryInformation} for this request. + * + * @return + */ + @Bean public RepositoryInformationHandlerMethodArgumentResolver repoInfoMethodArgumentResolver() { + return new RepositoryInformationHandlerMethodArgumentResolver(); + } - /** - * Resolves the {@link org.springframework.data.repository.core.RepositoryInformation} for this request. - * - * @return - */ - @Bean public RepositoryInformationHandlerMethodArgumentResolver repoInfoMethodArgumentResolver() { - return new RepositoryInformationHandlerMethodArgumentResolver(); - } + /** + * A convenience resolver that pulls together all the information needed to service a request. + * + * @return + */ + @Bean public RepositoryRestRequestHandlerMethodArgumentResolver repoRequestArgumentResolver() { + return new RepositoryRestRequestHandlerMethodArgumentResolver(); + } - /** - * A convenience resolver that pulls together all the information needed to service a request. - * - * @return - */ - @Bean public RepositoryRestRequestHandlerMethodArgumentResolver repoRequestArgumentResolver() { - return new RepositoryRestRequestHandlerMethodArgumentResolver(); - } + @Bean public RepositoryEntityLinksMethodArgumentResolver entityLinksMethodArgumentResolver() { + return new RepositoryEntityLinksMethodArgumentResolver(); + } - @Bean public RepositoryEntityLinksMethodArgumentResolver entityLinksMethodArgumentResolver() { - return new RepositoryEntityLinksMethodArgumentResolver(); - } + /** + * Reads incoming JSON into an entity. + * + * @return + */ + @Bean public PersistentEntityResourceHandlerMethodArgumentResolver persistentEntityArgumentResolver() { + List> messageConverters = defaultMessageConverters(); + configureHttpMessageConverters(messageConverters); - /** - * Reads incoming JSON into an entity. - * - * @return - */ - @Bean public PersistentEntityResourceHandlerMethodArgumentResolver persistentEntityArgumentResolver() { - List> messageConverters = defaultMessageConverters(); - configureHttpMessageConverters(messageConverters); + return new PersistentEntityResourceHandlerMethodArgumentResolver(messageConverters); + } - return new PersistentEntityResourceHandlerMethodArgumentResolver(messageConverters); - } + /** + * Turns a domain class into a {@link org.springframework.data.rest.repository.json.JsonSchema}. + * + * @return + */ + @Bean public PersistentEntityToJsonSchemaConverter jsonSchemaConverter() { + return new PersistentEntityToJsonSchemaConverter(); + } - /** - * Turns a domain class into a {@link org.springframework.data.rest.repository.json.JsonSchema}. - * - * @return - */ - @Bean public PersistentEntityToJsonSchemaConverter jsonSchemaConverter() { - return new PersistentEntityToJsonSchemaConverter(); - } + /** + * The Jackson {@link ObjectMapper} used internally. + * + * @return + */ + @Bean public ObjectMapper objectMapper() { + ObjectMapper objectMapper = new ObjectMapper(); + objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true); + // Our special PersistentEntityResource Module + objectMapper.registerModule(persistentEntityJackson2Module()); + Jackson2DatatypeHelper.configureObjectMapper(objectMapper); + // Configure custom Modules + configureJacksonObjectMapper(objectMapper); - /** - * The Jackson {@link ObjectMapper} used internally. - * - * @return - */ - @Bean public ObjectMapper objectMapper() { - ObjectMapper objectMapper = new ObjectMapper(); - objectMapper.configure(SerializationFeature.INDENT_OUTPUT, true); - // Our special PersistentEntityResource Module - objectMapper.registerModule(persistentEntityJackson2Module()); - // Hibernate types - if(IS_HIBERNATE4_MODULE_AVAILABLE) { - objectMapper.registerModule(new com.fasterxml.jackson.datatype.hibernate4.Hibernate4Module()); - } - // JODA time - if(IS_JODA_MODULE_AVAILABLE) { - objectMapper.registerModule(new com.fasterxml.jackson.datatype.joda.JodaModule()); - } - // Configure custom Modules - configureJacksonObjectMapper(objectMapper); + return objectMapper; + } - return objectMapper; - } + /** + * The {@link HttpMessageConverter} used by Spring MVC to read and write JSON data. + * + * @return + */ + @Bean public MappingJackson2HttpMessageConverter jacksonHttpMessageConverter() { + MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(); + jacksonConverter.setObjectMapper(objectMapper()); + jacksonConverter.setSupportedMediaTypes(Arrays.asList( + MediaType.APPLICATION_JSON, + MediaType.valueOf("application/schema+json"), + MediaType.valueOf("application/x-spring-data-verbose+json"), + MediaType.valueOf("application/x-spring-data-compact+json") + )); + return jacksonConverter; + } - /** - * The {@link HttpMessageConverter} used by Spring MVC to read and write JSON data. - * - * @return - */ - @Bean public MappingJackson2HttpMessageConverter jacksonHttpMessageConverter() { - MappingJackson2HttpMessageConverter jacksonConverter = new MappingJackson2HttpMessageConverter(); - jacksonConverter.setObjectMapper(objectMapper()); - jacksonConverter.setSupportedMediaTypes(Arrays.asList( - MediaType.APPLICATION_JSON, - MediaType.valueOf("application/schema+json"), - MediaType.valueOf("application/x-spring-data-verbose+json"), - MediaType.valueOf("application/x-spring-data-compact+json") - )); - return jacksonConverter; - } + /** + * The {@link HttpMessageConverter} used to create JSONP responses. + * + * @return + */ + @Bean public JsonpResponseHttpMessageConverter jsonpHttpMessageConverter() { + return new JsonpResponseHttpMessageConverter(jacksonHttpMessageConverter()); + } - /** - * The {@link HttpMessageConverter} used to create JSONP responses. - * - * @return - */ - @Bean public JsonpResponseHttpMessageConverter jsonpHttpMessageConverter() { - return new JsonpResponseHttpMessageConverter(jacksonHttpMessageConverter()); - } + /** + * The {@link HttpMessageConverter} used to create {@literal text/uri-list} responses. + * + * @return + */ + @Bean public UriListHttpMessageConverter uriListHttpMessageConverter() { + return new UriListHttpMessageConverter(); + } - /** - * The {@link HttpMessageConverter} used to create {@literal text/uri-list} responses. - * - * @return - */ - @Bean public UriListHttpMessageConverter uriListHttpMessageConverter() { - return new UriListHttpMessageConverter(); - } + /** + * Special {@link org.springframework.web.servlet.HandlerAdapter} that only recognizes handler methods defined in + * the provided controller classes. + * + * @return + */ + @Bean public RequestMappingHandlerAdapter repositoryExporterHandlerAdapter() { + List> messageConverters = defaultMessageConverters(); + configureHttpMessageConverters(messageConverters); - /** - * Special {@link org.springframework.web.servlet.HandlerAdapter} that only recognizes handler methods defined in - * the provided controller classes. - * - * @return - */ - @Bean public RepositoryRestHandlerAdapter repositoryExporterHandlerAdapter() { - List> messageConverters = defaultMessageConverters(); - configureHttpMessageConverters(messageConverters); + RepositoryRestHandlerAdapter handlerAdapter = new RepositoryRestHandlerAdapter(); + handlerAdapter.setMessageConverters(messageConverters); + handlerAdapter.setCustomArgumentResolvers(defaultMethodArgumentResolvers()); - RepositoryRestHandlerAdapter handlerAdapter = new RepositoryRestHandlerAdapter(); - handlerAdapter.setMessageConverters(messageConverters); - handlerAdapter.setCustomArgumentResolvers(defaultMethodArgumentResolvers()); + return handlerAdapter; + } - return handlerAdapter; - } + /** + * Special {@link org.springframework.web.servlet.HandlerMapping} that only recognizes handler methods defined in + * the provided controller classes. + * + * @return + */ + @Bean public RequestMappingHandlerMapping repositoryExporterHandlerMapping() { + return new RepositoryRestHandlerMapping(); + } - /** - * Special {@link org.springframework.web.servlet.HandlerMapping} that only recognizes handler methods defined in - * the provided controller classes. - * - * @return - */ - @Bean public RepositoryRestHandlerMapping repositoryExporterHandlerMapping() { - return new RepositoryRestHandlerMapping(); - } + /** + * Jackson module responsible for intelligently serializing and deserializing JSON that corresponds to an entity. + * + * @return + */ + @Bean public Module persistentEntityJackson2Module() { + return new PersistentEntityJackson2Module(defaultConversionService()); + } - /** - * Jackson module responsible for intelligently serializing and deserializing JSON that corresponds to an entity. - * - * @return - */ - @Bean public PersistentEntityJackson2Module persistentEntityJackson2Module() { - return new PersistentEntityJackson2Module(defaultConversionService()); - } + /** + * Bean for looking up methods annotated with {@link org.springframework.web.bind.annotation.ExceptionHandler}. + * + * @return + */ + @Bean public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() { + ExceptionHandlerExceptionResolver er = new ExceptionHandlerExceptionResolver(); + er.setCustomArgumentResolvers(defaultMethodArgumentResolvers()); - /** - * Bean for looking up methods annotated with {@link org.springframework.web.bind.annotation.ExceptionHandler}. - * - * @return - */ - @Bean public ExceptionHandlerExceptionResolver exceptionHandlerExceptionResolver() { - ExceptionHandlerExceptionResolver er = new ExceptionHandlerExceptionResolver(); - er.setCustomArgumentResolvers(defaultMethodArgumentResolvers()); + List> messageConverters = defaultMessageConverters(); + configureHttpMessageConverters(messageConverters); - List> messageConverters = defaultMessageConverters(); - configureHttpMessageConverters(messageConverters); + er.setMessageConverters(messageConverters); + configureExceptionHandlerExceptionResolver(er); - er.setMessageConverters(messageConverters); - configureExceptionHandlerExceptionResolver(er); + return er; + } - return er; - } + private List> defaultMessageConverters() { + List> messageConverters = new ArrayList>(); + messageConverters.add(jacksonHttpMessageConverter()); + messageConverters.add(jsonpHttpMessageConverter()); + messageConverters.add(uriListHttpMessageConverter()); + return messageConverters; + } - private List> defaultMessageConverters() { - List> messageConverters = new ArrayList>(); - messageConverters.add(jacksonHttpMessageConverter()); - messageConverters.add(jsonpHttpMessageConverter()); - messageConverters.add(uriListHttpMessageConverter()); - return messageConverters; - } + private List defaultMethodArgumentResolvers() { + return Arrays.asList(baseUriMethodArgumentResolver(), + pagingAndSortingMethodArgumentResolver(), + serverHttpRequestMethodArgumentResolver(), + repoInfoMethodArgumentResolver(), + repoRequestArgumentResolver(), + persistentEntityArgumentResolver(), + entityLinksMethodArgumentResolver()); + } - private List defaultMethodArgumentResolvers() { - return Arrays.asList(baseUriMethodArgumentResolver(), - pagingAndSortingMethodArgumentResolver(), - serverHttpRequestMethodArgumentResolver(), - repoInfoMethodArgumentResolver(), - repoRequestArgumentResolver(), - persistentEntityArgumentResolver(), - entityLinksMethodArgumentResolver()); - } + /** + * Override this method to add additional configuration. + * + * @param config + * Main configuration bean. + */ + protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) { + } - /** - * Override this method to add additional configuration. - * - * @param config - * Main configuration bean. - */ - protected void configureRepositoryRestConfiguration(RepositoryRestConfiguration config) { - } + /** + * Override this method to add your own converters. + * + * @param conversionService + * Default ConversionService bean. + */ + protected void configureConversionService(ConfigurableConversionService conversionService) { + } - /** - * Override this method to add your own converters. - * - * @param conversionService - * Default ConversionService bean. - */ - protected void configureConversionService(ConfigurableConversionService conversionService) { - } + /** + * Override this method to add validators manually. + * + * @param validatingListener + * The {@link org.springframework.context.ApplicationListener} responsible for invoking {@link + * org.springframework.validation.Validator} instances. + */ + protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) { + } - /** - * Override this method to add validators manually. - * - * @param validatingListener - * The {@link org.springframework.context.ApplicationListener} responsible for invoking {@link - * org.springframework.validation.Validator} instances. - */ - protected void configureValidatingRepositoryEventListener(ValidatingRepositoryEventListener validatingListener) { - } + /** + * Configure the {@link ExceptionHandlerExceptionResolver}. + * + * @param exceptionResolver + * The default exception resolver on which you can add custom argument resolvers. + */ + protected void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) { + } - /** - * Configure the {@link ExceptionHandlerExceptionResolver}. - * - * @param exceptionResolver - * The default exception resolver on which you can add custom argument resolvers. - */ - protected void configureExceptionHandlerExceptionResolver(ExceptionHandlerExceptionResolver exceptionResolver) { - } + /** + * Configure the available {@link HttpMessageConverter}s by adding your own. + * + * @param messageConverters + * The converters to be used by the system. + */ + protected void configureHttpMessageConverters(List> messageConverters) { + } - /** - * Configure the available {@link HttpMessageConverter}s by adding your own. - * - * @param messageConverters - * The converters to be used by the system. - */ - protected void configureHttpMessageConverters(List> messageConverters) { - } - - /** - * Configure the Jackson {@link ObjectMapper} directly. - * - * @param objectMapper - * The {@literal ObjectMapper} to be used by the system. - */ - protected void configureJacksonObjectMapper(ObjectMapper objectMapper) { - } + /** + * Configure the Jackson {@link ObjectMapper} directly. + * + * @param objectMapper + * The {@literal ObjectMapper} to be used by the system. + */ + protected void configureJacksonObjectMapper(ObjectMapper objectMapper) { + } }