Skip to content

Refactor SchemaBuilder using Introspection #86

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Mar 11, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,11 @@
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.*;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.stream.Collectors;
import java.util.stream.Stream;
Expand All @@ -47,6 +51,7 @@
import com.introproventures.graphql.jpa.query.schema.GraphQLSchemaBuilder;
import com.introproventures.graphql.jpa.query.schema.JavaScalars;
import com.introproventures.graphql.jpa.query.schema.NamingStrategy;
import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.CachedIntrospectionResult.CachedPropertyDescriptor;
import com.introproventures.graphql.jpa.query.schema.impl.PredicateFilter.Criteria;

import graphql.Assert;
Expand Down Expand Up @@ -92,7 +97,7 @@ public class GraphQLJpaSchemaBuilder implements GraphQLSchemaBuilder {

public static final String ORDER_BY_PARAM_NAME = "orderBy";

private Map<Class<?>, GraphQLType> classCache = new HashMap<>();
private Map<Class<?>, GraphQLOutputType> classCache = new HashMap<>();
private Map<EntityType<?>, GraphQLObjectType> entityCache = new HashMap<>();
private Map<EmbeddableType<?>, GraphQLObjectType> embeddableOutputCache = new HashMap<>();
private Map<EmbeddableType<?>, GraphQLInputObjectType> embeddableInputCache = new HashMap<>();
Expand Down Expand Up @@ -205,7 +210,7 @@ private GraphQLArgument getWhereArgument(ManagedType<?> managedType) {
if (managedType instanceof EmbeddableType){
typeName = managedType.getJavaType().getSimpleName()+"EmbeddableType";
} else if (managedType instanceof EntityType) {
typeName = ((EntityType)managedType).getName();
typeName = ((EntityType<?>)managedType).getName();
}

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

private GraphQLInputObjectField getWhereInputField(Attribute<?,?> attribute) {
GraphQLType type = getWhereAttributeType(attribute);
GraphQLInputType type = getWhereAttributeType(attribute);
String description = getSchemaDescription(attribute.getJavaMember());

if (type instanceof GraphQLInputType) {
return GraphQLInputObjectField.newInputObjectField()
.name(attribute.getName())
.description(description)
.type((GraphQLInputType) type)
.type(type)
.build();
}

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

return GraphQLArgument.newArgument()
.name(attribute.getName())
.type((GraphQLInputType) type)
.type(type)
.description(description)
.build();
}
Expand Down Expand Up @@ -454,89 +459,51 @@ private GraphQLType getEmbeddableType(EmbeddableType<?> embeddableType, boolean


private GraphQLObjectType getObjectType(EntityType<?> entityType) {
if (entityCache.containsKey(entityType))
return entityCache.get(entityType);


GraphQLObjectType objectType = GraphQLObjectType.newObject()
.name(entityType.getName())
.description(getSchemaDescription( entityType.getJavaType()))
.fields(entityType.getAttributes().stream()
.filter(this::isNotIgnored)
.map(this::getObjectField)
.collect(Collectors.toList())
)
.fields(getObjectCalcFields(entityType.getJavaType()))
.fields(getObjectCalcMethods(entityType.getJavaType()))
.build();

entityCache.putIfAbsent(entityType, objectType);

return objectType;
}

private List<GraphQLFieldDefinition> getObjectCalcFields(Class cls) {
return
Arrays.stream(cls.getDeclaredFields())
.filter(
f ->
f instanceof Member &&
f.isAnnotationPresent(Transient.class) &&
isNotIgnored((Member) f) &&
isNotIgnored(f.getType())
)
.map(f -> getObjectCalcField(f))
.collect(Collectors.toList());
}

private List<GraphQLFieldDefinition> getObjectCalcMethods(Class cls) {
return
Arrays.stream(cls.getDeclaredMethods())
.filter(
m ->
m instanceof Member &&
m.isAnnotationPresent(Transient.class) &&
isNotIgnored((Member) m) &&
isNotIgnored(m.getReturnType())
)
.map(m -> getObjectCalcMethtod(m))
.collect(Collectors.toList());
return entityCache.computeIfAbsent(entityType, this::computeObjectType);
}

@SuppressWarnings( { "rawtypes", "unchecked" } )
private GraphQLFieldDefinition getObjectCalcField(Field field) {
GraphQLType type = getGraphQLTypeFromJavaType(field.getType());
DataFetcher dataFetcher = PropertyDataFetcher.fetching(field.getName());

return GraphQLFieldDefinition.newFieldDefinition()
.name(field.getName())
.description(getSchemaDescription((AnnotatedElement) field))
.type((GraphQLOutputType) type)
.dataFetcher(dataFetcher)
.build();


private GraphQLObjectType computeObjectType(EntityType<?> entityType) {
return GraphQLObjectType.newObject()
.name(entityType.getName())
.description(getSchemaDescription(entityType.getJavaType()))
.fields(getEntityAttributesFields(entityType))
.fields(getTransientFields(entityType.getJavaType()))
.build();
}

@SuppressWarnings( { "rawtypes", "unchecked" } )
private GraphQLFieldDefinition getObjectCalcMethtod(Method method) {
String nm = method.getName();
if (nm.startsWith("is")) {
nm = Introspector.decapitalize(nm.substring(2));
}
if (nm.startsWith("get")) {
nm = Introspector.decapitalize(nm.substring(3));
}
private List<GraphQLFieldDefinition> getEntityAttributesFields(EntityType<?> entityType) {
return entityType.getAttributes()
.stream()
.filter(this::isNotIgnored)
.map(this::getObjectField)
.collect(Collectors.toList());
}

GraphQLType type = getGraphQLTypeFromJavaType(method.getReturnType());
DataFetcher dataFetcher = PropertyDataFetcher.fetching(nm);

private List<GraphQLFieldDefinition> getTransientFields(Class<?> clazz) {
return IntrospectionUtils.introspect(clazz)
.getPropertyDescriptors().stream()
.filter(it -> it.isAnnotationPresent(Transient.class))
.map(CachedPropertyDescriptor::getDelegate)
.filter(it -> isNotIgnored(it.getPropertyType()))
.map(this::getJavaFieldDefinition)
.collect(Collectors.toList());
}

@SuppressWarnings( { "rawtypes" } )
private GraphQLFieldDefinition getJavaFieldDefinition(PropertyDescriptor propertyDescriptor) {
GraphQLOutputType type = getGraphQLTypeFromJavaType(propertyDescriptor.getPropertyType());
DataFetcher dataFetcher = PropertyDataFetcher.fetching(propertyDescriptor.getName());

return GraphQLFieldDefinition.newFieldDefinition()
.name(nm)
.description(getSchemaDescription((AnnotatedElement) method))
.type((GraphQLOutputType) type)
.name(propertyDescriptor.getName())
.description(getSchemaDescription(propertyDescriptor.getPropertyType()))
.type(type)
.dataFetcher(dataFetcher)
.build();
}

@SuppressWarnings( { "rawtypes", "unchecked" } )
private GraphQLFieldDefinition getObjectField(Attribute attribute) {
GraphQLOutputType type = getAttributeOutputType(attribute);
Expand Down Expand Up @@ -583,7 +550,7 @@ else if (attribute instanceof PluralAttribute
.build();
}

@SuppressWarnings( { "rawtypes", "unchecked" } )
@SuppressWarnings( { "rawtypes" } )
private GraphQLInputObjectField getInputObjectField(Attribute attribute) {
GraphQLInputType type = getAttributeInputType(attribute);

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

@SuppressWarnings( "rawtypes" )
private GraphQLInputType getAttributeInputType(Attribute<?,?> attribute) {
try{
return (GraphQLInputType) getAttributeType(attribute, true);
Expand All @@ -607,7 +573,6 @@ private GraphQLInputType getAttributeInputType(Attribute<?,?> attribute) {
}
}

@SuppressWarnings( "rawtypes" )
private GraphQLOutputType getAttributeOutputType(Attribute<?,?> attribute) {
try {
return (GraphQLOutputType) getAttributeType(attribute, false);
Expand Down Expand Up @@ -786,7 +751,7 @@ private boolean isNotIgnored(AnnotatedElement annotatedElement) {
}

@SuppressWarnings( "unchecked" )
private GraphQLType getGraphQLTypeFromJavaType(Class<?> clazz) {
private GraphQLOutputType getGraphQLTypeFromJavaType(Class<?> clazz) {
if (clazz.isEnum()) {

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

GraphQLType enumType = enumBuilder.build();
GraphQLEnumType enumType = enumBuilder.build();
setNoOpCoercing(enumType);

classCache.putIfAbsent(clazz, enumType);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package com.introproventures.graphql.jpa.query.schema.impl;

import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.annotation.Annotation;
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;

import javax.persistence.Transient;

public class IntrospectionUtils {
private static final Map<Class<?>, CachedIntrospectionResult> map = new LinkedHashMap<>();

public static CachedIntrospectionResult introspect(Class<?> entity) {
return map.computeIfAbsent(entity, CachedIntrospectionResult::new);
}

public static boolean isTransient(Class<?> entity, String propertyName) {
return introspect(entity).getPropertyDescriptor(propertyName)
.map(it -> it.isAnnotationPresent(Transient.class))
.orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName)));
}

public static class CachedIntrospectionResult {

private final Map<String, CachedPropertyDescriptor> map;
private final Class<?> entity;
private final BeanInfo beanInfo;

public CachedIntrospectionResult(Class<?> entity) {
try {
this.beanInfo = Introspector.getBeanInfo(entity);
} catch (IntrospectionException cause) {
throw new RuntimeException(cause);
}

this.entity = entity;
this.map = Stream.of(beanInfo.getPropertyDescriptors())
.map(CachedPropertyDescriptor::new)
.collect(Collectors.toMap(CachedPropertyDescriptor::getName, it -> it));
}

public Collection<CachedPropertyDescriptor> getPropertyDescriptors() {
return map.values();
}

public Optional<CachedPropertyDescriptor> getPropertyDescriptor(String fieldName) {
return Optional.ofNullable(map.getOrDefault(fieldName, null));
}

public Class<?> getEntity() {
return entity;
}

public BeanInfo getBeanInfo() {
return beanInfo;
}

public class CachedPropertyDescriptor {
private final PropertyDescriptor delegate;

public CachedPropertyDescriptor(PropertyDescriptor delegate) {
this.delegate = delegate;
}

public PropertyDescriptor getDelegate() {
return delegate;
}

public String getName() {
return delegate.getName();
}

public boolean isAnnotationPresent(Class<? extends Annotation> annotation) {
boolean answer;
try {
answer = entity.getDeclaredField(delegate.getName())
.isAnnotationPresent(annotation);

} catch (NoSuchFieldException e) {
answer = delegate.getReadMethod()
.isAnnotationPresent(annotation);
}
return answer;
}

}
}
}
Loading