From f8e53058f882b91ddfb6bb920ac6d953f35c8381 Mon Sep 17 00:00:00 2001 From: Oliver Gierke Date: Mon, 8 Jul 2013 13:27:13 +0200 Subject: [PATCH] DATAREST-93 - More cleanups in code structure. Removed the need for RepositoryInformationSupport. Refactored Converter implementations to get dependencies injected directly and use constructor injection. Added new RepositoryInvoker abstraction to avoid reflection for known CrudRepository or PagingAndSortingRepository. Refactored existing RepositoryMethodInvoker to implement the new abstraction. Refactored AbstractRepositoryRestController and RepositoryEntityContoller to use new mapping API. --- .../repository/UriDomainClassConverter.java | 69 +++++-- .../invoke/CrudRepositoryInvoker.java | 173 ++++++++++++++++++ .../PagingAndSortingRepositoryInvoker.java | 57 ++++++ .../RepositoryInvocationInformation.java | 26 +++ .../repository/invoke/RepositoryInvoker.java | 28 +++ .../invoke/RepositoryInvokerFactory.java | 68 +++++++ .../invoke/RepositoryMethodInvoker.java | 13 +- .../support/RepositoryInformationSupport.java | 78 -------- .../rest/config/ResourceMappingUnitTests.java | 25 --- .../repository/RepositoryTestsConfig.java | 2 +- .../AbstractRepositoryRestController.java | 16 +- .../webmvc/RepositoryEntityController.java | 6 +- ...yInvokerHandlerMethodArgumentResolver.java | 63 +++++++ .../rest/webmvc/RepositoryRestRequest.java | 35 +--- ...tRequestHandlerMethodArgumentResolver.java | 20 +- ...etadataHandlerMethodArgumentResolver.java} | 51 +++++- ...PersistentEntityToJsonSchemaConverter.java | 59 ++++-- 17 files changed, 598 insertions(+), 191 deletions(-) create mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/CrudRepositoryInvoker.java create mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/PagingAndSortingRepositoryInvoker.java create mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryInvocationInformation.java create mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryInvoker.java create mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryInvokerFactory.java delete mode 100644 spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/RepositoryInformationSupport.java create mode 100644 spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryInvokerHandlerMethodArgumentResolver.java rename spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/{RepositoryInformationHandlerMethodArgumentResolver.java => ResourceMetadataHandlerMethodArgumentResolver.java} (50%) diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/UriDomainClassConverter.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/UriDomainClassConverter.java index d2a842110..8ab44de72 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/UriDomainClassConverter.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/UriDomainClassConverter.java @@ -1,59 +1,104 @@ +/* + * Copyright 2012-2013 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.repository; import java.net.URI; import java.util.HashSet; import java.util.Set; -import org.springframework.beans.factory.InitializingBean; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.convert.ConversionFailedException; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.repository.support.DomainClassConverter; -import org.springframework.data.rest.repository.support.RepositoryInformationSupport; +import org.springframework.data.repository.support.Repositories; +import org.springframework.util.Assert; /** * A {@link ConditionalGenericConverter} that can convert a {@link URI} domain entity. * * @author Jon Brisbin + * @author Oliver Gierke */ -public class UriDomainClassConverter extends RepositoryInformationSupport implements ConditionalGenericConverter, - InitializingBean { +public class UriDomainClassConverter implements ConditionalGenericConverter { - private static TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class); + private static final TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class); - @Autowired private DomainClassConverter domainClassConverter; - private Set convertiblePairs = new HashSet(); + private final Repositories repositories; + private final DomainClassConverter domainClassConverter; + private final Set convertiblePairs; + + /** + * Creates a new {@link UriDomainClassConverter} using the given {@link Repositories} and {@link DomainClassConverter} + * . + * + * @param repositories must not be {@literal null}. + * @param domainClassConverter must not be {@literal null}. + */ + public UriDomainClassConverter(Repositories repositories, DomainClassConverter domainClassConverter) { + + Assert.notNull(repositories, "Repositories must not be null!"); + Assert.notNull(domainClassConverter, "DomainClassConverter must not be null!"); + + this.repositories = repositories; + this.domainClassConverter = domainClassConverter; + this.convertiblePairs = new HashSet(); - @Override - public void afterPropertiesSet() throws Exception { for (Class domainType : repositories) { convertiblePairs.add(new ConvertiblePair(URI.class, domainType)); } } + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) + */ @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { + return URI.class.isAssignableFrom(sourceType.getType()) - && (null != repositories.getPersistentEntity(targetType.getType())); + && repositories.getPersistentEntity(targetType.getType()) != null; } + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes() + */ @Override public Set getConvertibleTypes() { return convertiblePairs; } + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.GenericConverter#convert(java.lang.Object, org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) + */ @Override public Object convert(Object source, TypeDescriptor sourceType, TypeDescriptor targetType) { + PersistentEntity entity = repositories.getPersistentEntity(targetType.getType()); - if (null == entity || !domainClassConverter.matches(STRING_TYPE, targetType)) { + + if (entity != null || !domainClassConverter.matches(STRING_TYPE, targetType)) { throw new ConversionFailedException(sourceType, targetType, source, new IllegalArgumentException( "No PersistentEntity information available for " + targetType.getType())); } URI uri = (URI) source; String[] parts = uri.getPath().split("/"); + if (parts.length < 2) { throw new ConversionFailedException(sourceType, targetType, source, new IllegalArgumentException( "Cannot resolve URI " + uri + ". Is it local or remote? Only local URIs are resolvable.")); diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/CrudRepositoryInvoker.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/CrudRepositoryInvoker.java new file mode 100644 index 000000000..a52ab8db7 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/CrudRepositoryInvoker.java @@ -0,0 +1,173 @@ +/* + * Copyright 2013 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.repository.invoke; + +import java.io.Serializable; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.CrudRepository; + +/** + * @author Oliver Gierke + */ +class CrudRepositoryInvoker implements RepositoryInvoker { + + private final CrudRepository repository; + + /** + * @param repository must not be {@literal null}. + */ + public CrudRepositoryInvoker(CrudRepository repository) { + this.repository = repository; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort) + */ + @Override + public Iterable findAll(Sort sort) { + throw new UnsupportedOperationException(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Pageable) + */ + @Override + public Page findAll(Pageable pageable) { + throw new UnsupportedOperationException(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#save(java.lang.Object) + */ + @Override + public S save(S entity) { + return repository.save(entity); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable) + */ + @Override + public Iterable save(Iterable entities) { + return repository.save(entities); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#findOne(java.io.Serializable) + */ + @Override + public Object findOne(Serializable id) { + return repository.findOne(id); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#exists(java.io.Serializable) + */ + @Override + public boolean exists(Serializable id) { + return repository.exists(id); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#findAll() + */ + @Override + public Iterable findAll() { + return repository.findAll(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable) + */ + @Override + public Iterable findAll(Iterable ids) { + return repository.findAll(ids); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#count() + */ + @Override + public long count() { + return repository.count(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable) + */ + @Override + public void delete(Serializable id) { + repository.delete(id); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object) + */ + @Override + public void delete(Object entity) { + repository.delete(entity); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable) + */ + @Override + public void delete(Iterable entities) { + repository.delete(entities); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.repository.CrudRepository#deleteAll() + */ + @Override + public void deleteAll() { + repository.deleteAll(); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.repository.invoke.RepositoryInvocationInformation#hasFindOne() + */ + @Override + public boolean hasFindOne() { + return true; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.repository.invoke.RepositoryInvocationInformation#hasFindAll() + */ + @Override + public boolean hasFindAll() { + return true; + } +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/PagingAndSortingRepositoryInvoker.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/PagingAndSortingRepositoryInvoker.java new file mode 100644 index 000000000..24ccd3ac1 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/PagingAndSortingRepositoryInvoker.java @@ -0,0 +1,57 @@ +/* + * Copyright 2013 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.repository.invoke; + +import java.io.Serializable; + +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.data.repository.PagingAndSortingRepository; + +/** + * @author Oliver Gierke + */ +class PagingAndSortingRepositoryInvoker extends CrudRepositoryInvoker { + + private final PagingAndSortingRepository repository; + + /** + * @param repository must not be {@literal null}. + */ + public PagingAndSortingRepositoryInvoker(PagingAndSortingRepository repository) { + super(repository); + this.repository = repository; + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.repository.invoke.CrudRepositoryInvoker#findAll(org.springframework.data.domain.Pageable) + */ + @Override + public Page findAll(Pageable pageable) { + return repository.findAll(pageable); + } + + /* + * (non-Javadoc) + * @see org.springframework.data.rest.repository.invoke.CrudRepositoryInvoker#findAll(org.springframework.data.domain.Sort) + */ + @Override + public Iterable findAll(Sort sort) { + return repository.findAll(sort); + } +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryInvocationInformation.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryInvocationInformation.java new file mode 100644 index 000000000..7e12352c4 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryInvocationInformation.java @@ -0,0 +1,26 @@ +/* + * Copyright 2013 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.repository.invoke; + +/** + * @author Oliver Gierke + */ +public interface RepositoryInvocationInformation { + + boolean hasFindOne(); + + boolean hasFindAll(); +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryInvoker.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryInvoker.java new file mode 100644 index 000000000..23ba8773f --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryInvoker.java @@ -0,0 +1,28 @@ +/* + * Copyright 2013 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.repository.invoke; + +import java.io.Serializable; + +import org.springframework.data.repository.PagingAndSortingRepository; + +/** + * @author Oliver Gierke + */ +public interface RepositoryInvoker extends PagingAndSortingRepository, + RepositoryInvocationInformation { + +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryInvokerFactory.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryInvokerFactory.java new file mode 100644 index 000000000..58a895406 --- /dev/null +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryInvokerFactory.java @@ -0,0 +1,68 @@ +/* + * Copyright 2013 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.repository.invoke; + +import java.io.Serializable; +import java.util.HashMap; +import java.util.Map; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.repository.PagingAndSortingRepository; +import org.springframework.data.repository.support.Repositories; + +/** + * @author Oliver Gierke + */ +public class RepositoryInvokerFactory { + + private final Repositories repositories; + private final Map, RepositoryInvoker> invokers; + + /** + * @param repositories + * @param invokers + */ + public RepositoryInvokerFactory(Repositories repositories) { + + this.repositories = repositories; + this.invokers = new HashMap, RepositoryInvoker>(); + prepareInvokers(repositories); + } + + @SuppressWarnings("unchecked") + private final void prepareInvokers(Repositories repositories) { + + for (Class domainType : repositories) { + + Object repository = repositories.getRepositoryFor(domainType); + RepositoryInvoker invoker = null; + + if (repository instanceof PagingAndSortingRepository) { + invoker = new PagingAndSortingRepositoryInvoker((PagingAndSortingRepository) repository); + } else if (repository instanceof CrudRepository) { + invoker = new CrudRepositoryInvoker((CrudRepository) repository); + } else { + invoker = new RepositoryMethodInvoker(repository, null, null); + } + + invokers.put(domainType, invoker); + } + } + + public RepositoryInvoker getInvokerFor(Class domainType) { + return invokers.get(domainType); + } +} diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryMethodInvoker.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryMethodInvoker.java index 28e97e6bd..abf85d55c 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryMethodInvoker.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/invoke/RepositoryMethodInvoker.java @@ -13,7 +13,6 @@ import org.springframework.core.convert.ConversionService; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Sort; -import org.springframework.data.repository.PagingAndSortingRepository; import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.rest.repository.support.ResourceMappingUtils; import org.springframework.util.ReflectionUtils.MethodCallback; @@ -22,7 +21,7 @@ import org.springframework.util.ReflectionUtils.MethodCallback; * @author Jon Brisbin */ @SuppressWarnings("deprecation") -public class RepositoryMethodInvoker implements PagingAndSortingRepository { +public class RepositoryMethodInvoker implements RepositoryInvoker { private final Object repository; private final Map queryMethods = new HashMap(); @@ -57,11 +56,11 @@ public class RepositoryMethodInvoker implements PagingAndSortingRepository paramType = (cardinality == 1 ? method.getParameterTypes()[0] : null); - boolean someMethod = (null != paramType && Iterable.class.isAssignableFrom(paramType)); - boolean byIdMethod = (null != paramType && paramType == Serializable.class); - boolean sortable = (null != paramType && Sort.class.isAssignableFrom(paramType)); - boolean pageable = (null != paramType && Pageable.class.isAssignableFrom(paramType)); + Class paramType = cardinality == 1 ? method.getParameterTypes()[0] : null; + boolean someMethod = null != paramType && Iterable.class.isAssignableFrom(paramType); + boolean byIdMethod = null != paramType && paramType == Serializable.class; + boolean sortable = null != paramType && Sort.class.isAssignableFrom(paramType); + boolean pageable = null != paramType && Pageable.class.isAssignableFrom(paramType); RepositoryMethod repoMethod = new RepositoryMethod(method); if ("save".equals(name) && someMethod) { diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/RepositoryInformationSupport.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/RepositoryInformationSupport.java deleted file mode 100644 index 169ba8027..000000000 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/support/RepositoryInformationSupport.java +++ /dev/null @@ -1,78 +0,0 @@ -package org.springframework.data.rest.repository.support; - -import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*; -import static org.springframework.util.ReflectionUtils.*; -import static org.springframework.util.StringUtils.*; - -import java.lang.reflect.Method; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.repository.core.RepositoryInformation; -import org.springframework.data.repository.support.Repositories; -import org.springframework.data.rest.config.RepositoryRestConfiguration; -import org.springframework.data.rest.config.ResourceMapping; -import org.springframework.data.rest.repository.invoke.RepositoryMethod; -import org.springframework.util.LinkedMultiValueMap; -import org.springframework.util.MultiValueMap; - -/** - * @author Jon Brisbin - */ -@SuppressWarnings("deprecation") -public abstract class RepositoryInformationSupport { - - protected Repositories repositories; - protected RepositoryRestConfiguration config; - protected MultiValueMap, RepositoryMethod> repositoryMethods = new LinkedMultiValueMap, RepositoryMethod>(); - - public Repositories getRepositories() { - return repositories; - } - - @Autowired - public void setRepositories(Repositories repositories) { - this.repositories = repositories; - for (Class domainType : repositories) { - final RepositoryInformation repoInfo = repositories.getRepositoryInformationFor(domainType); - doWithMethods(repoInfo.getRepositoryInterface(), new MethodCallback() { - @Override - public void doWith(Method method) throws IllegalArgumentException, IllegalAccessException { - repositoryMethods.add(repoInfo.getRepositoryInterface(), new RepositoryMethod(method)); - } - }); - } - } - - public RepositoryRestConfiguration getConfig() { - return config; - } - - @Autowired - public void setConfig(RepositoryRestConfiguration config) { - this.config = config; - } - - protected RepositoryInformation findRepositoryInfoFor(String pathSegment) { - if (!hasText(pathSegment)) { - return null; - } - for (Class domainType : repositories) { - RepositoryInformation repoInfo = findRepositoryInfoFor(domainType); - ResourceMapping mapping = getResourceMapping(config, repoInfo); - if (pathSegment.equals(mapping.getPath()) && mapping.isExported()) { - return repoInfo; - } - } - return null; - } - - protected RepositoryInformation findRepositoryInfoFor(Class domainType) { - PersistentEntity entity = repositories.getPersistentEntity(domainType); - if (null != entity) { - return repositories.getRepositoryInformationFor(domainType); - } - return null; - } - -} diff --git a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/config/ResourceMappingUnitTests.java b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/config/ResourceMappingUnitTests.java index 965bbb445..459a0422e 100644 --- a/spring-data-rest-repository/src/test/java/org/springframework/data/rest/config/ResourceMappingUnitTests.java +++ b/spring-data-rest-repository/src/test/java/org/springframework/data/rest/config/ResourceMappingUnitTests.java @@ -25,12 +25,8 @@ import org.junit.Test; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.repository.query.Param; -import org.springframework.data.rest.core.Path; import org.springframework.data.rest.repository.annotation.RestResource; -import org.springframework.data.rest.repository.domain.jpa.AnnotatedPersonRepository; import org.springframework.data.rest.repository.domain.jpa.Person; -import org.springframework.data.rest.repository.domain.jpa.PlainPersonRepository; -import org.springframework.data.rest.repository.mapping.RepositoryCollectionResourceMapping; import org.springframework.data.rest.repository.mapping.ResourceMapping; import org.springframework.hateoas.RelProvider; import org.springframework.hateoas.core.EvoInflectorRelProvider; @@ -44,27 +40,6 @@ import org.springframework.hateoas.core.EvoInflectorRelProvider; public class ResourceMappingUnitTests { RelProvider relProvider = new EvoInflectorRelProvider(); - - - @Test - public void shouldDetectDefaultRelAndPath() throws Exception { - - ResourceMapping newMapping = new RepositoryCollectionResourceMapping(PlainPersonRepository.class, relProvider); - - assertThat(newMapping.getRel(), is("persons")); - assertThat(newMapping.getPath(), is(new Path("person"))); - assertThat(newMapping.isExported(), is(true)); - } - - @Test - public void shouldDetectAnnotatedRelAndPath() throws Exception { - - ResourceMapping newMapping = new RepositoryCollectionResourceMapping(AnnotatedPersonRepository.class, relProvider); - - assertThat(newMapping.getRel(), is("people")); - assertThat(newMapping.getPath(), is(new Path("person"))); - assertThat(newMapping.isExported(), is(false)); - } @Test public void shouldDetectPathAndRemoveLeadingSlashIfAny() { 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 613710462..9742a2cbb 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 @@ -56,6 +56,6 @@ public class RepositoryTestsConfig { @Bean public UriDomainClassConverter uriDomainClassConverter() { - return new UriDomainClassConverter(); + return new UriDomainClassConverter(repositories(), domainClassConverter()); } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java index 54975e272..b07408791 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/AbstractRepositoryRestController.java @@ -33,8 +33,8 @@ import org.springframework.core.convert.ConversionFailedException; import org.springframework.dao.DataIntegrityViolationException; import org.springframework.dao.OptimisticLockingFailureException; import org.springframework.data.domain.Page; -import org.springframework.data.rest.config.ResourceMapping; import org.springframework.data.rest.repository.RepositoryConstraintViolationException; +import org.springframework.data.rest.repository.mapping.ResourceMetadata; import org.springframework.data.rest.webmvc.support.ExceptionMessage; import org.springframework.data.rest.webmvc.support.RepositoryConstraintViolationExceptionMessage; import org.springframework.data.rest.webmvc.support.ValidationExceptionHandler; @@ -196,8 +196,8 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi } protected Link resourceLink(RepositoryRestRequest repoRequest, Resource resource) { - ResourceMapping repoMapping = repoRequest.getRepositoryResourceMapping(); - ResourceMapping entityMapping = repoRequest.getPersistentEntityResourceMapping(); + ResourceMetadata repoMapping = repoRequest.getRepositoryResourceMapping(); + ResourceMetadata entityMapping = repoRequest.getPersistentEntityResourceMapping(); Link selfLink = resource.getLink("self"); String rel = repoMapping.getRel() + "." + entityMapping.getRel(); @@ -205,11 +205,11 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi } @SuppressWarnings({ "unchecked" }) - protected Resources resultToResources(Object result, Link baseLink) { + protected Resources resultToResources(Object result) { if (result instanceof Page) { Page page = (Page) result; - return entitiesToResources(page, baseLink, assembler); + return entitiesToResources(page, assembler); } else if (result instanceof Iterable) { return entitiesToResources((Iterable) result); } else if (null == result) { @@ -220,10 +220,10 @@ class AbstractRepositoryRestController implements MessageSourceAware, Initializi } } - protected Resources> entitiesToResources(Page page, Link baseLink, - final PagedResourcesAssembler assembler) { + protected Resources> entitiesToResources(Page page, + PagedResourcesAssembler assembler) { - return assembler.toResource(page, perAssembler, baseLink); + return assembler.toResource(page, perAssembler); } protected Resources> entitiesToResources(Iterable entities) { diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java index 08d15f487..d8131c6d8 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryEntityController.java @@ -44,6 +44,7 @@ import org.springframework.data.rest.repository.context.BeforeCreateEvent; import org.springframework.data.rest.repository.context.BeforeDeleteEvent; import org.springframework.data.rest.repository.context.BeforeSaveEvent; import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker; +import org.springframework.data.rest.repository.mapping.ResourceMetadata; import org.springframework.data.rest.repository.support.DomainObjectMerger; import org.springframework.data.web.PagedResourcesAssembler; import org.springframework.hateoas.EntityLinks; @@ -134,14 +135,13 @@ class RepositoryEntityController extends AbstractRepositoryRestController implem throw new ResourceNotFoundException(); } - ResourceMapping repoMapping = request.getRepositoryResourceMapping(); + ResourceMetadata repoMapping = request.getRepositoryResourceMapping(); if (!repoMethodInvoker.getQueryMethods().isEmpty()) { links.add(entityLinks.linkForSingleResource(request.getPersistentEntity().getType(), "search").withRel( repoMapping.getRel() + ".search")); } - Link baseLink = request.getRepositoryLink(); - Resources resources = resultToResources(results, baseLink); + Resources resources = resultToResources(results); resources.add(links); return resources; } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryInvokerHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryInvokerHandlerMethodArgumentResolver.java new file mode 100644 index 000000000..67de5ceee --- /dev/null +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryInvokerHandlerMethodArgumentResolver.java @@ -0,0 +1,63 @@ +/* + * Copyright 2013 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; + +import org.springframework.core.MethodParameter; +import org.springframework.data.rest.repository.invoke.RepositoryInvoker; +import org.springframework.data.rest.repository.invoke.RepositoryInvokerFactory; +import org.springframework.web.bind.support.WebDataBinderFactory; +import org.springframework.web.context.request.NativeWebRequest; +import org.springframework.web.method.support.HandlerMethodArgumentResolver; +import org.springframework.web.method.support.ModelAndViewContainer; + +/** + * @author Oliver Gierke + */ +public class RepositoryInvokerHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { + + private final RepositoryRestRequestHandlerMethodArgumentResolver requestResolver; + private final RepositoryInvokerFactory invokerFactory; + + /** + * @param requestResolver + * @param invokerFactory + */ + private RepositoryInvokerHandlerMethodArgumentResolver( + RepositoryRestRequestHandlerMethodArgumentResolver requestResolver, RepositoryInvokerFactory invokerFactory) { + this.requestResolver = requestResolver; + this.invokerFactory = invokerFactory; + } + + /* + * (non-Javadoc) + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) + */ + @Override + public boolean supportsParameter(MethodParameter parameter) { + return RepositoryInvoker.class.isAssignableFrom(parameter.getParameterType()); + } + + /* (non-Javadoc) + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory) + */ + @Override + public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { + + RepositoryRestRequest request = requestResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); + return invokerFactory.getInvokerFor(request.getPersistentEntity().getType()); + } +} diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequest.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequest.java index af942ffc4..37504583c 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequest.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequest.java @@ -15,19 +15,16 @@ */ package org.springframework.data.rest.webmvc; -import static org.springframework.data.rest.core.util.UriUtils.*; -import static org.springframework.data.rest.repository.support.ResourceMappingUtils.*; - import java.net.URI; + import javax.servlet.http.HttpServletRequest; import org.springframework.core.convert.ConversionService; import org.springframework.data.mapping.PersistentEntity; -import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.config.RepositoryRestConfiguration; -import org.springframework.data.rest.config.ResourceMapping; import org.springframework.data.rest.repository.invoke.RepositoryMethodInvoker; +import org.springframework.data.rest.repository.mapping.ResourceMetadata; import org.springframework.hateoas.Link; /** @@ -39,30 +36,22 @@ class RepositoryRestRequest { private final HttpServletRequest request; private final URI baseUri; - private final ResourceMapping repoMapping; + private final ResourceMetadata resourceMetadata; private final Link repoLink; - private final Object repository; private final RepositoryMethodInvoker repoMethodInvoker; private final PersistentEntity persistentEntity; - private final ResourceMapping entityMapping; public RepositoryRestRequest(RepositoryRestConfiguration config, Repositories repositories, - HttpServletRequest request, URI baseUri, RepositoryInformation repoInfo, ConversionService conversionService) { + HttpServletRequest request, URI baseUri, ResourceMetadata repoInfo, ConversionService conversionService) { this.request = request; this.baseUri = baseUri; - this.repoMapping = getResourceMapping(config, repoInfo); - if (null == repoMapping || !repoMapping.isExported()) { + this.resourceMetadata = repoInfo; + if (resourceMetadata == null || !resourceMetadata.isExported()) { this.repoLink = null; - this.repository = null; this.repoMethodInvoker = null; this.persistentEntity = null; - this.entityMapping = null; } else { - this.repoLink = new Link(buildUri(baseUri, repoMapping.getPath()).toString(), repoMapping.getRel()); - this.repository = repositories.getRepositoryFor(repoInfo.getDomainType()); this.persistentEntity = repositories.getPersistentEntity(repoInfo.getDomainType()); - this.repoMethodInvoker = new RepositoryMethodInvoker(repository, repoInfo, conversionService); - this.entityMapping = getResourceMapping(config, persistentEntity); } } @@ -74,12 +63,8 @@ class RepositoryRestRequest { return baseUri; } - ResourceMapping getRepositoryResourceMapping() { - return repoMapping; - } - - Link getRepositoryLink() { - return repoLink; + ResourceMetadata getRepositoryResourceMapping() { + return resourceMetadata; } RepositoryMethodInvoker getRepositoryMethodInvoker() { @@ -89,8 +74,4 @@ class RepositoryRestRequest { PersistentEntity getPersistentEntity() { return persistentEntity; } - - ResourceMapping getPersistentEntityResourceMapping() { - return entityMapping; - } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequestHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequestHandlerMethodArgumentResolver.java index ff6c9499b..fa763aa00 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequestHandlerMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestRequestHandlerMethodArgumentResolver.java @@ -16,14 +16,15 @@ package org.springframework.data.rest.webmvc; import java.net.URI; + import javax.servlet.http.HttpServletRequest; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.core.MethodParameter; import org.springframework.core.convert.ConversionService; -import org.springframework.data.repository.core.RepositoryInformation; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.config.RepositoryRestConfiguration; +import org.springframework.data.rest.repository.mapping.ResourceMetadata; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -39,25 +40,32 @@ public class RepositoryRestRequestHandlerMethodArgumentResolver implements Handl @Autowired private RepositoryRestConfiguration config; @Autowired private Repositories repositories; - @Autowired private RepositoryInformationHandlerMethodArgumentResolver repoInfoResolver; + @Autowired private ResourceMetadataHandlerMethodArgumentResolver repoInfoResolver; @Autowired private BaseUriMethodArgumentResolver baseUriResolver; public RepositoryRestRequestHandlerMethodArgumentResolver(ConversionService conversionService) { this.conversionService = conversionService; } + /* + * (non-Javadoc) + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) + */ @Override public boolean supportsParameter(MethodParameter parameter) { return RepositoryRestRequest.class.isAssignableFrom(parameter.getParameterType()); } + /* + * (non-Javadoc) + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory) + */ @Override - public Object resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + public RepositoryRestRequest resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { - URI baseUri = (URI) baseUriResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); - RepositoryInformation repoInfo = repoInfoResolver.resolveArgument(parameter, mavContainer, webRequest, - binderFactory); + URI baseUri = baseUriResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); + ResourceMetadata repoInfo = repoInfoResolver.resolveArgument(parameter, mavContainer, webRequest, binderFactory); return new RepositoryRestRequest(config, repositories, webRequest.getNativeRequest(HttpServletRequest.class), baseUri, repoInfo, conversionService); diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryInformationHandlerMethodArgumentResolver.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceMetadataHandlerMethodArgumentResolver.java similarity index 50% rename from spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryInformationHandlerMethodArgumentResolver.java rename to spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceMetadataHandlerMethodArgumentResolver.java index 3efbcb7fc..64acd14e0 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryInformationHandlerMethodArgumentResolver.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/ResourceMetadataHandlerMethodArgumentResolver.java @@ -16,12 +16,16 @@ package org.springframework.data.rest.webmvc; import static org.springframework.util.ClassUtils.*; +import static org.springframework.util.StringUtils.*; import javax.servlet.http.HttpServletRequest; import org.springframework.core.MethodParameter; import org.springframework.data.repository.core.RepositoryInformation; -import org.springframework.data.rest.repository.support.RepositoryInformationSupport; +import org.springframework.data.repository.support.Repositories; +import org.springframework.data.rest.repository.mapping.ResourceMappings; +import org.springframework.data.rest.repository.mapping.ResourceMetadata; +import org.springframework.util.Assert; import org.springframework.web.bind.support.WebDataBinderFactory; import org.springframework.web.context.request.NativeWebRequest; import org.springframework.web.method.support.HandlerMethodArgumentResolver; @@ -32,16 +36,39 @@ import org.springframework.web.util.UrlPathHelper; * @author Jon Brisbin * @author Oliver Gierke */ -public class RepositoryInformationHandlerMethodArgumentResolver extends RepositoryInformationSupport implements - HandlerMethodArgumentResolver { +public class ResourceMetadataHandlerMethodArgumentResolver implements HandlerMethodArgumentResolver { + private final Repositories repositories; + private final ResourceMappings mappings; + + /** + * @param repositories must not be {@literal null}. + * @param mappings must not be {@literal null}. + */ + public ResourceMetadataHandlerMethodArgumentResolver(Repositories repositories, ResourceMappings mappings) { + + Assert.notNull(repositories, "Repositories must not be null!"); + Assert.notNull(mappings, "ResourceMappings must not be null!"); + + this.repositories = repositories; + this.mappings = mappings; + } + + /* + * (non-Javadoc) + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#supportsParameter(org.springframework.core.MethodParameter) + */ @Override public boolean supportsParameter(MethodParameter parameter) { return isAssignable(parameter.getParameterType(), RepositoryInformation.class); } + /* + * (non-Javadoc) + * @see org.springframework.web.method.support.HandlerMethodArgumentResolver#resolveArgument(org.springframework.core.MethodParameter, org.springframework.web.method.support.ModelAndViewContainer, org.springframework.web.context.request.NativeWebRequest, org.springframework.web.bind.support.WebDataBinderFactory) + */ @Override - public RepositoryInformation resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, + public ResourceMetadata resolveArgument(MethodParameter parameter, ModelAndViewContainer mavContainer, NativeWebRequest webRequest, WebDataBinderFactory binderFactory) throws Exception { HttpServletRequest request = webRequest.getNativeRequest(HttpServletRequest.class); @@ -60,4 +87,20 @@ public class RepositoryInformationHandlerMethodArgumentResolver extends Reposito return findRepositoryInfoFor(parts[0]); } + + private ResourceMetadata findRepositoryInfoFor(String pathSegment) { + + if (!hasText(pathSegment)) { + return null; + } + + for (Class domainType : repositories) { + ResourceMetadata mapping = mappings.getMappingFor(domainType); + if (pathSegment.equals(mapping.getPath()) && mapping.isExported()) { + return mapping; + } + } + + return null; + } } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityToJsonSchemaConverter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityToJsonSchemaConverter.java index 28465b8ca..7997555b1 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityToJsonSchemaConverter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/PersistentEntityToJsonSchemaConverter.java @@ -3,6 +3,7 @@ package org.springframework.data.rest.webmvc.json; import static org.springframework.data.rest.webmvc.json.PersistentEntityJackson2Module.*; import static org.springframework.util.StringUtils.*; +import java.net.URI; import java.util.ArrayList; import java.util.HashSet; import java.util.List; @@ -11,7 +12,6 @@ import java.util.Set; import javax.annotation.Nonnull; import javax.validation.constraints.NotNull; -import org.springframework.beans.factory.InitializingBean; import org.springframework.core.convert.TypeDescriptor; import org.springframework.core.convert.converter.ConditionalGenericConverter; import org.springframework.data.mapping.Association; @@ -19,40 +19,53 @@ import org.springframework.data.mapping.AssociationHandler; import org.springframework.data.mapping.PersistentEntity; import org.springframework.data.mapping.PersistentProperty; import org.springframework.data.mapping.PropertyHandler; +import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.repository.annotation.Description; import org.springframework.data.rest.repository.mapping.ResourceMappings; import org.springframework.data.rest.repository.mapping.ResourceMetadata; -import org.springframework.data.rest.repository.support.RepositoryInformationSupport; import org.springframework.data.rest.webmvc.support.RepositoryLinkBuilder; import org.springframework.hateoas.Link; /** * @author Jon Brisbin */ -public class PersistentEntityToJsonSchemaConverter extends RepositoryInformationSupport implements - ConditionalGenericConverter, InitializingBean { +public class PersistentEntityToJsonSchemaConverter implements ConditionalGenericConverter { private static final TypeDescriptor STRING_TYPE = TypeDescriptor.valueOf(String.class); private static final TypeDescriptor SCHEMA_TYPE = TypeDescriptor.valueOf(JsonSchema.class); - private Set convertiblePairs = new HashSet(); - private ResourceMappings mappings; - @Override - public void afterPropertiesSet() throws Exception { + private final Set convertiblePairs = new HashSet(); + private final ResourceMappings mappings; + private final Repositories repositories; + + /** + * @param repositories must not be {@literal null}. + * @param mappings must not be {@literal null}. + */ + public PersistentEntityToJsonSchemaConverter(Repositories repositories, ResourceMappings mappings) { + + this.repositories = repositories; + this.mappings = mappings; for (Class domainType : repositories) { convertiblePairs.add(new ConvertiblePair(domainType, JsonSchema.class)); } - - this.mappings = new ResourceMappings(config, repositories); } + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.ConditionalConverter#matches(org.springframework.core.convert.TypeDescriptor, org.springframework.core.convert.TypeDescriptor) + */ @Override public boolean matches(TypeDescriptor sourceType, TypeDescriptor targetType) { - return (Class.class.isAssignableFrom(sourceType.getType()) && JsonSchema.class.isAssignableFrom(targetType - .getType())); + return Class.class.isAssignableFrom(sourceType.getType()) + && JsonSchema.class.isAssignableFrom(targetType.getType()); } + /* + * (non-Javadoc) + * @see org.springframework.core.convert.converter.GenericConverter#getConvertibleTypes() + */ @Override public Set getConvertibleTypes() { return convertiblePairs; @@ -68,8 +81,8 @@ public class PersistentEntityToJsonSchemaConverter extends RepositoryInformation PersistentEntity persistentEntity = repositories.getPersistentEntity((Class) source); final ResourceMetadata metadata = mappings.getMappingFor(persistentEntity.getClass()); - String entityDesc = persistentEntity.getType().isAnnotationPresent(Description.class) ? ((Description) persistentEntity - .getType().getAnnotation(Description.class)).value() : null; + String entityDesc = persistentEntity.getType().isAnnotationPresent(Description.class) ? persistentEntity.getType() + .getAnnotation(Description.class).value() : null; final JsonSchema jsonSchema = new JsonSchema(persistentEntity.getName(), entityDesc); persistentEntity.doWithProperties(new PropertyHandler() { @@ -77,10 +90,10 @@ public class PersistentEntityToJsonSchemaConverter extends RepositoryInformation public void doWithPersistentProperty(PersistentProperty persistentProperty) { Class propertyType = persistentProperty.getType(); String type = uncapitalize(propertyType.getSimpleName()); - boolean notNull = (persistentProperty.getField().isAnnotationPresent(Nonnull.class) || persistentProperty - .getGetter().isAnnotationPresent(Nonnull.class)) - || (persistentProperty.getField().isAnnotationPresent(NotNull.class) || persistentProperty.getGetter() - .isAnnotationPresent(NotNull.class)); + boolean notNull = persistentProperty.getField().isAnnotationPresent(Nonnull.class) + || persistentProperty.getGetter().isAnnotationPresent(Nonnull.class) + || persistentProperty.getField().isAnnotationPresent(NotNull.class) + || persistentProperty.getGetter().isAnnotationPresent(NotNull.class); String desc = persistentProperty.getField().isAnnotationPresent(Description.class) ? persistentProperty .getField().getAnnotation(Description.class).value() : persistentProperty.getGetter().isAnnotationPresent( Description.class) ? persistentProperty.getGetter().getAnnotation(Description.class).value() : null; @@ -98,14 +111,21 @@ public class PersistentEntityToJsonSchemaConverter extends RepositoryInformation final List links = new ArrayList(); persistentEntity.doWithAssociations(new AssociationHandler() { + + /* + * (non-Javadoc) + * @see org.springframework.data.mapping.AssociationHandler#doWithAssociation(org.springframework.data.mapping.Association) + */ @Override public void doWithAssociation(Association association) { + PersistentProperty persistentProperty = association.getInverse(); + if (!metadata.isMapped(persistentProperty)) { return; } - RepositoryLinkBuilder builder = new RepositoryLinkBuilder(metadata, config.getBaseUri()).slash("{id}"); + RepositoryLinkBuilder builder = new RepositoryLinkBuilder(metadata, URI.create("{id}")); maybeAddAssociationLink(builder, mappings, persistentProperty, links); } }); @@ -114,5 +134,4 @@ public class PersistentEntityToJsonSchemaConverter extends RepositoryInformation return jsonSchema; } - }