From fe8a263a599374504949c6675d4a60afa2ec6ed9 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 10 Mar 2019 18:31:41 -0700 Subject: [PATCH 1/2] fix: refactor introspection using BeanInfo --- .../impl/CashGraphQLCalculatedFields.java | 66 -------------- .../query/schema/impl/IntrospectionUtils.java | 90 +++++++++++++++++++ .../impl/QraphQLJpaBaseDataFetcher.java | 15 +++- .../schema/impl/IntrospectionUtilsTest.java | 61 +++++++++++++ .../CalculatedEntity.java} | 16 ++-- 5 files changed, 172 insertions(+), 76 deletions(-) delete mode 100644 graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/CashGraphQLCalculatedFields.java create mode 100644 graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtils.java create mode 100644 graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtilsTest.java rename graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/{calc/CalcEntity.java => calculated/CalculatedEntity.java} (94%) diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/CashGraphQLCalculatedFields.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/CashGraphQLCalculatedFields.java deleted file mode 100644 index 262769fe1..000000000 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/CashGraphQLCalculatedFields.java +++ /dev/null @@ -1,66 +0,0 @@ -package com.introproventures.graphql.jpa.query.schema.impl; - - -import javax.persistence.Transient; -import java.util.Arrays; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -public class CashGraphQLCalculatedFields { - protected static Map>> cashCalcFields = new ConcurrentHashMap<>(); - - public static void clearCashCalcFields() { - cashCalcFields.values().stream().forEach(v -> v.clear()); - cashCalcFields.clear(); - } - - public static boolean isCalcField(Class cls, String field) { - if (cashCalcFields.containsKey(cls)) { - if (cashCalcFields.get(cls).containsKey(field)) { - return cashCalcFields.get(cls).get(field).isPresent(); - } - } - - Optional cf = getTransient(cls, field); - addCashCalcFields(cls, field, cf); - - return cf.isPresent(); - } - - public static void addCashCalcFields(Class cls, String field, Optional an) { - if (!cashCalcFields.containsKey(cls)) { - Map> tpMap = new ConcurrentHashMap<>(); - cashCalcFields.put(cls, tpMap); - } - - cashCalcFields.get(cls).put(field, an); - } - - public static Optional getTransient(Class cls, String field) { - Optional calcField = Arrays.stream(cls.getDeclaredFields()) - .filter(f -> f.getName().equals(field) && f.isAnnotationPresent(Transient.class)) - .map(f -> f.getAnnotation(Transient.class)) - .findFirst(); - - if (!calcField.isPresent()) { - calcField = getGraphQLCalcMethod(cls, field, "get"); - } - - if (!calcField.isPresent()) { - calcField = getGraphQLCalcMethod(cls, field, "is"); - } - - return calcField; - } - - public static Optional getGraphQLCalcMethod(Class cls, String field, String prefix) { - String methodName = prefix + field.substring(0,1).toUpperCase() + field.substring(1); - - return Arrays.stream(cls.getDeclaredMethods()) - .filter(m -> m.getName().equals(methodName) && m.isAnnotationPresent(Transient.class)) - .map(m -> m.getAnnotation(Transient.class)) - .findFirst() - ; - } -} diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtils.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtils.java new file mode 100644 index 000000000..2dd10de47 --- /dev/null +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtils.java @@ -0,0 +1,90 @@ +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.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, 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 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 Optional 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 annotation) { + boolean answer; + try { + answer = entity.getDeclaredField(delegate.getName()) + .isAnnotationPresent(annotation); + + } catch (NoSuchFieldException e) { + answer = delegate.getReadMethod() + .isAnnotationPresent(annotation); + } + return answer; + } + + } + } +} diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java index 2fc8083e6..a901ff915 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/QraphQLJpaBaseDataFetcher.java @@ -19,7 +19,16 @@ import static graphql.introspection.Introspection.TypeMetaFieldDef; import static graphql.introspection.Introspection.TypeNameMetaFieldDef; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.EnumSet; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -137,7 +146,7 @@ protected final List getFieldArguments(Field field, CriteriaQuery q Field selectedField = (Field) selection; // "__typename" is part of the graphql introspection spec and has to be ignored by jpa - if(!TYPENAME.equals(selectedField.getName()) && !CashGraphQLCalculatedFields.isCalcField(from.getJavaType(), selectedField.getName())) { + if(!TYPENAME.equals(selectedField.getName()) && !IntrospectionUtils.isTransient(from.getJavaType(), selectedField.getName())) { Path fieldPath = from.get(selectedField.getName()); @@ -719,7 +728,7 @@ && isManagedType(entityType.getAttribute(it.getName())) Subgraph sg = entityGraph.addSubgraph(it.getName()); buildSubgraph(it, sg); } else { - if(!TYPENAME.equals(it.getName()) && !CashGraphQLCalculatedFields.isCalcField(entityType.getJavaType(), it.getName())) + if(!TYPENAME.equals(it.getName()) && !IntrospectionUtils.isTransient(entityType.getJavaType(), it.getName())) entityGraph.addAttributeNodes(it.getName()); } }); diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtilsTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtilsTest.java new file mode 100644 index 000000000..1eef689a6 --- /dev/null +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtilsTest.java @@ -0,0 +1,61 @@ +package com.introproventures.graphql.jpa.query.schema.impl; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.Test; + +import com.introproventures.graphql.jpa.query.schema.model.calculated.CalculatedEntity; + +public class IntrospectionUtilsTest { + + // given + Class entity = CalculatedEntity.class; + + + @Test(expected=RuntimeException.class) + public void testIsTransientNonExisting() throws Exception { + // then + assertThat(IntrospectionUtils.isTransient(entity, "notFound")).isFalse(); + } + + @Test + public void testIsTransientClasss() throws Exception { + // then + assertThat(IntrospectionUtils.isTransient(entity, "class")).isFalse(); + } + + @Test + public void testIsTransientFunction() throws Exception { + // then + assertThat(IntrospectionUtils.isTransient(entity, "fieldFun")).isTrue(); + assertThat(IntrospectionUtils.isTransient(entity, "hideFieldFunction")).isFalse(); + } + + @Test + public void testIsTransientId() throws Exception { + // then + assertThat(IntrospectionUtils.isTransient(entity, "id")).isFalse(); + } + + + @Test + public void testIsTransientFields() throws Exception { + // then + assertThat(IntrospectionUtils.isTransient(entity, "fieldFun")).isTrue(); + assertThat(IntrospectionUtils.isTransient(entity, "fieldMem")).isTrue(); + assertThat(IntrospectionUtils.isTransient(entity, "hideField")).isTrue(); + } + + @Test + public void testIsTransientOther() throws Exception { + // given + Class entity = CalculatedEntity.class; + + // then + assertThat(IntrospectionUtils.isTransient(entity, "info")).isFalse(); + assertThat(IntrospectionUtils.isTransient(entity, "logic")).isTrue(); + assertThat(IntrospectionUtils.isTransient(entity, "title")).isFalse(); + } + + +} diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calc/CalcEntity.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/CalculatedEntity.java similarity index 94% rename from graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calc/CalcEntity.java rename to graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/CalculatedEntity.java index 7411a33a8..e312c7bfa 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calc/CalcEntity.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/CalculatedEntity.java @@ -1,16 +1,17 @@ -package com.introproventures.graphql.jpa.query.schema.model.calc; - -import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; -import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore; -import lombok.Data; +package com.introproventures.graphql.jpa.query.schema.model.calculated; import javax.persistence.Entity; import javax.persistence.Id; import javax.persistence.Transient; +import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; +import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore; + +import lombok.Data; + @Data @Entity -public class CalcEntity { +public class CalculatedEntity { @Id Long id; @@ -20,7 +21,7 @@ public class CalcEntity { @Transient boolean logic = true; - + @Transient @GraphQLDescription("i desc member") String fieldMem = "member"; @@ -29,6 +30,7 @@ public class CalcEntity { @GraphQLIgnore String hideField = "hideField"; + @Transient @GraphQLDescription("i desc function") public String getFieldFun() { From 91ee88a612b9dfb6c7615b33f118f930423e4ef0 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 10 Mar 2019 20:50:39 -0700 Subject: [PATCH 2/2] fix: polish GraphQLSchemaBuilder implementation --- .../schema/impl/GraphQLJpaSchemaBuilder.java | 135 +++++++----------- .../query/schema/impl/IntrospectionUtils.java | 7 +- ...yTests.java => CalculatedEntityTests.java} | 34 +++-- .../schema/impl/IntrospectionUtilsTest.java | 14 +- .../src/test/resources/data.sql | 2 +- 5 files changed, 83 insertions(+), 109 deletions(-) rename graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/{CalcEntityTests.java => CalculatedEntityTests.java} (57%) diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java index 4a35d16d5..7a00249b9 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java @@ -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; @@ -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; @@ -92,7 +97,7 @@ public class GraphQLJpaSchemaBuilder implements GraphQLSchemaBuilder { public static final String ORDER_BY_PARAM_NAME = "orderBy"; - private Map, GraphQLType> classCache = new HashMap<>(); + private Map, GraphQLOutputType> classCache = new HashMap<>(); private Map, GraphQLObjectType> entityCache = new HashMap<>(); private Map, GraphQLObjectType> embeddableOutputCache = new HashMap<>(); private Map, GraphQLInputObjectType> embeddableInputCache = new HashMap<>(); @@ -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"; @@ -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(); } @@ -409,7 +414,7 @@ private GraphQLArgument getArgument(Attribute attribute) { return GraphQLArgument.newArgument() .name(attribute.getName()) - .type((GraphQLInputType) type) + .type(type) .description(description) .build(); } @@ -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 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 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 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 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); @@ -583,7 +550,7 @@ else if (attribute instanceof PluralAttribute .build(); } - @SuppressWarnings( { "rawtypes", "unchecked" } ) + @SuppressWarnings( { "rawtypes" } ) private GraphQLInputObjectField getInputObjectField(Attribute attribute) { GraphQLInputType type = getAttributeInputType(attribute); @@ -598,7 +565,6 @@ private Stream> findBasicAttributes(Collection> at return attributes.stream().filter(it -> it.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC); } - @SuppressWarnings( "rawtypes" ) private GraphQLInputType getAttributeInputType(Attribute attribute) { try{ return (GraphQLInputType) getAttributeType(attribute, true); @@ -607,7 +573,6 @@ private GraphQLInputType getAttributeInputType(Attribute attribute) { } } - @SuppressWarnings( "rawtypes" ) private GraphQLOutputType getAttributeOutputType(Attribute attribute) { try { return (GraphQLOutputType) getAttributeType(attribute, false); @@ -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)) @@ -797,7 +762,7 @@ private GraphQLType getGraphQLTypeFromJavaType(Class clazz) { for (Enum enumValue : ((Class>)clazz).getEnumConstants()) enumBuilder.value(enumValue.name(), ordinal++); - GraphQLType enumType = enumBuilder.build(); + GraphQLEnumType enumType = enumBuilder.build(); setNoOpCoercing(enumType); classCache.putIfAbsent(clazz, enumType); diff --git a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtils.java b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtils.java index 2dd10de47..ebb2341cd 100644 --- a/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtils.java +++ b/graphql-jpa-query-schema/src/main/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtils.java @@ -5,6 +5,7 @@ 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; @@ -45,6 +46,10 @@ public CachedIntrospectionResult(Class entity) { .collect(Collectors.toMap(CachedPropertyDescriptor::getName, it -> it)); } + public Collection getPropertyDescriptors() { + return map.values(); + } + public Optional getPropertyDescriptor(String fieldName) { return Optional.ofNullable(map.getOrDefault(fieldName, null)); } @@ -71,7 +76,7 @@ public PropertyDescriptor getDelegate() { public String getName() { return delegate.getName(); } - + public boolean isAnnotationPresent(Class annotation) { boolean answer; try { diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalcEntityTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalculatedEntityTests.java similarity index 57% rename from graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalcEntityTests.java rename to graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalculatedEntityTests.java index 70641cb8d..c63167e7b 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalcEntityTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalculatedEntityTests.java @@ -1,7 +1,12 @@ package com.introproventures.graphql.jpa.query.schema; -import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor; -import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder; +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.Arrays; +import java.util.List; + +import javax.persistence.EntityManager; + import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -12,14 +17,17 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.util.Assert; -import javax.persistence.EntityManager; +import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor; +import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder; -import static org.assertj.core.api.Assertions.assertThat; +import graphql.ErrorType; +import graphql.GraphQLError; +import graphql.validation.ValidationError; @RunWith(SpringRunner.class) @SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.NONE) @TestPropertySource({"classpath:hibernate.properties"}) -public class CalcEntityTests { +public class CalculatedEntityTests { @SpringBootApplication static class Application { @Bean @@ -48,9 +56,9 @@ public void contextLoads() { @Test public void getAllRecords() { //given - String query = "query GraphQLCalcFields { CalcEntities { select {id title fieldMem fieldFun logic customLogic } } }"; + String query = "query GraphQLCalcFields { CalculatedEntities { select {id title fieldMem fieldFun logic customLogic } } }"; - String expected = "{CalcEntities={select=[{id=1, title=title 1, fieldMem=member, fieldFun=title 1 function, logic=true, customLogic=false}, {id=2, title=title 2, fieldMem=member, fieldFun=title 2 function, logic=true, customLogic=false}]}}"; + String expected = "{CalculatedEntities={select=[{id=1, title=title 1, fieldMem=member, fieldFun=title 1 function, logic=true, customLogic=false}, {id=2, title=title 2, fieldMem=member, fieldFun=title 2 function, logic=true, customLogic=false}]}}"; //when Object result = executor.execute(query).getData(); @@ -61,15 +69,17 @@ public void getAllRecords() { @Test public void testIgnoreFields() { - String query = "query GraphQLCalcFields { CalcEntities { select {id title fieldMem fieldFun logic customLogic hideField hideFieldFunction } } }"; - - String expected = "[ValidationError{validationErrorType=FieldUndefined, queryPath=[CalcEntities, select, hideField], message=Validation error of type FieldUndefined: Field 'hideField' in type 'CalcEntity' is undefined @ 'CalcEntities/select/hideField', locations=[SourceLocation{line=1, column=95}], description='Field 'hideField' in type 'CalcEntity' is undefined'}, ValidationError{validationErrorType=FieldUndefined, queryPath=[CalcEntities, select, hideFieldFunction], message=Validation error of type FieldUndefined: Field 'hideFieldFunction' in type 'CalcEntity' is undefined @ 'CalcEntities/select/hideFieldFunction', locations=[SourceLocation{line=1, column=105}], description='Field 'hideFieldFunction' in type 'CalcEntity' is undefined'}]"; + String query = "query GraphQLCalcFields { CalculatedEntities { select {id title fieldMem fieldFun logic customLogic hideField hideFieldFunction } } }"; //when - Object result = executor.execute(query).getErrors(); + List result = executor.execute(query).getErrors(); //then - assertThat(result.toString()).isEqualTo(expected); + assertThat(result).hasSize(1); + assertThat(result.get(0)).isExactlyInstanceOf(ValidationError.class) + .extracting(ValidationError.class::cast) + .extracting("errorType", "queryPath") + .contains(ErrorType.ValidationError, Arrays.asList("CalculatedEntities", "select", "hideFieldFunction")); } } diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtilsTest.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtilsTest.java index 1eef689a6..0510e9db1 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtilsTest.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/impl/IntrospectionUtilsTest.java @@ -19,7 +19,7 @@ public void testIsTransientNonExisting() throws Exception { } @Test - public void testIsTransientClasss() throws Exception { + public void testIsTransientClass() throws Exception { // then assertThat(IntrospectionUtils.isTransient(entity, "class")).isFalse(); } @@ -31,29 +31,23 @@ public void testIsTransientFunction() throws Exception { assertThat(IntrospectionUtils.isTransient(entity, "hideFieldFunction")).isFalse(); } - @Test - public void testIsTransientId() throws Exception { - // then - assertThat(IntrospectionUtils.isTransient(entity, "id")).isFalse(); - } - - @Test public void testIsTransientFields() throws Exception { // then assertThat(IntrospectionUtils.isTransient(entity, "fieldFun")).isTrue(); assertThat(IntrospectionUtils.isTransient(entity, "fieldMem")).isTrue(); assertThat(IntrospectionUtils.isTransient(entity, "hideField")).isTrue(); + assertThat(IntrospectionUtils.isTransient(entity, "logic")).isTrue(); } @Test - public void testIsTransientOther() throws Exception { + public void testNotTransientFields() throws Exception { // given Class entity = CalculatedEntity.class; // then + assertThat(IntrospectionUtils.isTransient(entity, "id")).isFalse(); assertThat(IntrospectionUtils.isTransient(entity, "info")).isFalse(); - assertThat(IntrospectionUtils.isTransient(entity, "logic")).isTrue(); assertThat(IntrospectionUtils.isTransient(entity, "title")).isFalse(); } diff --git a/graphql-jpa-query-schema/src/test/resources/data.sql b/graphql-jpa-query-schema/src/test/resources/data.sql index 9c580088e..d622bc616 100644 --- a/graphql-jpa-query-schema/src/test/resources/data.sql +++ b/graphql-jpa-query-schema/src/test/resources/data.sql @@ -135,6 +135,6 @@ insert into Boat (id, country, identification) values (1, 'FR', '34567'); -- Calculate entity -insert into calc_entity (id, title, info) values +insert into calculated_entity (id, title, info) values (1, 'title 1', 'inf 1'), (2, 'title 2', 'inf 2');