From 180d0ce3f43a993874edcbb30082d0da3247207e Mon Sep 17 00:00:00 2001 From: Greg Turnquist Date: Wed, 21 Jan 2015 16:39:54 -0600 Subject: [PATCH] DATAREST-463 - Hide ignored attributes from ALPS metadata. If @JsonIgnore is applied to either a field or a getter method for an association, then do not expose that property's metadata down from ALPS. Original pull request: #160. --- ...eInformationToAlpsDescriptorConverter.java | 39 ++++----- .../rest/webmvc/json/JacksonMetadata.java | 10 +++ .../rest/webmvc/mapping/AssociationLinks.java | 3 + .../RepositoryControllerIntegrationTests.java | 9 +- .../alps/AlpsControllerIntegrationTests.java | 25 +++++- .../data/rest/webmvc/jpa/Item.java | 87 +++++++++++++++++++ .../data/rest/webmvc/jpa/ItemRepository.java | 25 ++++++ .../data/rest/webmvc/jpa/User.java | 72 +++++++++++++++ .../data/rest/webmvc/jpa/UserRepository.java | 27 ++++++ 9 files changed, 274 insertions(+), 23 deletions(-) create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Item.java create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/ItemRepository.java create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/User.java create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/UserRepository.java diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/RootResourceInformationToAlpsDescriptorConverter.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/RootResourceInformationToAlpsDescriptorConverter.java index 586398442..6918dcbd3 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/RootResourceInformationToAlpsDescriptorConverter.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/alps/RootResourceInformationToAlpsDescriptorConverter.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -71,6 +71,7 @@ import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; * Converter to create Alps {@link Descriptor} instances for a {@link RootResourceInformation}. * * @author Oliver Gierke + * @author Greg Turnquist */ public class RootResourceInformationToAlpsDescriptorConverter { @@ -302,7 +303,7 @@ public class RootResourceInformationToAlpsDescriptorConverter { private List buildPropertyDescriptors(Class type, final String baseRel) { - PersistentEntity entity = persistentEntities.getPersistentEntity(type); + final PersistentEntity entity = persistentEntities.getPersistentEntity(type); final List propertyDescriptors = new ArrayList(); final JacksonMetadata jackson = new JacksonMetadata(mapper, type); final PropertyMappings propertyMappings = new PropertyMappings(mappings); @@ -333,30 +334,28 @@ public class RootResourceInformationToAlpsDescriptorConverter { public void doWithAssociation(Association> association) { PersistentProperty property = association.getInverse(); + final Class propertyType = property.getType(); + + if (!jackson.isExportableProperty(property)) { + return; + } + + if (!associationLinks.isLinkableAssociation(property)) { + return; + } + ResourceMapping mapping = propertyMappings.getMappingFor(property); DescriptorBuilder builder = descriptor().// name(mapping.getRel()).doc(getDocFor(mapping.getDescription())); - if (associationLinks.isLinkableAssociation(property)) { + ResourceMetadata targetTypeMapping = mappings.getMappingFor(property.getActualType()); + String localPath = targetTypeMapping.getRel().concat("#").concat(targetTypeMapping.getItemResourceRel()); + Link link = ControllerLinkBuilder.linkTo(AlpsController.class).slash(localPath).withSelfRel(); - ResourceMetadata targetTypeMapping = mappings.getMappingFor(property.getActualType()); - String localPath = targetTypeMapping.getRel().concat("#").concat(targetTypeMapping.getItemResourceRel()); - Link link = ControllerLinkBuilder.linkTo(AlpsController.class).slash(localPath).withSelfRel(); - - builder.// - type(Type.SAFE).// - rt(link.getHref()); - - } else { - - List nestedDescriptors = buildPropertyDescriptors(property.getActualType(), baseRel.concat(".") - .concat(mapping.getRel())); - - builder = builder.// - type(Type.SEMANTIC).// - descriptors(nestedDescriptors); - } + builder.// + type(Type.SAFE).// + rt(link.getHref()); propertyDescriptors.add(builder.build()); } diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/JacksonMetadata.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/JacksonMetadata.java index 3ee0689ee..bd5f264c3 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/JacksonMetadata.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/json/JacksonMetadata.java @@ -31,6 +31,7 @@ import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; * Value object to abstract Jackson based bean metadata of a given type. * * @author Oliver Gierke + * @author Greg Turnquist */ public class JacksonMetadata implements Iterable { @@ -74,6 +75,15 @@ public class JacksonMetadata implements Iterable { return null; } + /** + * Check if a given property for a type is avaiaable to be exported, i.e. serialized via Jackson + * @param property + * @return + */ + public boolean isExportableProperty(PersistentProperty property) { + return getDefinitionFor(property) != null; + } + /* * (non-Javadoc) * @see java.lang.Iterable#iterator() diff --git a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/mapping/AssociationLinks.java b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/mapping/AssociationLinks.java index df7e5ab79..d23309c6c 100644 --- a/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/mapping/AssociationLinks.java +++ b/spring-data-rest-webmvc/src/main/java/org/springframework/data/rest/webmvc/mapping/AssociationLinks.java @@ -31,6 +31,7 @@ import org.springframework.util.Assert; * A value object to for {@link Link}s representing an association. * * @author Oliver Gierke + * @author Greg Turnquist * @since 2.1 */ public class AssociationLinks { @@ -90,6 +91,7 @@ public class AssociationLinks { return false; } +// ResourceMetadata metadata = mappings.getMappingFor(property.getActualType()); ResourceMetadata metadata = mappings.getMappingFor(property.getOwner().getType()); if (metadata != null && !metadata.isExported(property)) { @@ -99,4 +101,5 @@ public class AssociationLinks { metadata = mappings.getMappingFor(property.getActualType()); return metadata == null ? false : metadata.isExported(); } + } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryControllerIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryControllerIntegrationTests.java index 4e74f3488..1842f39bc 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryControllerIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/RepositoryControllerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import org.springframework.transaction.annotation.Transactional; * Integration tests for {@link RepositoryController}. * * @author Oliver Gierke + * @author Greg Turnquist */ @ContextConfiguration(classes = JpaRepositoryConfig.class) @Transactional @@ -57,17 +58,21 @@ public class RepositoryControllerIntegrationTests extends AbstractControllerInte assertThat(controller.headForRepositories().getStatusCode(), is(HttpStatus.NO_CONTENT)); } + /** + * @see DATAREST-160, DATAREST-333, DATAREST-463 + */ @Test public void exposesLinksToRepositories() { RepositoryLinksResource resource = controller.listRepositories().getBody(); - assertThat(resource.getLinks(), hasSize(5)); + assertThat(resource.getLinks(), hasSize(6)); assertThat(resource.hasLink("people"), is(true)); assertThat(resource.hasLink("orders"), is(true)); assertThat(resource.hasLink("addresses"), is(true)); assertThat(resource.hasLink("books"), is(true)); assertThat(resource.hasLink("authors"), is(true)); + assertThat(resource.hasLink("items"), is(true)); } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/alps/AlpsControllerIntegrationTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/alps/AlpsControllerIntegrationTests.java index 5f4966f21..d55d1af7a 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/alps/AlpsControllerIntegrationTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/alps/AlpsControllerIntegrationTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014 the original author or authors. + * Copyright 2014-2015 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,6 +44,7 @@ import org.springframework.web.context.WebApplicationContext; * Integration tests for {@link AlpsController}. * * @author Oliver Gierke + * @author Greg Turnquist */ @WebAppConfiguration @ContextConfiguration(classes = { JpaRepositoryConfig.class, AlpsControllerIntegrationTests.Config.class }) @@ -90,6 +91,7 @@ public class AlpsControllerIntegrationTests extends AbstractControllerIntegratio assertThat(discoverUnique(profileLink.getHref(), "orders"), is(notNullValue())); assertThat(discoverUnique(profileLink.getHref(), "people"), is(notNullValue())); + assertThat(discoverUnique(profileLink.getHref(), "items"), is(notNullValue())); } /** @@ -107,6 +109,26 @@ public class AlpsControllerIntegrationTests extends AbstractControllerIntegratio andExpect(jsonPath("$.descriptors[*].name", hasItems("people", "person"))); } + /** + * @see DATAREST-463 + */ + @Test + public void verifyThatAttributesIgnoredDontAppearInAlps() throws Exception { + + Link profileLink = discoverUnique("/", "profile"); + Link usersLink = discoverUnique(profileLink.getHref(), "users"); + Link itemsLink = discoverUnique(profileLink.getHref(), "items"); + + assertThat(usersLink, is(nullValue())); + + mvc.perform(get(itemsLink.getHref())).// + andDo(print()).// + andExpect(jsonPath("$.descriptors[*].descriptors[*].name", hasItems("id", "name"))).// + andExpect(jsonPath("$.descriptors[*].descriptors[*].name", + everyItem(not(isIn(new String[]{"owner", "manager", "curator"}))))); + + } + private Link discoverUnique(String href, String rel) throws Exception { MockHttpServletResponse response = mvc.perform(get(href)).// @@ -116,4 +138,5 @@ public class AlpsControllerIntegrationTests extends AbstractControllerIntegratio LinkDiscoverer discoverer = discoverers.getLinkDiscovererFor(MediaType.valueOf(response.getContentType())); return discoverer.findLinkWithRel(rel, response.getContentAsString()); } + } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Item.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Item.java new file mode 100644 index 000000000..c7b479ce6 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/Item.java @@ -0,0 +1,87 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.OneToOne; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * @author Greg Turnquist + * @see DATAREST-463 + */ +@Entity +public class Item { + + @Id @GeneratedValue + private Long id; + + private String name; + + @JsonIgnore + @OneToOne + private User owner; + + @OneToOne + private User manager; + + @OneToOne + private User curator; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public User getOwner() { + return owner; + } + + public void setOwner(User owner) { + this.owner = owner; + } + + @JsonIgnore + public User getManager() { + return manager; + } + + public void setManager(User manager) { + this.manager = manager; + } + + public User getCurator() { + return curator; + } + + public void setCurator(User curator) { + this.curator = curator; + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/ItemRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/ItemRepository.java new file mode 100644 index 000000000..5ae29bfd2 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/ItemRepository.java @@ -0,0 +1,25 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.jpa; + +import org.springframework.data.repository.CrudRepository; + +/** + * @author Greg Turnquist + * @see DATAREST-463 + */ +public interface ItemRepository extends CrudRepository { +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/User.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/User.java new file mode 100644 index 000000000..2a3f24fe7 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/User.java @@ -0,0 +1,72 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.jpa; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; + +import com.fasterxml.jackson.annotation.JsonIgnore; + +/** + * @author Greg Turnquist + * @see DATAREST-463 + */ +@Entity +public class User { + + @Id @GeneratedValue + private Long id; + + private String name; + + @JsonIgnore + private String password; + + private String[] roles; + + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + public String getPassword() { + return password; + } + + public void setPassword(String password) { + this.password = password; + } + + public String[] getRoles() { + return roles; + } + + public void setRoles(String... roles) { + this.roles = roles; + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/UserRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/UserRepository.java new file mode 100644 index 000000000..7771c47eb --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/UserRepository.java @@ -0,0 +1,27 @@ +/* + * Copyright 2015 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.data.rest.webmvc.jpa; + +import org.springframework.data.repository.CrudRepository; +import org.springframework.data.rest.core.annotation.RepositoryRestResource; + +/** + * @author Greg Turnquist + * @see DATAREST-463 + */ +@RepositoryRestResource(exported = false) +public interface UserRepository extends CrudRepository { +}