diff --git a/doc/configuring_path.md b/doc/configuring_path.md index 14378bb0d..7224397ff 100644 --- a/doc/configuring_path.md +++ b/doc/configuring_path.md @@ -98,3 +98,34 @@ This would result in a link value of: } ] } +### Hiding certain Repositories, query methods, or fields + +You may not want a certain Repository, a query method on a Repository, or a field of your entity to be exported at all. To tell the exporter to not export your these items, annotate them with `@RestResource` and set `exported = false`. + +For example, to skip exporting a Repository: + + @RestResource(exported = false) + public interface PersonRepository extends CrudRepository { + } + +To skip exporting a query method: + + @RestResource(path = "people", rel = "people") + public interface PersonRepository extends CrudRepository { + + @RestResource(exported = false) + public List findByName(String name); + + } + +Or to skip exporting a field: + + @Entity + @RestResource(exported = false) + public class Person { + @Id @GeneratedValue private Long id; + @OneToMany + @RestResource(exported = false) + private Map profiles; + } + diff --git a/doc/main_wiki.md b/doc/main_wiki.md index 7a690344f..cdcbc8cf7 100644 --- a/doc/main_wiki.md +++ b/doc/main_wiki.md @@ -104,6 +104,8 @@ If you have a JPA entity in your domain model that looks like... ...your PersonRepository will by default be declared in the ApplicationContext with a bean name of "personRepository". The web exporter will strip the word "Repository" from it and expose a resource named "person". The resulting URL of this repository (assuming the exporter webapp is deployed at context path `/data` in your servlet container) will be `http://localhost:8080/data/person`. +You can configure under what path, or whether a resource is exported at all, by using the `@RestResource` annotation. Details are here: [Configuring the REST URL path](../wiki/Configuring-the-REST-URL-path) + ### Discoverability The Web Exporter implements some aspects of the [HATEOS](http://en.wikipedia.org/wiki/HATEOAS) methodology. That means all the services of the web exporter are discoverable and exposed to the client using links. diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java index a407d72f5..6bb74b3fb 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/RepositoryExporter.java @@ -7,7 +7,6 @@ import java.util.Map; import java.util.Set; import org.springframework.beans.BeansException; -import org.springframework.beans.factory.BeanFactory; import org.springframework.beans.factory.InitializingBean; import org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; @@ -124,19 +123,21 @@ public abstract class RepositoryExporter, E exte repositories = new Repositories(applicationContext); repositoryMetadata = new HashMap(); for (Class domainType : repositories) { - if (!exportOnlyTheseClasses.isEmpty() && !exportOnlyTheseClasses.contains(domainType.getName())) { - // Don't export this domain type - continue; + if (exportOnlyTheseClasses.isEmpty() || !exportOnlyTheseClasses.contains(domainType.getName())) { + Class repoClass = repositories.getRepositoryInformationFor(domainType).getRepositoryInterface(); + String name = StringUtils.uncapitalize(repoClass.getSimpleName().replaceAll("Repository", "")); + RestResource resourceAnno = repoClass.getAnnotation(RestResource.class); + boolean exported = true; + if (null != resourceAnno) { + if (StringUtils.hasText(resourceAnno.path())) { + name = resourceAnno.path(); + } + exported = resourceAnno.exported(); + } + if (exported) { + repositoryMetadata.put(name, createRepositoryMetadata(name, domainType, repoClass, repositories)); + } } - Class repoClass = repositories.getRepositoryInformationFor(domainType).getRepositoryInterface(); - String name; - RestResource pathSeg = repoClass.getAnnotation(RestResource.class); - if (null != pathSeg) { - name = pathSeg.path(); - } else { - name = StringUtils.uncapitalize(repoClass.getSimpleName().replaceAll("Repository", "")); - } - repositoryMetadata.put(name, createRepositoryMetadata(name, domainType, repoClass, repositories)); } } } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestResource.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestResource.java index a434d4ac5..c34b990e7 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestResource.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/annotation/RestResource.java @@ -13,6 +13,7 @@ import java.lang.annotation.Target; * @author Jon Brisbin */ @Target({ + ElementType.FIELD, ElementType.METHOD, ElementType.TYPE }) @@ -20,7 +21,9 @@ import java.lang.annotation.Target; @Inherited public @interface RestResource { - String path(); + boolean exported() default true; + + String path() default ""; String rel() default ""; diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaEntityMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaEntityMetadata.java index ae0b78d0d..90ff4e5dc 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaEntityMetadata.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaEntityMetadata.java @@ -1,5 +1,6 @@ package org.springframework.data.rest.repository.jpa; +import java.lang.reflect.Field; import java.util.HashMap; import java.util.Map; import javax.persistence.metamodel.Attribute; @@ -9,6 +10,8 @@ import javax.persistence.metamodel.SingularAttribute; import org.springframework.data.repository.support.Repositories; import org.springframework.data.rest.repository.EntityMetadata; +import org.springframework.data.rest.repository.annotation.RestResource; +import org.springframework.util.ReflectionUtils; /** * @author Jon Brisbin @@ -30,15 +33,25 @@ public class JpaEntityMetadata implements EntityMetadata { } for (Attribute attr : entityType.getAttributes()) { - Class attrType = (attr instanceof PluralAttribute - ? ((PluralAttribute) attr).getElementType().getJavaType() - : attr.getJavaType()); - if (repositories.hasRepositoryFor(attrType)) { - linkedAttributes.put(attr.getName(), new JpaAttributeMetadata(entityType, attr)); - } else { - if (!(attr instanceof SingularAttribute && ((SingularAttribute) attr).isId()) - && !(attr instanceof SingularAttribute && ((SingularAttribute) attr).isVersion())) { - embeddedAttributes.put(attr.getName(), new JpaAttributeMetadata(entityType, attr)); + boolean exported = true; + Field field = ReflectionUtils.findField(type, attr.getJavaMember().getName()); + if (null != field) { + RestResource fieldResourceAnno = field.getAnnotation(RestResource.class); + if (null != fieldResourceAnno) { + exported = fieldResourceAnno.exported(); + } + } + if (exported) { + Class attrType = (attr instanceof PluralAttribute + ? ((PluralAttribute) attr).getElementType().getJavaType() + : attr.getJavaType()); + if (repositories.hasRepositoryFor(attrType)) { + linkedAttributes.put(attr.getName(), new JpaAttributeMetadata(entityType, attr)); + } else { + if (!(attr instanceof SingularAttribute && ((SingularAttribute) attr).isId()) + && !(attr instanceof SingularAttribute && ((SingularAttribute) attr).isVersion())) { + embeddedAttributes.put(attr.getName(), new JpaAttributeMetadata(entityType, attr)); + } } } } diff --git a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryMetadata.java b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryMetadata.java index b424c6c99..52fab5ad2 100644 --- a/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryMetadata.java +++ b/spring-data-rest-repository/src/main/java/org/springframework/data/rest/repository/jpa/JpaRepositoryMetadata.java @@ -52,11 +52,17 @@ public class JpaRepositoryMetadata implements RepositoryMetadata */ @@ -16,6 +18,7 @@ public class Family { @GeneratedValue private Long id; private String surname; + @RestResource(exported = false) @OneToMany private List members; diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java index 000da40b5..479111335 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/RepositoryRestController.java @@ -299,7 +299,9 @@ public class RepositoryRestController URI path = buildUri(baseUri, repository, "search", entry.getKey()); RestResource resourceAnno = entry.getValue().method().getAnnotation(RestResource.class); if (null != resourceAnno) { - path = buildUri(baseUri, repository, "search", resourceAnno.path()); + if (StringUtils.hasText(resourceAnno.path())) { + path = buildUri(baseUri, repository, "search", resourceAnno.path()); + } if (StringUtils.hasText(resourceAnno.rel())) { rel = repoMeta.rel() + "." + resourceAnno.rel(); } @@ -1069,10 +1071,7 @@ public class RepositoryRestController } for (String attrName : entityMetadata.linkedAttributes().keySet()) { - URI uri = UriComponentsBuilder.fromUri(baseUri) - .pathSegment(attrName) - .build() - .toUri(); + URI uri = buildUri(baseUri, attrName); Link l = new SimpleLink(repoRel + "." + entity.getClass().getSimpleName() + "." + attrName, uri); List links = (List) entityDto.get(LINKS); if (null == links) { diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Person.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Person.java index 7b2c46a48..f6e14890c 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Person.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/Person.java @@ -8,6 +8,8 @@ import javax.persistence.Id; import javax.persistence.OneToMany; import javax.persistence.Version; +import org.springframework.data.rest.repository.annotation.RestResource; + /** * @author Jon Brisbin */ diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/UuidTestRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/UuidTestRepository.java index 8c5f0a4b3..205c6214a 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/UuidTestRepository.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/test/webmvc/UuidTestRepository.java @@ -3,9 +3,11 @@ package org.springframework.data.rest.test.webmvc; import java.util.UUID; import org.springframework.data.repository.CrudRepository; +import org.springframework.data.rest.repository.annotation.RestResource; /** * @author Jon Brisbin */ +@RestResource(exported = false) public interface UuidTestRepository extends CrudRepository { }