Skip to content

Commit 493f65d

Browse files
authored
Refactor SchemaBuilder using Introspection (#86)
* fix: refactor introspection using BeanInfo * fix: polish GraphQLSchemaBuilder implementation
1 parent 1cbf29c commit 493f65d

File tree

8 files changed

+244
-174
lines changed

8 files changed

+244
-174
lines changed

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

Lines changed: 0 additions & 66 deletions
This file was deleted.

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

Lines changed: 50 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,11 @@
2424
import java.lang.reflect.Field;
2525
import java.lang.reflect.Member;
2626
import java.lang.reflect.Method;
27-
import java.util.*;
27+
import java.util.ArrayList;
28+
import java.util.Collection;
29+
import java.util.HashMap;
30+
import java.util.List;
31+
import java.util.Map;
2832
import java.util.concurrent.atomic.AtomicInteger;
2933
import java.util.stream.Collectors;
3034
import java.util.stream.Stream;
@@ -47,6 +51,7 @@
4751
import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder;
4852
import com.introproventures.graphql.jpa.query.schema.JavaScalars;
4953
import com.introproventures.graphql.jpa.query.schema.NamingStrategy;
54+
import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.CachedIntrospectionResult.CachedPropertyDescriptor;
5055
import com.introproventures.graphql.jpa.query.schema.impl.PredicateFilter.Criteria;
5156

5257
import graphql.Assert;
@@ -92,7 +97,7 @@ public class GraphQLJpaSchemaBuilder implements GraphQLSchemaBuilder {
9297

9398
public static final String ORDER_BY_PARAM_NAME = "orderBy";
9499

95-
private Map<Class<?>, GraphQLType> classCache = new HashMap<>();
100+
private Map<Class<?>, GraphQLOutputType> classCache = new HashMap<>();
96101
private Map<EntityType<?>, GraphQLObjectType> entityCache = new HashMap<>();
97102
private Map<EmbeddableType<?>, GraphQLObjectType> embeddableOutputCache = new HashMap<>();
98103
private Map<EmbeddableType<?>, GraphQLInputObjectType> embeddableInputCache = new HashMap<>();
@@ -205,7 +210,7 @@ private GraphQLArgument getWhereArgument(ManagedType<?> managedType) {
205210
if (managedType instanceof EmbeddableType){
206211
typeName = managedType.getJavaType().getSimpleName()+"EmbeddableType";
207212
} else if (managedType instanceof EntityType) {
208-
typeName = ((EntityType)managedType).getName();
213+
typeName = ((EntityType<?>)managedType).getName();
209214
}
210215

211216
String type = namingStrategy.pluralize(typeName)+"CriteriaExpression";
@@ -251,14 +256,14 @@ private GraphQLArgument getWhereArgument(ManagedType<?> managedType) {
251256
}
252257

253258
private GraphQLInputObjectField getWhereInputField(Attribute<?,?> attribute) {
254-
GraphQLType type = getWhereAttributeType(attribute);
259+
GraphQLInputType type = getWhereAttributeType(attribute);
255260
String description = getSchemaDescription(attribute.getJavaMember());
256261

257262
if (type instanceof GraphQLInputType) {
258263
return GraphQLInputObjectField.newInputObjectField()
259264
.name(attribute.getName())
260265
.description(description)
261-
.type((GraphQLInputType) type)
266+
.type(type)
262267
.build();
263268
}
264269

@@ -409,7 +414,7 @@ private GraphQLArgument getArgument(Attribute<?,?> attribute) {
409414

410415
return GraphQLArgument.newArgument()
411416
.name(attribute.getName())
412-
.type((GraphQLInputType) type)
417+
.type(type)
413418
.description(description)
414419
.build();
415420
}
@@ -454,89 +459,51 @@ private GraphQLType getEmbeddableType(EmbeddableType<?> embeddableType, boolean
454459

455460

456461
private GraphQLObjectType getObjectType(EntityType<?> entityType) {
457-
if (entityCache.containsKey(entityType))
458-
return entityCache.get(entityType);
459-
460-
461-
GraphQLObjectType objectType = GraphQLObjectType.newObject()
462-
.name(entityType.getName())
463-
.description(getSchemaDescription( entityType.getJavaType()))
464-
.fields(entityType.getAttributes().stream()
465-
.filter(this::isNotIgnored)
466-
.map(this::getObjectField)
467-
.collect(Collectors.toList())
468-
)
469-
.fields(getObjectCalcFields(entityType.getJavaType()))
470-
.fields(getObjectCalcMethods(entityType.getJavaType()))
471-
.build();
472-
473-
entityCache.putIfAbsent(entityType, objectType);
474-
475-
return objectType;
476-
}
477-
478-
private List<GraphQLFieldDefinition> getObjectCalcFields(Class cls) {
479-
return
480-
Arrays.stream(cls.getDeclaredFields())
481-
.filter(
482-
f ->
483-
f instanceof Member &&
484-
f.isAnnotationPresent(Transient.class) &&
485-
isNotIgnored((Member) f) &&
486-
isNotIgnored(f.getType())
487-
)
488-
.map(f -> getObjectCalcField(f))
489-
.collect(Collectors.toList());
490-
}
491-
492-
private List<GraphQLFieldDefinition> getObjectCalcMethods(Class cls) {
493-
return
494-
Arrays.stream(cls.getDeclaredMethods())
495-
.filter(
496-
m ->
497-
m instanceof Member &&
498-
m.isAnnotationPresent(Transient.class) &&
499-
isNotIgnored((Member) m) &&
500-
isNotIgnored(m.getReturnType())
501-
)
502-
.map(m -> getObjectCalcMethtod(m))
503-
.collect(Collectors.toList());
462+
return entityCache.computeIfAbsent(entityType, this::computeObjectType);
504463
}
505-
506-
@SuppressWarnings( { "rawtypes", "unchecked" } )
507-
private GraphQLFieldDefinition getObjectCalcField(Field field) {
508-
GraphQLType type = getGraphQLTypeFromJavaType(field.getType());
509-
DataFetcher dataFetcher = PropertyDataFetcher.fetching(field.getName());
510-
511-
return GraphQLFieldDefinition.newFieldDefinition()
512-
.name(field.getName())
513-
.description(getSchemaDescription((AnnotatedElement) field))
514-
.type((GraphQLOutputType) type)
515-
.dataFetcher(dataFetcher)
516-
.build();
464+
465+
466+
private GraphQLObjectType computeObjectType(EntityType<?> entityType) {
467+
return GraphQLObjectType.newObject()
468+
.name(entityType.getName())
469+
.description(getSchemaDescription(entityType.getJavaType()))
470+
.fields(getEntityAttributesFields(entityType))
471+
.fields(getTransientFields(entityType.getJavaType()))
472+
.build();
517473
}
518474

519-
@SuppressWarnings( { "rawtypes", "unchecked" } )
520-
private GraphQLFieldDefinition getObjectCalcMethtod(Method method) {
521-
String nm = method.getName();
522-
if (nm.startsWith("is")) {
523-
nm = Introspector.decapitalize(nm.substring(2));
524-
}
525-
if (nm.startsWith("get")) {
526-
nm = Introspector.decapitalize(nm.substring(3));
527-
}
475+
private List<GraphQLFieldDefinition> getEntityAttributesFields(EntityType<?> entityType) {
476+
return entityType.getAttributes()
477+
.stream()
478+
.filter(this::isNotIgnored)
479+
.map(this::getObjectField)
480+
.collect(Collectors.toList());
481+
}
528482

529-
GraphQLType type = getGraphQLTypeFromJavaType(method.getReturnType());
530-
DataFetcher dataFetcher = PropertyDataFetcher.fetching(nm);
483+
484+
private List<GraphQLFieldDefinition> getTransientFields(Class<?> clazz) {
485+
return IntrospectionUtils.introspect(clazz)
486+
.getPropertyDescriptors().stream()
487+
.filter(it -> it.isAnnotationPresent(Transient.class))
488+
.map(CachedPropertyDescriptor::getDelegate)
489+
.filter(it -> isNotIgnored(it.getPropertyType()))
490+
.map(this::getJavaFieldDefinition)
491+
.collect(Collectors.toList());
492+
}
493+
494+
@SuppressWarnings( { "rawtypes" } )
495+
private GraphQLFieldDefinition getJavaFieldDefinition(PropertyDescriptor propertyDescriptor) {
496+
GraphQLOutputType type = getGraphQLTypeFromJavaType(propertyDescriptor.getPropertyType());
497+
DataFetcher dataFetcher = PropertyDataFetcher.fetching(propertyDescriptor.getName());
531498

532499
return GraphQLFieldDefinition.newFieldDefinition()
533-
.name(nm)
534-
.description(getSchemaDescription((AnnotatedElement) method))
535-
.type((GraphQLOutputType) type)
500+
.name(propertyDescriptor.getName())
501+
.description(getSchemaDescription(propertyDescriptor.getPropertyType()))
502+
.type(type)
536503
.dataFetcher(dataFetcher)
537504
.build();
538505
}
539-
506+
540507
@SuppressWarnings( { "rawtypes", "unchecked" } )
541508
private GraphQLFieldDefinition getObjectField(Attribute attribute) {
542509
GraphQLOutputType type = getAttributeOutputType(attribute);
@@ -583,7 +550,7 @@ else if (attribute instanceof PluralAttribute
583550
.build();
584551
}
585552

586-
@SuppressWarnings( { "rawtypes", "unchecked" } )
553+
@SuppressWarnings( { "rawtypes" } )
587554
private GraphQLInputObjectField getInputObjectField(Attribute attribute) {
588555
GraphQLInputType type = getAttributeInputType(attribute);
589556

@@ -598,7 +565,6 @@ private Stream<Attribute<?,?>> findBasicAttributes(Collection<Attribute<?,?>> at
598565
return attributes.stream().filter(it -> it.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC);
599566
}
600567

601-
@SuppressWarnings( "rawtypes" )
602568
private GraphQLInputType getAttributeInputType(Attribute<?,?> attribute) {
603569
try{
604570
return (GraphQLInputType) getAttributeType(attribute, true);
@@ -607,7 +573,6 @@ private GraphQLInputType getAttributeInputType(Attribute<?,?> attribute) {
607573
}
608574
}
609575

610-
@SuppressWarnings( "rawtypes" )
611576
private GraphQLOutputType getAttributeOutputType(Attribute<?,?> attribute) {
612577
try {
613578
return (GraphQLOutputType) getAttributeType(attribute, false);
@@ -786,7 +751,7 @@ private boolean isNotIgnored(AnnotatedElement annotatedElement) {
786751
}
787752

788753
@SuppressWarnings( "unchecked" )
789-
private GraphQLType getGraphQLTypeFromJavaType(Class<?> clazz) {
754+
private GraphQLOutputType getGraphQLTypeFromJavaType(Class<?> clazz) {
790755
if (clazz.isEnum()) {
791756

792757
if (classCache.containsKey(clazz))
@@ -797,7 +762,7 @@ private GraphQLType getGraphQLTypeFromJavaType(Class<?> clazz) {
797762
for (Enum<?> enumValue : ((Class<Enum<?>>)clazz).getEnumConstants())
798763
enumBuilder.value(enumValue.name(), ordinal++);
799764

800-
GraphQLType enumType = enumBuilder.build();
765+
GraphQLEnumType enumType = enumBuilder.build();
801766
setNoOpCoercing(enumType);
802767

803768
classCache.putIfAbsent(clazz, enumType);
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
package com.introproventures.graphql.jpa.query.schema.impl;
2+
3+
import java.beans.BeanInfo;
4+
import java.beans.IntrospectionException;
5+
import java.beans.Introspector;
6+
import java.beans.PropertyDescriptor;
7+
import java.lang.annotation.Annotation;
8+
import java.util.Collection;
9+
import java.util.LinkedHashMap;
10+
import java.util.Map;
11+
import java.util.Optional;
12+
import java.util.stream.Collectors;
13+
import java.util.stream.Stream;
14+
15+
import javax.persistence.Transient;
16+
17+
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+
24+
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)));
28+
}
29+
30+
public static class CachedIntrospectionResult {
31+
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+
answer = delegate.getReadMethod()
88+
.isAnnotationPresent(annotation);
89+
}
90+
return answer;
91+
}
92+
93+
}
94+
}
95+
}

0 commit comments

Comments
 (0)