Skip to content

Commit 2df2a46

Browse files
molexxigdianov
authored andcommitted
Flexible description annotation (#63)
* add test for description * allow @GraphQLDescription to be on methods * search for @GraphQLDescription on the given Member's getter Method or Field * test descriptions on getters are picked up * test more combinations of JPA and Description annotation placement
1 parent 93f2273 commit 2df2a46

File tree

5 files changed

+149
-6
lines changed

5 files changed

+149
-6
lines changed

graphql-jpa-query-annotations/src/main/java/com/introproventures/graphql/jpa/query/annotation/GraphQLDescription.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717

1818
import static java.lang.annotation.ElementType.FIELD;
1919
import static java.lang.annotation.ElementType.TYPE;
20+
import static java.lang.annotation.ElementType.METHOD;
2021
import static java.lang.annotation.RetentionPolicy.RUNTIME;
2122

2223
import java.lang.annotation.Retention;
@@ -28,7 +29,7 @@
2829
* @author Igor Dianov
2930
*
3031
*/
31-
@Target( { TYPE, FIELD })
32+
@Target( { TYPE, FIELD, METHOD })
3233
@Retention(RUNTIME)
3334
public @interface GraphQLDescription {
3435

graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,9 +16,14 @@
1616

1717
package com.introproventures.graphql.jpa.query.schema.impl;
1818

19+
import java.beans.BeanInfo;
20+
import java.beans.IntrospectionException;
21+
import java.beans.Introspector;
22+
import java.beans.PropertyDescriptor;
1923
import java.lang.reflect.AnnotatedElement;
2024
import java.lang.reflect.Field;
2125
import java.lang.reflect.Member;
26+
import java.lang.reflect.Method;
2227
import java.util.ArrayList;
2328
import java.util.Collection;
2429
import java.util.HashMap;
@@ -572,7 +577,68 @@ protected final boolean isValidInput(Attribute<?,?> attribute) {
572577

573578
private String getSchemaDescription(Member member) {
574579
if (member instanceof AnnotatedElement) {
575-
return getSchemaDescription((AnnotatedElement) member);
580+
String desc = getSchemaDescription((AnnotatedElement) member);
581+
if (desc != null) {
582+
return(desc);
583+
}
584+
}
585+
586+
//The given Member has no @GraphQLDescription set.
587+
//If the Member is a Method it might be a getter/setter, see if the property it represents
588+
//is annotated with @GraphQLDescription
589+
//Alternatively if the Member is a Field its getter might be annotated, see if its getter
590+
//is annotated with @GraphQLDescription
591+
if (member instanceof Method) {
592+
Field fieldMember = getFieldByAccessor((Method)member);
593+
if (fieldMember != null) {
594+
return(getSchemaDescription((AnnotatedElement) fieldMember));
595+
}
596+
} else if (member instanceof Field) {
597+
Method fieldGetter = getGetterOfField((Field)member);
598+
if (fieldGetter != null) {
599+
return(getSchemaDescription((AnnotatedElement) fieldGetter));
600+
}
601+
}
602+
603+
return null;
604+
}
605+
606+
private Method getGetterOfField(Field field) {
607+
try {
608+
Class<?> clazz = field.getDeclaringClass();
609+
BeanInfo info = Introspector.getBeanInfo(clazz);
610+
PropertyDescriptor[] props = info.getPropertyDescriptors();
611+
for (PropertyDescriptor pd : props) {
612+
if (pd.getName().equals(field.getName())) {
613+
return(pd.getReadMethod());
614+
}
615+
}
616+
} catch (IntrospectionException e) {
617+
e.printStackTrace();
618+
}
619+
620+
return(null);
621+
}
622+
623+
//from https://stackoverflow.com/questions/13192734/getting-a-property-field-name-using-getter-method-of-a-pojo-java-bean/13514566
624+
private static Field getFieldByAccessor(Method method) {
625+
try {
626+
Class<?> clazz = method.getDeclaringClass();
627+
BeanInfo info = Introspector.getBeanInfo(clazz);
628+
PropertyDescriptor[] props = info.getPropertyDescriptors();
629+
for (PropertyDescriptor pd : props) {
630+
if(method.equals(pd.getWriteMethod()) || method.equals(pd.getReadMethod())) {
631+
String fieldName = pd.getName();
632+
try {
633+
return(clazz.getDeclaredField(fieldName));
634+
} catch (Throwable t) {
635+
log.error("class '" + clazz.getName() + "' contains method '" + method.getName() + "' which is an accessor for a Field named '" + fieldName + "', error getting the field:", t);
636+
return(null);
637+
}
638+
}
639+
}
640+
} catch (Throwable t) {
641+
log.error("error finding Field for accessor with name '" + method.getName() + "'", t);
576642
}
577643

578644
return null;

graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/StarwarsSchemaBuildTest.java

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import graphql.Scalars;
3636
import graphql.schema.GraphQLInputObjectType;
3737
import graphql.schema.GraphQLSchema;
38+
import graphql.schema.GraphQLObjectType;
3839

3940
@RunWith(SpringRunner.class)
4041
@SpringBootTest(
@@ -120,4 +121,66 @@ public void correctlyDerivesPageableSchemaFromGivenEntities() {
120121

121122
}
122123

124+
125+
@Test
126+
public void correctlyDerivesSchemaDescriptionsFromGivenEntities() {
127+
//when
128+
GraphQLSchema schema = builder.build();
129+
130+
// then
131+
assertThat(schema)
132+
.describedAs("Ensure the schema is generated")
133+
.isNotNull();
134+
135+
//then
136+
assertThat(schema.getQueryType().getFieldDefinition("Droid").getDescription())
137+
.describedAs( "Ensure that Droid has the expected description")
138+
.isEqualTo("Represents an electromechanical robot in the Star Wars Universe");
139+
140+
//then
141+
assertThat(
142+
((GraphQLObjectType)schema.getQueryType().getFieldDefinition("Droid").getType())
143+
.getFieldDefinition("primaryFunction")
144+
.getDescription()
145+
)
146+
.describedAs( "Ensure that Droid.primaryFunction has the expected description")
147+
.isEqualTo("Documents the primary purpose this droid serves");
148+
149+
//then
150+
assertThat(
151+
((GraphQLObjectType)schema.getQueryType().getFieldDefinition("Droid").getType())
152+
.getFieldDefinition("id")
153+
.getDescription()
154+
)
155+
.describedAs( "Ensure that Droid.id has the expected description, inherited from Character")
156+
.isEqualTo("Primary Key for the Character Class");
157+
158+
//then
159+
assertThat(
160+
((GraphQLObjectType)schema.getQueryType().getFieldDefinition("Droid").getType())
161+
.getFieldDefinition("name")
162+
.getDescription()
163+
)
164+
.describedAs( "Ensure that Droid.name has the expected description, inherited from Character")
165+
.isEqualTo("Name of the character");
166+
167+
//then
168+
assertThat(
169+
((GraphQLObjectType)schema.getQueryType().getFieldDefinition("CodeList").getType())
170+
.getFieldDefinition("id")
171+
.getDescription()
172+
)
173+
.describedAs( "Ensure that CodeList.id has the expected description")
174+
.isEqualTo("Primary Key for the Code List Class");
175+
176+
//then
177+
assertThat(
178+
((GraphQLObjectType)schema.getQueryType().getFieldDefinition("CodeList").getType())
179+
.getFieldDefinition("parent")
180+
.getDescription()
181+
)
182+
.describedAs( "Ensure that CodeList.parent has the expected description")
183+
.isEqualTo("The CodeList's parent CodeList");
184+
}
185+
123186
}

graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/starwars/CodeList.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
@Data
3232
public class CodeList {
3333

34-
@Id
3534
@GraphQLDescription("Primary Key for the Code List Class")
3635
Long id;
3736

@@ -41,8 +40,18 @@ public class CodeList {
4140
boolean active;
4241
String description;
4342

44-
@ManyToOne(fetch=FetchType.LAZY)
45-
@JoinColumn(name = "parent_id")
4643
CodeList parent;
4744

45+
//JPA annotations moved to getters to test that @GraphQLDescription can be placed on the field when the JPA annotation is on the getter
46+
@Id
47+
public Long getId() {
48+
return(id);
49+
}
50+
51+
@ManyToOne(fetch=FetchType.LAZY)
52+
@JoinColumn(name = "parent_id")
53+
@GraphQLDescription("The CodeList's parent CodeList")
54+
public CodeList getParent() {
55+
return(parent);
56+
}
4857
}

graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/starwars/Droid.java

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,11 @@
2929
@EqualsAndHashCode(callSuper=true)
3030
public class Droid extends Character {
3131

32-
@GraphQLDescription("Documents the primary purpose this droid serves")
3332
String primaryFunction;
3433

34+
//description moved to getter to test it gets picked up
35+
@GraphQLDescription("Documents the primary purpose this droid serves")
36+
public String getPrimaryFunction() {
37+
return(primaryFunction);
38+
}
3539
}

0 commit comments

Comments
 (0)