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.
This commit is contained in:
Greg Turnquist
2015-01-21 16:39:54 -06:00
committed by Oliver Gierke
parent 67e368e3c7
commit 180d0ce3f4
9 changed files with 274 additions and 23 deletions

View File

@@ -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<Descriptor> buildPropertyDescriptors(Class<?> type, final String baseRel) {
PersistentEntity<?, ?> entity = persistentEntities.getPersistentEntity(type);
final PersistentEntity<?, ?> entity = persistentEntities.getPersistentEntity(type);
final List<Descriptor> propertyDescriptors = new ArrayList<Descriptor>();
final JacksonMetadata jackson = new JacksonMetadata(mapper, type);
final PropertyMappings propertyMappings = new PropertyMappings(mappings);
@@ -333,30 +334,28 @@ public class RootResourceInformationToAlpsDescriptorConverter {
public void doWithAssociation(Association<? extends PersistentProperty<?>> 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<Descriptor> 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());
}

View File

@@ -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<BeanPropertyDefinition> {
@@ -74,6 +75,15 @@ public class JacksonMetadata implements Iterable<BeanPropertyDefinition> {
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()

View File

@@ -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();
}
}

View File

@@ -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));
}
}

View File

@@ -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());
}
}

View File

@@ -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;
}
}

View File

@@ -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<Item, Long> {
}

View File

@@ -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;
}
}

View File

@@ -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<User, Long> {
}