Skip to content

Commit 8028d12

Browse files
anotenderigdianov
authored andcommitted
Add METHOD to the @GraphQLIgnore annotation target (#165)
* Add METHOD to the @GraphQLIgnore annotation target * Fix issue with not ignoring transient field when it's name starts with 'get' and has no reflection in any of the class' properties * Use formatter on IntrospectionUtils.java * Add test and fix schema building issue * Reorganised test case in IntrospectionUtilsTest.java * Change return value in isAnnotationPresent to false instead of throwing an exception when property not found
1 parent 4bf7596 commit 8028d12

File tree

8 files changed

+192
-123
lines changed

8 files changed

+192
-123
lines changed

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package com.introproventures.graphql.jpa.query.annotation;
1717

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

@@ -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 GraphQLIgnore {
3435
}

graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,9 @@
1818

1919
import java.util.Date;
2020

21-
import javax.persistence.Entity;
22-
import javax.persistence.EnumType;
23-
import javax.persistence.Enumerated;
24-
import javax.persistence.FetchType;
25-
import javax.persistence.Id;
26-
import javax.persistence.ManyToOne;
21+
import javax.persistence.*;
2722

23+
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore;
2824
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreFilter;
2925
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreOrder;
3026
import lombok.Data;
@@ -49,5 +45,11 @@ public class Book {
4945
@Enumerated(EnumType.STRING)
5046
Genre genre;
5147

52-
Date publicationDate;
48+
Date publicationDate;
49+
50+
@Transient
51+
@GraphQLIgnore
52+
public String getAuthorName(){
53+
return author.getName();
54+
}
5355
}

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

Lines changed: 13 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -661,22 +661,22 @@ private GraphQLObjectType computeObjectType(EntityType<?> entityType) {
661661
}
662662

663663
private List<GraphQLFieldDefinition> getEntityAttributesFields(EntityType<?> entityType) {
664-
return entityType.getAttributes()
665-
.stream()
666-
.filter(this::isNotIgnored)
667-
.map(it -> getObjectField(it, entityType))
668-
.collect(Collectors.toList());
664+
return entityType
665+
.getAttributes().stream()
666+
.filter(this::isNotIgnored)
667+
.filter(attribute -> !IntrospectionUtils.isIgnored(entityType.getJavaType(), attribute.getName()))
668+
.map(it -> getObjectField(it, entityType))
669+
.collect(Collectors.toList());
669670
}
670671

671-
672672
private List<GraphQLFieldDefinition> getTransientFields(Class<?> clazz) {
673673
return IntrospectionUtils.introspect(clazz)
674-
.getPropertyDescriptors().stream()
675-
.filter(it -> it.isAnnotationPresent(Transient.class))
676-
.map(CachedPropertyDescriptor::getDelegate)
677-
.filter(it -> isNotIgnored(it.getPropertyType()))
678-
.map(this::getJavaFieldDefinition)
679-
.collect(Collectors.toList());
674+
.getPropertyDescriptors().stream()
675+
.filter(it -> it.isAnnotationPresent(Transient.class))
676+
.filter(it -> !it.isAnnotationPresent(GraphQLIgnore.class))
677+
.map(CachedPropertyDescriptor::getDelegate)
678+
.map(this::getJavaFieldDefinition)
679+
.collect(Collectors.toList());
680680
}
681681

682682
@SuppressWarnings( { "rawtypes" } )
@@ -978,12 +978,7 @@ private boolean isNotIgnored(Member member) {
978978
}
979979

980980
private boolean isNotIgnored(AnnotatedElement annotatedElement) {
981-
if (annotatedElement != null) {
982-
GraphQLIgnore schemaDocumentation = annotatedElement.getAnnotation(GraphQLIgnore.class);
983-
return schemaDocumentation == null;
984-
}
985-
986-
return false;
981+
return annotatedElement != null && annotatedElement.getAnnotation(GraphQLIgnore.class) == null;
987982
}
988983

989984
protected boolean isNotIgnoredFilter(Attribute<?,?> attribute) {
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package com.introproventures.graphql.jpa.query.schema.impl;
22

3+
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore;
4+
35
import java.beans.BeanInfo;
46
import java.beans.IntrospectionException;
57
import java.beans.Introspector;
@@ -15,82 +17,92 @@
1517
import javax.persistence.Transient;
1618

1719
public class IntrospectionUtils {
18-
private static final Map<Class<?>, CachedIntrospectionResult> map = new LinkedHashMap<>();
19-
20-
public static CachedIntrospectionResult introspect(Class<?> entity) {
21-
return map.computeIfAbsent(entity, CachedIntrospectionResult::new);
22-
}
23-
20+
private static final Map<Class<?>, CachedIntrospectionResult> map = new LinkedHashMap<>();
21+
22+
public static CachedIntrospectionResult introspect(Class<?> entity) {
23+
return map.computeIfAbsent(entity, CachedIntrospectionResult::new);
24+
}
25+
2426
public static boolean isTransient(Class<?> entity, String propertyName) {
25-
return introspect(entity).getPropertyDescriptor(propertyName)
26-
.map(it -> it.isAnnotationPresent(Transient.class))
27-
.orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName)));
27+
return isAnnotationPresent(entity, propertyName, Transient.class);
28+
}
29+
30+
public static boolean isIgnored(Class<?> entity, String propertyName) {
31+
return isAnnotationPresent(entity, propertyName, GraphQLIgnore.class);
2832
}
29-
33+
34+
private static boolean isAnnotationPresent(Class<?> entity, String propertyName, Class<? extends Annotation> annotation){
35+
return introspect(entity).getPropertyDescriptor(propertyName)
36+
.map(it -> it.isAnnotationPresent(annotation))
37+
.orElse(false);
38+
}
39+
3040
public static class CachedIntrospectionResult {
3141

32-
private final Map<String, CachedPropertyDescriptor> map;
33-
private final Class<?> entity;
34-
private final BeanInfo beanInfo;
35-
36-
public CachedIntrospectionResult(Class<?> entity) {
37-
try {
38-
this.beanInfo = Introspector.getBeanInfo(entity);
39-
} catch (IntrospectionException cause) {
40-
throw new RuntimeException(cause);
41-
}
42-
43-
this.entity = entity;
44-
this.map = Stream.of(beanInfo.getPropertyDescriptors())
45-
.map(CachedPropertyDescriptor::new)
46-
.collect(Collectors.toMap(CachedPropertyDescriptor::getName, it -> it));
47-
}
48-
49-
public Collection<CachedPropertyDescriptor> getPropertyDescriptors() {
50-
return map.values();
51-
}
52-
53-
public Optional<CachedPropertyDescriptor> getPropertyDescriptor(String fieldName) {
54-
return Optional.ofNullable(map.getOrDefault(fieldName, null));
55-
}
56-
57-
public Class<?> getEntity() {
58-
return entity;
59-
}
60-
61-
public BeanInfo getBeanInfo() {
62-
return beanInfo;
63-
}
64-
65-
public class CachedPropertyDescriptor {
66-
private final PropertyDescriptor delegate;
67-
68-
public CachedPropertyDescriptor(PropertyDescriptor delegate) {
69-
this.delegate = delegate;
70-
}
71-
72-
public PropertyDescriptor getDelegate() {
73-
return delegate;
74-
}
75-
76-
public String getName() {
77-
return delegate.getName();
78-
}
79-
80-
public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
81-
boolean answer;
82-
try {
83-
answer = entity.getDeclaredField(delegate.getName())
84-
.isAnnotationPresent(annotation);
85-
86-
} catch (NoSuchFieldException e) {
87-
if(delegate.getReadMethod() == null) return false;
88-
answer = delegate.getReadMethod()
89-
.isAnnotationPresent(annotation);
90-
}
91-
return answer;
92-
}
93-
94-
}
42+
private final Map<String, CachedPropertyDescriptor> map;
43+
private final Class<?> entity;
44+
private final BeanInfo beanInfo;
45+
46+
public CachedIntrospectionResult(Class<?> entity) {
47+
try {
48+
this.beanInfo = Introspector.getBeanInfo(entity);
49+
} catch (IntrospectionException cause) {
50+
throw new RuntimeException(cause);
51+
}
52+
53+
this.entity = entity;
54+
this.map = Stream.of(beanInfo.getPropertyDescriptors())
55+
.map(CachedPropertyDescriptor::new)
56+
.collect(Collectors.toMap(CachedPropertyDescriptor::getName, it -> it));
57+
}
58+
59+
public Collection<CachedPropertyDescriptor> getPropertyDescriptors() {
60+
return map.values();
61+
}
62+
63+
public Optional<CachedPropertyDescriptor> getPropertyDescriptor(String fieldName) {
64+
return Optional.ofNullable(map.getOrDefault(fieldName, null));
65+
}
66+
67+
public Class<?> getEntity() {
68+
return entity;
69+
}
70+
71+
public BeanInfo getBeanInfo() {
72+
return beanInfo;
73+
}
74+
75+
public class CachedPropertyDescriptor {
76+
private final PropertyDescriptor delegate;
77+
78+
public CachedPropertyDescriptor(PropertyDescriptor delegate) {
79+
this.delegate = delegate;
80+
}
81+
82+
public PropertyDescriptor getDelegate() {
83+
return delegate;
84+
}
85+
86+
public String getName() {
87+
return delegate.getName();
88+
}
89+
90+
public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
91+
return isAnnotationPresentOnField(annotation) || isAnnotationPresentOnReadMethod(annotation);
92+
}
93+
94+
private boolean isAnnotationPresentOnField(Class<? extends Annotation> annotation) {
95+
try {
96+
return entity.getDeclaredField(delegate.getName()).isAnnotationPresent(annotation);
97+
} catch (NoSuchFieldException e) {
98+
return false;
99+
}
100+
}
101+
102+
private boolean isAnnotationPresentOnReadMethod(Class<? extends Annotation> annotation) {
103+
return delegate.getReadMethod() != null && delegate.getReadMethod().isAnnotationPresent(annotation);
104+
}
105+
106+
}
95107
}
96108
}

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

Lines changed: 31 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
package com.introproventures.graphql.jpa.query.schema;
22

33
import static org.assertj.core.api.Assertions.assertThat;
4-
5-
import java.util.Arrays;
6-
import java.util.List;
4+
import static org.assertj.core.api.Assertions.tuple;
5+
import static org.assertj.core.util.Lists.list;
76

87
import javax.persistence.EntityManager;
98

9+
import graphql.ExecutionResult;
10+
import graphql.validation.ValidationErrorType;
1011
import org.junit.Test;
1112
import org.junit.runner.RunWith;
1213
import org.springframework.beans.factory.annotation.Autowired;
@@ -20,10 +21,6 @@
2021
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor;
2122
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder;
2223

23-
import graphql.ErrorType;
24-
import graphql.GraphQLError;
25-
import graphql.validation.ValidationError;
26-
2724
@RunWith(SpringRunner.class)
2825
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.NONE)
2926
@TestPropertySource({"classpath:hibernate.properties"})
@@ -69,17 +66,37 @@ public void getAllRecords() {
6966

7067
@Test
7168
public void testIgnoreFields() {
72-
String query = "query GraphQLCalcFields { CalculatedEntities { select {id title fieldMem fieldFun logic customLogic hideField hideFieldFunction } } }";
69+
String query = "" +
70+
"query GraphQLCalcFields { " +
71+
" CalculatedEntities { " +
72+
" select {" +
73+
" id" +
74+
" title" +
75+
" fieldMem" +
76+
" fieldFun" +
77+
" logic" +
78+
" customLogic" +
79+
" hideField" +
80+
" hideFieldFunction" +
81+
" propertyIgnoredOnGetter" +
82+
" ignoredTransientValue" +
83+
" } " +
84+
" } " +
85+
"}";
7386

7487
//when
75-
List<GraphQLError> result = executor.execute(query).getErrors();
88+
ExecutionResult result = executor.execute(query);
7689

7790
//then
78-
assertThat(result).hasSize(1);
79-
assertThat(result.get(0)).isExactlyInstanceOf(ValidationError.class)
80-
.extracting(ValidationError.class::cast)
81-
.extracting("errorType", "queryPath")
82-
.contains(ErrorType.ValidationError, Arrays.asList("CalculatedEntities", "select", "hideFieldFunction"));
91+
assertThat(result.getErrors())
92+
.isNotEmpty()
93+
.extracting("validationErrorType", "queryPath")
94+
.containsOnly(
95+
tuple(ValidationErrorType.FieldUndefined, list("CalculatedEntities", "select", "hideField")),
96+
tuple(ValidationErrorType.FieldUndefined, list("CalculatedEntities", "select", "hideFieldFunction")),
97+
tuple(ValidationErrorType.FieldUndefined, list("CalculatedEntities", "select", "propertyIgnoredOnGetter")),
98+
tuple(ValidationErrorType.FieldUndefined, list("CalculatedEntities", "select", "ignoredTransientValue"))
99+
);
83100
}
84101

85102
}

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

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
package com.introproventures.graphql.jpa.query.schema;
1818

1919
import static org.assertj.core.api.Assertions.assertThat;
20+
import static org.assertj.core.api.Assertions.tuple;
21+
import static org.assertj.core.util.Lists.list;
2022

2123
import java.util.Arrays;
2224
import java.util.HashMap;
@@ -31,6 +33,7 @@
3133
import graphql.ExecutionResult;
3234
import graphql.GraphQLError;
3335
import graphql.validation.ValidationError;
36+
import graphql.validation.ValidationErrorType;
3437
import org.junit.Test;
3538
import org.junit.runner.RunWith;
3639
import org.springframework.beans.factory.annotation.Autowired;
@@ -1415,4 +1418,26 @@ public void queryForAuthorsWithExlicitOptionalBooksTrue() {
14151418
// then
14161419
assertThat(result.toString()).isEqualTo(expected);
14171420
}
1421+
1422+
@Test
1423+
public void queryForTransientMethodAnnotatedWithGraphQLIgnoreShouldFail() {
1424+
//given
1425+
String query = ""
1426+
+ "query { "
1427+
+ " Books {"
1428+
+ " select {"
1429+
+ " authorName"
1430+
+ " }"
1431+
+ " }"
1432+
+ "}";
1433+
1434+
//when
1435+
ExecutionResult result = executor.execute(query);
1436+
1437+
// then
1438+
assertThat(result.getErrors())
1439+
.isNotEmpty()
1440+
.extracting("validationErrorType", "queryPath")
1441+
.containsOnly(tuple(ValidationErrorType.FieldUndefined, list("Books", "select", "authorName")));
1442+
}
14181443
}

0 commit comments

Comments
 (0)