From 18cc22738c0cd39e02b7fb40a31d4db2a0011048 Mon Sep 17 00:00:00 2001 From: Greg Turnquist Date: Fri, 22 Jan 2016 09:33:40 -0600 Subject: [PATCH] DATAREST-753 - Added support for Groovy-based domain objects. Groovy-based objects inherit from GroovyObject. This brings along attributes that Spring Data REST may try to parse when generating metadata like JSON Schema. Also verify ALPS is supported. We now exclude those artificial properties from being exposed in the schema as well as causing issues. Original pull request: #206. --- spring-data-rest-webmvc/pom.xml | 11 +++ ...PersistentEntityToJsonSchemaConverter.java | 21 +++-- .../RepositoryControllerIntegrationTests.java | 4 +- .../alps/AlpsControllerIntegrationTests.java | 19 +++- .../groovy/SimulatedGroovyDomainClass.java | 88 +++++++++++++++++++ .../SimulatedGroovyDomainClassRepository.java | 28 ++++++ ...tEntityToJsonSchemaConverterUnitTests.java | 16 +++- .../groovy/SimulatedGroovyDomainClass.java | 81 +++++++++++++++++ .../SimulatedGroovyDomainClassRepository.java | 28 ++++++ 9 files changed, 286 insertions(+), 10 deletions(-) create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/groovy/SimulatedGroovyDomainClass.java create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/groovy/SimulatedGroovyDomainClassRepository.java create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/groovy/SimulatedGroovyDomainClass.java create mode 100644 spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/groovy/SimulatedGroovyDomainClassRepository.java diff --git a/spring-data-rest-webmvc/pom.xml b/spring-data-rest-webmvc/pom.xml index 09eba5e7f..ed5652daa 100644 --- a/spring-data-rest-webmvc/pom.xml +++ b/spring-data-rest-webmvc/pom.xml @@ -20,6 +20,7 @@ 2.0.9 2.0.2.1 4.0.1.RELEASE + 2.3.7 @@ -122,6 +123,13 @@ test + + org.codehaus.groovy + groovy-all + ${groovy.version} + test + + @@ -481,6 +489,9 @@ target/generated-sources/annotations org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor true + + org.springframework.data.rest.webmvc.mongodb.groovy,groovy.lang + 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 4d02b0d8e..613bffb2b 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 @@ -1,5 +1,5 @@ /* - * Copyright 2012-2015 the original author or authors. + * Copyright 2012-2016 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. @@ -54,6 +54,7 @@ import org.springframework.util.StringUtils; import com.fasterxml.jackson.databind.JsonSerializer; import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.introspect.AnnotatedMember; import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; /** @@ -61,6 +62,7 @@ import com.fasterxml.jackson.databind.introspect.BeanPropertyDefinition; * * @author Jon Brisbin * @author Oliver Gierke + * @author Greg Turnquist */ public class PersistentEntityToJsonSchemaConverter implements ConditionalGenericConverter { @@ -186,18 +188,25 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric } } - TypeInformation propertyType = persistentProperty == null - ? ClassTypeInformation.from(definition.getPrimaryMember().getRawType()) + boolean isPersistentPropertyNull = persistentProperty == null; + + AnnotatedMember primaryMember = definition.getPrimaryMember(); + if (primaryMember == null) { // Ignore bean definitions with neither a getter nor a setter + continue; + } + + TypeInformation propertyType = isPersistentPropertyNull + ? ClassTypeInformation.from(primaryMember.getRawType()) : persistentProperty.getTypeInformation(); TypeInformation actualPropertyType = propertyType.getActualType(); Class rawPropertyType = propertyType.getType(); JsonSchemaFormat format = configuration.getMetadataConfiguration().getSchemaFormatFor(rawPropertyType); - ResourceDescription description = persistentProperty == null + ResourceDescription description = isPersistentPropertyNull ? jackson.getFallbackDescription(metadata, definition) : getDescriptionFor(persistentProperty, metadata); JsonSchemaProperty property = getSchemaProperty(definition, propertyType, description); - boolean isSyntheticProperty = persistentProperty == null; + boolean isSyntheticProperty = isPersistentPropertyNull; boolean isNotWritable = !isSyntheticProperty && !persistentProperty.isWritable(); boolean isJacksonReadOnly = !isSyntheticProperty && jackson.isReadOnly(persistentProperty); @@ -224,7 +233,7 @@ public class PersistentEntityToJsonSchemaConverter implements ConditionalGeneric continue; } - if (persistentProperty == null) { + if (isPersistentPropertyNull) { registrar.register(property, actualPropertyType); continue; } 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 c0805c562..3081e49a2 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-2015 the original author or authors. + * Copyright 2014-2016 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. @@ -66,7 +66,7 @@ public class RepositoryControllerIntegrationTests extends AbstractControllerInte RepositoryLinksResource resource = controller.listRepositories().getBody(); - assertThat(resource.getLinks(), hasSize(7)); + assertThat(resource.getLinks(), hasSize(8)); assertThat(resource.hasLink("people"), is(true)); assertThat(resource.hasLink("orders"), 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 fa66a3543..c870f6a35 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-2015 the original author or authors. + * Copyright 2014-2016 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. @@ -17,6 +17,7 @@ package org.springframework.data.rest.webmvc.alps; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; +import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; import org.junit.After; @@ -199,4 +200,20 @@ public class AlpsControllerIntegrationTests extends AbstractControllerIntegratio "$.alps.descriptors[?(@.id == 'person-representation')].descriptors[?(@.name == 'gender')][0].doc.value", is("Male, Female, Undefined"))); } + + /** + * @see DATAREST-753 + */ + @Test + public void alpsCanHandleGroovyDomainObjects() throws Exception { + + Link profileLink = client.discoverUnique("profile"); + Link groovyDomainObjectLink = client.discoverUnique(profileLink, "simulatedGroovyDomainClasses"); + client.follow(groovyDomainObjectLink)// + .andExpect(jsonPath( + "$.alps.descriptors[?(@.id == 'simulatedGroovyDomainClass-representation')][0].descriptors[0].name", + is("name") + )); + + } } diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/groovy/SimulatedGroovyDomainClass.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/groovy/SimulatedGroovyDomainClass.java new file mode 100644 index 000000000..2c739f195 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/groovy/SimulatedGroovyDomainClass.java @@ -0,0 +1,88 @@ +/* + * Copyright 2016 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.groovy; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.springframework.data.mongodb.core.mapping.Document; + +import groovy.lang.GroovyObject; +import groovy.lang.MetaClass; + +/** + * Simulates a Groovy domain object by extending {@link GroovyObject}. + * + * @author Greg Turnquist + * @see DATAREST-754 + */ +@Entity +public class SimulatedGroovyDomainClass implements GroovyObject { + + @Id @GeneratedValue private Long id; + private String name; + + 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; + } + + protected SimulatedGroovyDomainClass() {} + + // + // The following fields don't actually have to be implemented since the test cases don't + // make any Groovy calls. This just simulates the structure of a Groovy object to + // verify proper handling. + // + + @Override + public Object invokeMethod(String s, Object o) { + return null; + } + + @Override + public Object getProperty(String s) { + return null; + } + + @Override + public void setProperty(String s, Object o) { + + } + + @Override + public MetaClass getMetaClass() { + return null; + } + + @Override + public void setMetaClass(MetaClass metaClass) { + + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/groovy/SimulatedGroovyDomainClassRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/groovy/SimulatedGroovyDomainClassRepository.java new file mode 100644 index 000000000..90f7bd8f8 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/jpa/groovy/SimulatedGroovyDomainClassRepository.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 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.groovy; + +import org.springframework.data.repository.CrudRepository; + +/** + * Simulates a repository built on a Groovy domain object. + * + * @author Greg Turnquist + * @see DATAREST-754 + */ +public interface SimulatedGroovyDomainClassRepository extends CrudRepository { + +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityToJsonSchemaConverterUnitTests.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityToJsonSchemaConverterUnitTests.java index 8579a3917..6b95964a4 100644 --- a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityToJsonSchemaConverterUnitTests.java +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/json/PersistentEntityToJsonSchemaConverterUnitTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2014-2015 the original author or authors. + * Copyright 2014-2016 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. @@ -43,6 +43,7 @@ import org.springframework.data.rest.webmvc.mongodb.Profile; import org.springframework.data.rest.webmvc.mongodb.User; import org.springframework.data.rest.webmvc.mongodb.User.EmailAddress; import org.springframework.data.rest.webmvc.mongodb.User.TypeWithPattern; +import org.springframework.data.rest.webmvc.mongodb.groovy.SimulatedGroovyDomainClass; import org.springframework.test.context.ContextConfiguration; import org.springframework.test.context.junit4.SpringJUnit4ClassRunner; @@ -73,6 +74,7 @@ public class PersistentEntityToJsonSchemaConverterUnitTests { config.getMetadataConfiguration().registerJsonSchemaFormat(JsonSchemaFormat.EMAIL, EmailAddress.class); config.getMetadataConfiguration().registerFormattingPatternFor("[A-Z]+", TypeWithPattern.class); + config.getMetadataConfiguration().registerJsonSchemaFormat(JsonSchemaFormat.EMAIL); config.exposeIdsFor(Profile.class); } @@ -159,6 +161,18 @@ public class PersistentEntityToJsonSchemaConverterUnitTests { assertConstraints(User.class, constraints); } + /** + * @see DATAREST-754 + */ + @Test + public void handlesGroovyDomainObjects() { + + List constraints = new ArrayList(); + constraints.add(new Constraint("$.properties.name", is(notNullValue()), "Has descriptor for name property")); + + assertConstraints(SimulatedGroovyDomainClass.class, constraints); + } + @SuppressWarnings("unchecked") private void assertConstraints(Class type, Iterable constraints) { diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/groovy/SimulatedGroovyDomainClass.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/groovy/SimulatedGroovyDomainClass.java new file mode 100644 index 000000000..2701bf6ee --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/groovy/SimulatedGroovyDomainClass.java @@ -0,0 +1,81 @@ +/* + * Copyright 2016 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.mongodb.groovy; + +import org.springframework.data.mongodb.core.mapping.Document; + +import groovy.lang.GroovyObject; +import groovy.lang.MetaClass; + +/** + * Simulates a Groovy domain object by extending {@link GroovyObject}. + * + * @author Greg Turnquist + * @see DATAREST-754 + */ +@Document +public class SimulatedGroovyDomainClass implements GroovyObject { + + private String id; + private String name; + + public String getId() { + return id; + } + + public void setId(String id) { + this.id = id; + } + + public String getName() { + return name; + } + + public void setName(String name) { + this.name = name; + } + + // + // The following fields don't actually have to be implemented since the test cases don't + // make any Groovy calls. This just simulates the structure of a Groovy object to + // verify proper handling. + // + + @Override + public Object invokeMethod(String s, Object o) { + return null; + } + + @Override + public Object getProperty(String s) { + return null; + } + + @Override + public void setProperty(String s, Object o) { + + } + + @Override + public MetaClass getMetaClass() { + return null; + } + + @Override + public void setMetaClass(MetaClass metaClass) { + + } +} diff --git a/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/groovy/SimulatedGroovyDomainClassRepository.java b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/groovy/SimulatedGroovyDomainClassRepository.java new file mode 100644 index 000000000..b11e50699 --- /dev/null +++ b/spring-data-rest-webmvc/src/test/java/org/springframework/data/rest/webmvc/mongodb/groovy/SimulatedGroovyDomainClassRepository.java @@ -0,0 +1,28 @@ +/* + * Copyright 2016 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.mongodb.groovy; + +import org.springframework.data.repository.CrudRepository; + +/** + * Simulates a repository built on a Groovy domain object. + * + * @author Greg Turnquist + * @see DATAREST-754 + */ +public interface SimulatedGroovyDomainClassRepository extends CrudRepository { + +}