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.
This commit is contained in:
Greg Turnquist
2016-01-22 09:33:40 -06:00
committed by Oliver Gierke
parent 3c411d1261
commit 18cc22738c
9 changed files with 286 additions and 10 deletions

View File

@@ -20,6 +20,7 @@
<cassandra.version>2.0.9</cassandra.version>
<cassandraunit.version>2.0.2.1</cassandraunit.version>
<spring-security.version>4.0.1.RELEASE</spring-security.version>
<groovy.version>2.3.7</groovy.version>
</properties>
<dependencies>
@@ -122,6 +123,13 @@
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
<version>${groovy.version}</version>
<scope>test</scope>
</dependency>
</dependencies>
<profiles>
@@ -481,6 +489,9 @@
<outputDirectory>target/generated-sources/annotations</outputDirectory>
<processor>org.springframework.data.mongodb.repository.support.MongoAnnotationProcessor</processor>
<logOnlyOnError>true</logOnlyOnError>
<options>
<querydsl.excludedPackages>org.springframework.data.rest.webmvc.mongodb.groovy,groovy.lang</querydsl.excludedPackages>
</options>
</configuration>
</execution>
</executions>

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -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<Constraint> constraints = new ArrayList<Constraint>();
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<Constraint> constraints) {

View File

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

View File

@@ -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<SimulatedGroovyDomainClass, String> {
}