From 34b0d35c0c2afdf8a861c2c0dc2484f24a8cd37a Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 25 Aug 2019 00:03:38 -0700 Subject: [PATCH 1/6] fix: expose Java feld and read method introspection Api --- .../query/schema/impl/IntrospectionUtils.java | 80 ++++++++++++------- 1 file changed, 51 insertions(+), 29 deletions(-) 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 2921c5058..874e363e2 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 @@ -6,6 +6,7 @@ import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; import java.lang.reflect.Field; +import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; import java.util.Collection; @@ -32,34 +33,38 @@ public static CachedIntrospectionResult introspect(Class entity) { return map.computeIfAbsent(entity, CachedIntrospectionResult::new); } + /** + * Test if Java bean property is transient according to JPA specification + * + * @param entity a Java entity class to introspect + * @param propertyName the name of the property + * @return true if property has Transient annotation or transient field modifier + * @throws RuntimeException if property does not exists + */ public static boolean isTransient(Class entity, String propertyName) { - if(!introspect(entity).hasPropertyDescriptor(propertyName)) { - throw new RuntimeException(new NoSuchFieldException(propertyName)); - } - - return Stream.of(isAnnotationPresent(entity, propertyName, Transient.class), - isModifierPresent(entity, propertyName, Modifier::isTransient)) - .anyMatch(it -> it.isPresent() && it.get() == true); - } - - public static boolean isIgnored(Class entity, String propertyName) { - return isAnnotationPresent(entity, propertyName, GraphQLIgnore.class) - .orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName))); + return introspect(entity).getPropertyDescriptor(propertyName) + .map(it -> it.isAnnotationPresent(Transient.class) + || it.hasModifier(Modifier::isTransient)) + .orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName))); } - private static Optional isAnnotationPresent(Class entity, String propertyName, Class annotation){ + /** + * Test if entity property is annotated with GraphQLIgnore + * + * @param entity a Java entity class to introspect + * @param propertyName the name of the property + * @return true if property has GraphQLIgnore + * @throws RuntimeException if property does not exists + */ + public static boolean isIgnored(Class entity, String propertyName) { return introspect(entity).getPropertyDescriptor(propertyName) - .map(it -> it.isAnnotationPresent(annotation)); + .map(it -> it.isAnnotationPresent(GraphQLIgnore.class)) + .orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName))); } - private static Optional isModifierPresent(Class entity, String propertyName, Function function){ - return introspect(entity).getField(propertyName) - .map(it -> function.apply(it.getModifiers())); - } - public static class CachedIntrospectionResult { - private final Map map; + private final Map descriptors; private final Class entity; private final BeanInfo beanInfo; private final Map fields; @@ -73,26 +78,30 @@ public CachedIntrospectionResult(Class entity) { } this.entity = entity; - this.map = Stream.of(beanInfo.getPropertyDescriptors()) + this.descriptors = Stream.of(beanInfo.getPropertyDescriptors()) .map(CachedPropertyDescriptor::new) .collect(Collectors.toMap(CachedPropertyDescriptor::getName, it -> it)); this.fields = iterate((Class) entity, k -> Optional.ofNullable(k.getSuperclass())) .flatMap(k -> Arrays.stream(k.getDeclaredFields())) - .filter(f -> map.containsKey(f.getName())) + .filter(f -> descriptors.containsKey(f.getName())) .collect(Collectors.toMap(Field::getName, it -> it)); } public Collection getPropertyDescriptors() { - return map.values(); + return descriptors.values(); } + public Collection getFields() { + return fields.values(); + } + public Optional getPropertyDescriptor(String fieldName) { - return Optional.ofNullable(map.getOrDefault(fieldName, null)); + return Optional.ofNullable(descriptors.getOrDefault(fieldName, null)); } public boolean hasPropertyDescriptor(String fieldName) { - return map.containsKey(fieldName); + return descriptors.containsKey(fieldName); } public Optional getField(String fieldName) { @@ -125,19 +134,32 @@ public Class getPropertyType() { public String getName() { return delegate.getName(); } + + public Optional getField() { + return Optional.ofNullable(fields.get(getName())); + } + + public Optional getReadMethod() { + return Optional.ofNullable(delegate.getReadMethod()); + } + + public boolean hasModifier(Function test) { + return getField().map(it -> test.apply(it.getModifiers())) + .orElse(false); + } public boolean isAnnotationPresent(Class annotation) { return isAnnotationPresentOnField(annotation) || isAnnotationPresentOnReadMethod(annotation); } private boolean isAnnotationPresentOnField(Class annotation) { - return Optional.ofNullable(fields.get(delegate.getName())) - .map(f -> f.isAnnotationPresent(annotation)) - .orElse(false); + return getField().map(f -> f.isAnnotationPresent(annotation)) + .orElse(false); } private boolean isAnnotationPresentOnReadMethod(Class annotation) { - return delegate.getReadMethod() != null && delegate.getReadMethod().isAnnotationPresent(annotation); + return getReadMethod().map(m -> m.isAnnotationPresent(annotation)) + .orElse(false); } } From 55ef4e827ea46ab6582a0dd7b7fdb62e7b06393e Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 25 Aug 2019 04:03:50 -0700 Subject: [PATCH 2/6] fix: refactor schema description introspection --- .../schema/impl/GraphQLJpaSchemaBuilder.java | 139 +++------ .../query/schema/impl/IntrospectionUtils.java | 263 ++++++++++++++++-- .../schema/impl/IntrospectionUtilsTest.java | 90 ++++++ .../model/calculated/CalculatedEntity.java | 2 + .../calculated/ParentCalculatedEntity.java | 4 + 5 files changed, 374 insertions(+), 124 deletions(-) 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 1c682b2d9..8bf506277 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 @@ -16,14 +16,9 @@ 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.reflect.AnnotatedElement; import java.lang.reflect.Field; import java.lang.reflect.Member; -import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; @@ -46,14 +41,13 @@ import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore; import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreFilter; import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreOrder; 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.IntrospectionUtils.EntityIntrospectionResult.EntityPropertyDescriptor; import com.introproventures.graphql.jpa.query.schema.impl.PredicateFilter.Criteria; import graphql.Assert; @@ -164,7 +158,7 @@ private GraphQLObjectType getQueryType() { private GraphQLFieldDefinition getQueryFieldByIdDefinition(EntityType entityType) { return GraphQLFieldDefinition.newFieldDefinition() .name(entityType.getName()) - .description(getSchemaDescription( entityType.getJavaType())) + .description(getSchemaDescription(entityType)) .type(getObjectType(entityType)) .dataFetcher(new GraphQLJpaSimpleDataFetcher(entityManager, entityType, toManyDefaultOptional)) .argument(entityType.getAttributes().stream() @@ -426,7 +420,7 @@ private GraphQLInputObjectField getWhereInputRelationField(Attribute attrib ManagedType foreignType = getForeignType(attribute); String type = resolveWhereInputTypeName(foreignType); - String description = getSchemaDescription(attribute.getJavaMember()); + String description = getSchemaDescription(attribute); return GraphQLInputObjectField.newInputObjectField() .name(attribute.getName()) @@ -437,7 +431,7 @@ private GraphQLInputObjectField getWhereInputRelationField(Attribute attrib private GraphQLInputObjectField getWhereInputField(Attribute attribute) { GraphQLInputType type = getWhereAttributeType(attribute); - String description = getSchemaDescription(attribute.getJavaMember()); + String description = getSchemaDescription(attribute); if (type instanceof GraphQLInputType) { return GraphQLInputObjectField.newInputObjectField() @@ -599,7 +593,7 @@ else if (attribute.getJavaMember().getClass().isAssignableFrom(Field.class) private GraphQLArgument getArgument(Attribute attribute) { GraphQLInputType type = getAttributeInputType(attribute); - String description = getSchemaDescription(attribute.getJavaMember()); + String description = getSchemaDescription(attribute); return GraphQLArgument.newArgument() .name(attribute.getName()) @@ -619,7 +613,7 @@ private GraphQLType getEmbeddableType(EmbeddableType embeddableType, boolean if (input) { graphQLType = GraphQLInputObjectType.newInputObject() .name(embeddableTypeName) - .description(getSchemaDescription(embeddableType.getJavaType())) + .description(getSchemaDescription(embeddableType)) .fields(embeddableType.getAttributes().stream() .filter(this::isNotIgnored) .map(this::getInputObjectField) @@ -629,7 +623,7 @@ private GraphQLType getEmbeddableType(EmbeddableType embeddableType, boolean } else { graphQLType = GraphQLObjectType.newObject() .name(embeddableTypeName) - .description(getSchemaDescription(embeddableType.getJavaType())) + .description(getSchemaDescription(embeddableType)) .fields(embeddableType.getAttributes().stream() .filter(this::isNotIgnored) .map(this::getObjectField) @@ -655,7 +649,7 @@ private GraphQLObjectType getObjectType(EntityType entityType) { private GraphQLObjectType computeObjectType(EntityType entityType) { return GraphQLObjectType.newObject() .name(entityType.getName()) - .description(getSchemaDescription(entityType.getJavaType())) + .description(getSchemaDescription(entityType)) .fields(getEntityAttributesFields(entityType)) .fields(getTransientFields(entityType.getJavaType())) .build(); @@ -673,27 +667,29 @@ private List getEntityAttributesFields(EntityType ent private List getTransientFields(Class clazz) { return IntrospectionUtils.introspect(clazz) .getPropertyDescriptors().stream() - .filter(it -> IntrospectionUtils.isTransient(clazz, it.getName())) - .filter(it -> !it.isAnnotationPresent(GraphQLIgnore.class)) - .map(CachedPropertyDescriptor::getDelegate) + .filter(EntityPropertyDescriptor::isTransient) + .filter(EntityPropertyDescriptor::isNotIgnored) .map(this::getJavaFieldDefinition) .collect(Collectors.toList()); } @SuppressWarnings( { "rawtypes" } ) - private GraphQLFieldDefinition getJavaFieldDefinition(PropertyDescriptor propertyDescriptor) { + private GraphQLFieldDefinition getJavaFieldDefinition(EntityPropertyDescriptor propertyDescriptor) { GraphQLOutputType type = getGraphQLTypeFromJavaType(propertyDescriptor.getPropertyType()); DataFetcher dataFetcher = PropertyDataFetcher.fetching(propertyDescriptor.getName()); + + String description = propertyDescriptor.getSchemaDescription() + .orElse(null); return GraphQLFieldDefinition.newFieldDefinition() - .name(propertyDescriptor.getName()) - .description(getSchemaDescription(propertyDescriptor.getPropertyType())) - .type(type) - .dataFetcher(dataFetcher) - .build(); + .name(propertyDescriptor.getName()) + .description(description) + .type(type) + .dataFetcher(dataFetcher) + .build(); } - private GraphQLFieldDefinition getObjectField(Attribute attribute) { + private GraphQLFieldDefinition getObjectField(Attribute attribute) { return getObjectField(attribute, null); } @@ -751,7 +747,7 @@ else if (attribute instanceof PluralAttribute return GraphQLFieldDefinition.newFieldDefinition() .name(attribute.getName()) - .description(getSchemaDescription(attribute.getJavaMember())) + .description(getSchemaDescription(attribute)) .type(type) .dataFetcher(dataFetcher) .argument(arguments) @@ -780,7 +776,7 @@ private GraphQLInputObjectField getInputObjectField(Attribute attribute) { return GraphQLInputObjectField.newInputObjectField() .name(attribute.getName()) - .description(getSchemaDescription(attribute.getJavaMember())) + .description(getSchemaDescription(attribute)) .type(type) .build(); } @@ -878,86 +874,27 @@ protected final boolean isValidAssociation(Attribute attribute) { return isOneToMany(attribute) || isToOne(attribute); } - - - private String getSchemaDescription(Member member) { - if (member instanceof AnnotatedElement) { - String desc = getSchemaDescription((AnnotatedElement) member); - if (desc != null) { - return(desc); - } - } - - //The given Member has no @GraphQLDescription set. - //If the Member is a Method it might be a getter/setter, see if the property it represents - //is annotated with @GraphQLDescription - //Alternatively if the Member is a Field its getter might be annotated, see if its getter - //is annotated with @GraphQLDescription - if (member instanceof Method) { - Field fieldMember = getFieldByAccessor((Method)member); - if (fieldMember != null) { - return(getSchemaDescription((AnnotatedElement) fieldMember)); - } - } else if (member instanceof Field) { - Method fieldGetter = getGetterOfField((Field)member); - if (fieldGetter != null) { - return(getSchemaDescription((AnnotatedElement) fieldGetter)); - } - } - return null; + private String getSchemaDescription(Attribute attribute) { + return IntrospectionUtils.introspect(attribute.getDeclaringType() + .getJavaType()) + .getPropertyDescriptor(attribute) + .flatMap(it -> it.getSchemaDescription()) + .orElse(null); } - - private Method getGetterOfField(Field field) { - try { - Class clazz = field.getDeclaringClass(); - BeanInfo info = Introspector.getBeanInfo(clazz); - PropertyDescriptor[] props = info.getPropertyDescriptors(); - for (PropertyDescriptor pd : props) { - if (pd.getName().equals(field.getName())) { - return(pd.getReadMethod()); - } - } - } catch (IntrospectionException e) { - e.printStackTrace(); - } - - return(null); - } - - //from https://stackoverflow.com/questions/13192734/getting-a-property-field-name-using-getter-method-of-a-pojo-java-bean/13514566 - private static Field getFieldByAccessor(Method method) { - try { - Class clazz = method.getDeclaringClass(); - BeanInfo info = Introspector.getBeanInfo(clazz); - PropertyDescriptor[] props = info.getPropertyDescriptors(); - for (PropertyDescriptor pd : props) { - if(method.equals(pd.getWriteMethod()) || method.equals(pd.getReadMethod())) { - String fieldName = pd.getName(); - try { - return(clazz.getDeclaredField(fieldName)); - } catch (Throwable t) { - log.error("class '" + clazz.getName() + "' contains method '" + method.getName() + "' which is an accessor for a Field named '" + fieldName + "', error getting the field:", t); - return(null); - } - } - } - } catch (Throwable t) { - log.error("error finding Field for accessor with name '" + method.getName() + "'", t); - } - - return null; + + private String getSchemaDescription(EntityType entityType) { + return IntrospectionUtils.introspect(entityType.getJavaType()) + .getSchemaDescription() + .orElse(null); } - private String getSchemaDescription(AnnotatedElement annotatedElement) { - if (annotatedElement != null) { - GraphQLDescription schemaDocumentation = annotatedElement.getAnnotation(GraphQLDescription.class); - return schemaDocumentation != null ? schemaDocumentation.value() : null; - } - - return null; + private String getSchemaDescription(EmbeddableType embeddableType) { + return IntrospectionUtils.introspect(embeddableType.getJavaType()) + .getSchemaDescription() + .orElse(null); } - + private boolean isNotIgnored(EmbeddableType attribute) { return isNotIgnored(attribute.getJavaType()); } 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 874e363e2..35f3da9c8 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,7 +5,9 @@ import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; +import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; +import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; @@ -23,14 +25,22 @@ import java.util.stream.StreamSupport; import javax.persistence.Transient; +import javax.persistence.metamodel.Attribute; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore; +import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult.EntityPropertyDescriptor; public class IntrospectionUtils { - private static final Map, CachedIntrospectionResult> map = new LinkedHashMap<>(); + private static final Logger log = LoggerFactory.getLogger(IntrospectionUtils.class); + + private static final Map, EntityIntrospectionResult> map = new LinkedHashMap<>(); - public static CachedIntrospectionResult introspect(Class entity) { - return map.computeIfAbsent(entity, CachedIntrospectionResult::new); + public static EntityIntrospectionResult introspect(Class entity) { + return map.computeIfAbsent(entity, EntityIntrospectionResult::new); } /** @@ -43,34 +53,57 @@ public static CachedIntrospectionResult introspect(Class entity) { */ public static boolean isTransient(Class entity, String propertyName) { return introspect(entity).getPropertyDescriptor(propertyName) - .map(it -> it.isAnnotationPresent(Transient.class) - || it.hasModifier(Modifier::isTransient)) + .map(EntityPropertyDescriptor::isTransient) .orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName))); } + + /** + * Test if Java bean property is persistent according to JPA specification + * + * @param entity a Java entity class to introspect + * @param propertyName the name of the property + * @return true if property is persitent + * @throws RuntimeException if property does not exists + */ + public static boolean isPesistent(Class entity, String propertyName) { + return !isTransient(entity, propertyName); + } /** * Test if entity property is annotated with GraphQLIgnore * * @param entity a Java entity class to introspect * @param propertyName the name of the property - * @return true if property has GraphQLIgnore + * @return true if property has GraphQLIgnore annotation * @throws RuntimeException if property does not exists */ public static boolean isIgnored(Class entity, String propertyName) { return introspect(entity).getPropertyDescriptor(propertyName) - .map(it -> it.isAnnotationPresent(GraphQLIgnore.class)) + .map(EntityPropertyDescriptor::isIgnored) .orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName))); } + + /** + * Test if entity property is not ignored + * + * @param entity a Java entity class to introspect + * @param propertyName the name of the property + * @return true if property has no GraphQLIgnore annotation + * @throws RuntimeException if property does not exists + */ + public static boolean isNotIgnored(Class entity, String propertyName) { + return !isIgnored(entity, propertyName); + } - public static class CachedIntrospectionResult { + public static class EntityIntrospectionResult { - private final Map descriptors; + private final Map descriptors; private final Class entity; private final BeanInfo beanInfo; private final Map fields; @SuppressWarnings("rawtypes") - public CachedIntrospectionResult(Class entity) { + public EntityIntrospectionResult(Class entity) { try { this.beanInfo = Introspector.getBeanInfo(entity); } catch (IntrospectionException cause) { @@ -79,16 +112,15 @@ public CachedIntrospectionResult(Class entity) { this.entity = entity; this.descriptors = Stream.of(beanInfo.getPropertyDescriptors()) - .map(CachedPropertyDescriptor::new) - .collect(Collectors.toMap(CachedPropertyDescriptor::getName, it -> it)); + .map(EntityPropertyDescriptor::new) + .collect(Collectors.toMap(EntityPropertyDescriptor::getName, it -> it)); - this.fields = iterate((Class) entity, k -> Optional.ofNullable(k.getSuperclass())) - .flatMap(k -> Arrays.stream(k.getDeclaredFields())) - .filter(f -> descriptors.containsKey(f.getName())) - .collect(Collectors.toMap(Field::getName, it -> it)); + this.fields = getClasses().flatMap(k -> Arrays.stream(k.getDeclaredFields())) + .filter(f -> descriptors.containsKey(f.getName())) + .collect(Collectors.toMap(Field::getName, it -> it)); } - - public Collection getPropertyDescriptors() { + + public Collection getPropertyDescriptors() { return descriptors.values(); } @@ -96,9 +128,13 @@ public Collection getFields() { return fields.values(); } - public Optional getPropertyDescriptor(String fieldName) { + public Optional getPropertyDescriptor(String fieldName) { return Optional.ofNullable(descriptors.getOrDefault(fieldName, null)); } + + public Optional getPropertyDescriptor(Attribute attribute) { + return getPropertyDescriptor(attribute.getName()); + } public boolean hasPropertyDescriptor(String fieldName) { return descriptors.containsKey(fieldName); @@ -115,11 +151,24 @@ public Class getEntity() { public BeanInfo getBeanInfo() { return beanInfo; } + + public Optional getSchemaDescription() { + return getClasses().map(cls -> Optional.ofNullable(cls.getAnnotation(GraphQLDescription.class)) + .map(GraphQLDescription::value)) + .filter(Optional::isPresent) + .map(Optional::get) + .findFirst(); + } + + public Stream> getClasses() { + return iterate(entity, k -> Optional.ofNullable(k.getSuperclass())); + } + + public class EntityPropertyDescriptor { - public class CachedPropertyDescriptor { private final PropertyDescriptor delegate; - public CachedPropertyDescriptor(PropertyDescriptor delegate) { + public EntityPropertyDescriptor(PropertyDescriptor delegate) { this.delegate = delegate; } @@ -139,11 +188,46 @@ public Optional getField() { return Optional.ofNullable(fields.get(getName())); } + public boolean hasField() { + return getField().isPresent(); + } + public Optional getReadMethod() { return Optional.ofNullable(delegate.getReadMethod()); } - public boolean hasModifier(Function test) { + public Optional getAnnotation(Class annotationClass) { + return getReadMethod().map(m -> m.getAnnotation(annotationClass)) + .map(Optional::of) + .orElseGet(() -> getField().map(f -> f.getAnnotation(annotationClass))); + } + + public Optional getSchemaDescription() { + return getAnnotation(GraphQLDescription.class).map(it -> it.value()); + } + + public boolean hasSchemaDescription() { + return getSchemaDescription().isPresent(); + } + + public boolean isTransient() { + return isAnnotationPresent(Transient.class) + || hasFieldModifier(Modifier::isTransient); + } + + public boolean isIgnored() { + return isAnnotationPresent(GraphQLIgnore.class); + } + + public boolean isNotIgnored() { + return !isIgnored(); + } + + public boolean hasReadMethod() { + return getReadMethod().isPresent(); + } + + public boolean hasFieldModifier(Function test) { return getField().map(it -> test.apply(it.getModifiers())) .orElse(false); } @@ -162,7 +246,140 @@ private boolean isAnnotationPresentOnReadMethod(Class anno .orElse(false); } + @Override + public String toString() { + return "EntityPropertyDescriptor [delegate=" + delegate + "]"; + } + + private EntityIntrospectionResult getEnclosingInstance() { + return EntityIntrospectionResult.this; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + getEnclosingInstance().hashCode(); + result = prime * result + Objects.hash(delegate); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EntityPropertyDescriptor other = (EntityPropertyDescriptor) obj; + if (!getEnclosingInstance().equals(other.getEnclosingInstance())) + return false; + return Objects.equals(delegate, other.delegate); + } } + + @Override + public int hashCode() { + return Objects.hash(beanInfo, entity); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + EntityIntrospectionResult other = (EntityIntrospectionResult) obj; + return Objects.equals(beanInfo, other.beanInfo) && Objects.equals(entity, other.entity); + } + + @Override + public String toString() { + return "EntityIntrospectionResult [beanInfo=" + beanInfo + "]"; + } + + } + + private String getSchemaDescription(Member member) { + if (member instanceof AnnotatedElement) { + String desc = getSchemaDescription((AnnotatedElement) member); + if (desc != null) { + return (desc); + } + } + + //The given Member has no @GraphQLDescription set. + //If the Member is a Method it might be a getter/setter, see if the property it represents + //is annotated with @GraphQLDescription + //Alternatively if the Member is a Field its getter might be annotated, see if its getter + //is annotated with @GraphQLDescription + if (member instanceof Method) { + Field fieldMember = getFieldByAccessor((Method) member); + if (fieldMember != null) { + return (getSchemaDescription((AnnotatedElement) fieldMember)); + } + } else if (member instanceof Field) { + Method fieldGetter = getGetterOfField((Field) member); + if (fieldGetter != null) { + return (getSchemaDescription((AnnotatedElement) fieldGetter)); + } + } + + return null; + } + + private Method getGetterOfField(Field field) { + try { + Class clazz = field.getDeclaringClass(); + BeanInfo info = Introspector.getBeanInfo(clazz); + PropertyDescriptor[] props = info.getPropertyDescriptors(); + for (PropertyDescriptor pd : props) { + if (pd.getName().equals(field.getName())) { + return (pd.getReadMethod()); + } + } + } catch (IntrospectionException e) { + e.printStackTrace(); + } + + return (null); + } + + //from https://stackoverflow.com/questions/13192734/getting-a-property-field-name-using-getter-method-of-a-pojo-java-bean/13514566 + private static Field getFieldByAccessor(Method method) { + try { + Class clazz = method.getDeclaringClass(); + BeanInfo info = Introspector.getBeanInfo(clazz); + PropertyDescriptor[] props = info.getPropertyDescriptors(); + for (PropertyDescriptor pd : props) { + if (method.equals(pd.getWriteMethod()) || method.equals(pd.getReadMethod())) { + String fieldName = pd.getName(); + try { + return (clazz.getDeclaredField(fieldName)); + } catch (Throwable t) { + log.error("class '" + clazz.getName() + "' contains method '" + method.getName() + "' which is an accessor for a Field named '" + fieldName + "', error getting the field:", + t); + return (null); + } + } + } + } catch (Throwable t) { + log.error("error finding Field for accessor with name '" + method.getName() + "'", t); + } + + return null; + } + + private String getSchemaDescription(AnnotatedElement annotatedElement) { + if (annotatedElement != null) { + GraphQLDescription schemaDocumentation = annotatedElement.getAnnotation(GraphQLDescription.class); + return schemaDocumentation != null ? schemaDocumentation.value() : null; + } + + return null; } /** @@ -172,7 +389,7 @@ private boolean isAnnotationPresentOnReadMethod(Class anno * This version has been modified to end when Optional.empty() * is returned from the fetchNextFunction. */ - protected static Stream iterate( T seed, Function> fetchNextFunction ) { + public static Stream iterate(T seed, Function> fetchNextFunction) { Objects.requireNonNull(fetchNextFunction); Iterator iterator = new Iterator() { 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 6febd3d13..18a6efa9e 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 @@ -1,10 +1,19 @@ package com.introproventures.graphql.jpa.query.schema.impl; import static org.assertj.core.api.Assertions.assertThat; +import static org.mockito.Mockito.when; + +import java.util.Optional; + +import javax.persistence.metamodel.Attribute; import org.junit.Test; +import org.mockito.Mockito; +import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult; +import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult.EntityPropertyDescriptor; import com.introproventures.graphql.jpa.query.schema.model.calculated.CalculatedEntity; +import com.introproventures.graphql.jpa.query.schema.model.calculated.ParentCalculatedEntity; public class IntrospectionUtilsTest { @@ -36,6 +45,13 @@ public void testIsTransientFunction() throws Exception { assertThat(IntrospectionUtils.isTransient(entity, "hideFieldFunction")).isFalse(); } + @Test + public void testIsPersistentFunction() throws Exception { + // then + assertThat(IntrospectionUtils.isPesistent(entity, "fieldFun")).isFalse(); + assertThat(IntrospectionUtils.isPesistent(entity, "hideFieldFunction")).isTrue(); + } + @Test public void testIsTransientFields() throws Exception { // then @@ -71,5 +87,79 @@ public void shouldIgnoreMethodsThatAreAnnotatedWithGraphQLIgnore() { assertThat(IntrospectionUtils.isIgnored(entity, "ignoredTransientValue")).isTrue(); assertThat(IntrospectionUtils.isIgnored(entity, "hideField")).isTrue(); assertThat(IntrospectionUtils.isIgnored(entity, "parentGraphQLIgnore")).isTrue(); + + assertThat(IntrospectionUtils.isIgnored(entity, "transientModifier")).isFalse(); + assertThat(IntrospectionUtils.isIgnored(entity, "parentTransientModifier")).isFalse(); + assertThat(IntrospectionUtils.isIgnored(entity, "parentTransient")).isFalse(); + assertThat(IntrospectionUtils.isIgnored(entity, "parentTransientGetter")).isFalse(); } + + @Test + public void shouldNotIgnoreMethodsThatAreNotAnnotatedWithGraphQLIgnore() { + //then + assertThat(IntrospectionUtils.isNotIgnored(entity, "propertyIgnoredOnGetter")).isFalse(); + assertThat(IntrospectionUtils.isNotIgnored(entity, "ignoredTransientValue")).isFalse(); + assertThat(IntrospectionUtils.isNotIgnored(entity, "hideField")).isFalse(); + assertThat(IntrospectionUtils.isNotIgnored(entity, "parentGraphQLIgnore")).isFalse(); + + assertThat(IntrospectionUtils.isNotIgnored(entity, "transientModifier")).isTrue(); + assertThat(IntrospectionUtils.isNotIgnored(entity, "parentTransientModifier")).isTrue(); + assertThat(IntrospectionUtils.isNotIgnored(entity, "parentTransient")).isTrue(); + assertThat(IntrospectionUtils.isNotIgnored(entity, "parentTransientGetter")).isTrue(); + } + + @SuppressWarnings("rawtypes") + @Test + public void shouldGetClassesInHierarchy() { + //when + Class[] result = IntrospectionUtils.introspect(entity) + .getClasses() + .toArray(Class[]::new); + + //then + assertThat(result).containsExactly(CalculatedEntity.class, + ParentCalculatedEntity.class, + Object.class); + } + + @Test + public void testGetPropertyDescriptorsSchemaDescription() throws Exception { + // when + EntityIntrospectionResult result = IntrospectionUtils.introspect(CalculatedEntity.class); + + // then + assertThat(result.getPropertyDescriptors()) + .extracting(EntityPropertyDescriptor::getSchemaDescription) + .filteredOn(Optional::isPresent) + .extracting(Optional::get) + .containsOnly("title", + "transientModifier", + "i desc member", + "i desc function", + "getParentTransientGetter", + "parentTransientModifier"); + } + + @Test + public void testGetPropertyDescriptorSchemaDescriptionByAttribute() throws Exception { + Attribute attribute = Mockito.mock(Attribute.class); + + when(attribute.getName()).thenReturn("title"); + + // when + Optional result = IntrospectionUtils.introspect(CalculatedEntity.class) + .getPropertyDescriptor(attribute); + // then + assertThat(result.isPresent()).isTrue(); + } + + @Test + public void testGetParentEntitySchemaDescription() throws Exception { + // when + EntityIntrospectionResult result = IntrospectionUtils.introspect(CalculatedEntity.class); + + // then + assertThat(result.getSchemaDescription()).contains("ParentCalculatedEntity description"); + } + } diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/CalculatedEntity.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/CalculatedEntity.java index da537cf51..250e2b901 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/CalculatedEntity.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/CalculatedEntity.java @@ -47,10 +47,12 @@ public class CalculatedEntity extends ParentCalculatedEntity { @Id Long id; + @GraphQLDescription("title") String title; String info; + @GraphQLDescription("transientModifier") transient Integer transientModifier; // transient property @GraphQLIgnore diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/ParentCalculatedEntity.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/ParentCalculatedEntity.java index b7ff4f607..83527d7fb 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/ParentCalculatedEntity.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/model/calculated/ParentCalculatedEntity.java @@ -3,16 +3,19 @@ import javax.persistence.MappedSuperclass; import javax.persistence.Transient; +import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore; import lombok.Data; +@GraphQLDescription("ParentCalculatedEntity description") @MappedSuperclass @Data public class ParentCalculatedEntity { private Integer parentField; // persistent property + @GraphQLDescription("parentTransientModifier") private transient String parentTransientModifier; // transient property @GraphQLIgnore @@ -35,6 +38,7 @@ public class ParentCalculatedEntity { private String parentTransientGraphQLIgnoreGetter; @Transient // transient getter property + @GraphQLDescription("getParentTransientGetter") public String getParentTransientGetter() { return parentTransientGetter; } From d0cd37c1df2dd4c8e2c33499befebba79161f4c3 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 25 Aug 2019 04:19:15 -0700 Subject: [PATCH 3/6] fix: polish introspection class names and remove obsolete code --- .../schema/impl/GraphQLJpaSchemaBuilder.java | 8 +- .../query/schema/impl/IntrospectionUtils.java | 113 +++--------------- .../schema/impl/IntrospectionUtilsTest.java | 7 +- 3 files changed, 26 insertions(+), 102 deletions(-) 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 8bf506277..a1cdc39ef 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 @@ -47,7 +47,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.EntityIntrospectionResult.EntityPropertyDescriptor; +import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult.AttributePropertyDescriptor; import com.introproventures.graphql.jpa.query.schema.impl.PredicateFilter.Criteria; import graphql.Assert; @@ -667,14 +667,14 @@ private List getEntityAttributesFields(EntityType ent private List getTransientFields(Class clazz) { return IntrospectionUtils.introspect(clazz) .getPropertyDescriptors().stream() - .filter(EntityPropertyDescriptor::isTransient) - .filter(EntityPropertyDescriptor::isNotIgnored) + .filter(AttributePropertyDescriptor::isTransient) + .filter(AttributePropertyDescriptor::isNotIgnored) .map(this::getJavaFieldDefinition) .collect(Collectors.toList()); } @SuppressWarnings( { "rawtypes" } ) - private GraphQLFieldDefinition getJavaFieldDefinition(EntityPropertyDescriptor propertyDescriptor) { + private GraphQLFieldDefinition getJavaFieldDefinition(AttributePropertyDescriptor propertyDescriptor) { GraphQLOutputType type = getGraphQLTypeFromJavaType(propertyDescriptor.getPropertyType()); DataFetcher dataFetcher = PropertyDataFetcher.fetching(propertyDescriptor.getName()); 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 35f3da9c8..42b96af83 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,9 +5,7 @@ import java.beans.Introspector; import java.beans.PropertyDescriptor; import java.lang.annotation.Annotation; -import java.lang.reflect.AnnotatedElement; import java.lang.reflect.Field; -import java.lang.reflect.Member; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.Arrays; @@ -32,7 +30,7 @@ import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription; import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore; -import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult.EntityPropertyDescriptor; +import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult.AttributePropertyDescriptor; public class IntrospectionUtils { private static final Logger log = LoggerFactory.getLogger(IntrospectionUtils.class); @@ -53,7 +51,7 @@ public static EntityIntrospectionResult introspect(Class entity) { */ public static boolean isTransient(Class entity, String propertyName) { return introspect(entity).getPropertyDescriptor(propertyName) - .map(EntityPropertyDescriptor::isTransient) + .map(AttributePropertyDescriptor::isTransient) .orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName))); } @@ -79,7 +77,7 @@ public static boolean isPesistent(Class entity, String propertyName) { */ public static boolean isIgnored(Class entity, String propertyName) { return introspect(entity).getPropertyDescriptor(propertyName) - .map(EntityPropertyDescriptor::isIgnored) + .map(AttributePropertyDescriptor::isIgnored) .orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName))); } @@ -97,7 +95,7 @@ public static boolean isNotIgnored(Class entity, String propertyName) { public static class EntityIntrospectionResult { - private final Map descriptors; + private final Map descriptors; private final Class entity; private final BeanInfo beanInfo; private final Map fields; @@ -112,15 +110,15 @@ public EntityIntrospectionResult(Class entity) { this.entity = entity; this.descriptors = Stream.of(beanInfo.getPropertyDescriptors()) - .map(EntityPropertyDescriptor::new) - .collect(Collectors.toMap(EntityPropertyDescriptor::getName, it -> it)); + .map(AttributePropertyDescriptor::new) + .collect(Collectors.toMap(AttributePropertyDescriptor::getName, it -> it)); this.fields = getClasses().flatMap(k -> Arrays.stream(k.getDeclaredFields())) .filter(f -> descriptors.containsKey(f.getName())) .collect(Collectors.toMap(Field::getName, it -> it)); } - public Collection getPropertyDescriptors() { + public Collection getPropertyDescriptors() { return descriptors.values(); } @@ -128,11 +126,11 @@ public Collection getFields() { return fields.values(); } - public Optional getPropertyDescriptor(String fieldName) { + public Optional getPropertyDescriptor(String fieldName) { return Optional.ofNullable(descriptors.getOrDefault(fieldName, null)); } - public Optional getPropertyDescriptor(Attribute attribute) { + public Optional getPropertyDescriptor(Attribute attribute) { return getPropertyDescriptor(attribute.getName()); } @@ -160,15 +158,19 @@ public Optional getSchemaDescription() { .findFirst(); } + public boolean hasSchemaDescription() { + return getSchemaDescription().isPresent(); + } + public Stream> getClasses() { return iterate(entity, k -> Optional.ofNullable(k.getSuperclass())); } - public class EntityPropertyDescriptor { + public class AttributePropertyDescriptor { private final PropertyDescriptor delegate; - public EntityPropertyDescriptor(PropertyDescriptor delegate) { + public AttributePropertyDescriptor(PropertyDescriptor delegate) { this.delegate = delegate; } @@ -198,12 +200,12 @@ public Optional getReadMethod() { public Optional getAnnotation(Class annotationClass) { return getReadMethod().map(m -> m.getAnnotation(annotationClass)) - .map(Optional::of) + .map(Optional::of) // Java 8 support .orElseGet(() -> getField().map(f -> f.getAnnotation(annotationClass))); } public Optional getSchemaDescription() { - return getAnnotation(GraphQLDescription.class).map(it -> it.value()); + return getAnnotation(GraphQLDescription.class).map(GraphQLDescription::value); } public boolean hasSchemaDescription() { @@ -272,7 +274,7 @@ public boolean equals(Object obj) { return false; if (getClass() != obj.getClass()) return false; - EntityPropertyDescriptor other = (EntityPropertyDescriptor) obj; + AttributePropertyDescriptor other = (AttributePropertyDescriptor) obj; if (!getEnclosingInstance().equals(other.getEnclosingInstance())) return false; return Objects.equals(delegate, other.delegate); @@ -303,85 +305,6 @@ public String toString() { } - private String getSchemaDescription(Member member) { - if (member instanceof AnnotatedElement) { - String desc = getSchemaDescription((AnnotatedElement) member); - if (desc != null) { - return (desc); - } - } - - //The given Member has no @GraphQLDescription set. - //If the Member is a Method it might be a getter/setter, see if the property it represents - //is annotated with @GraphQLDescription - //Alternatively if the Member is a Field its getter might be annotated, see if its getter - //is annotated with @GraphQLDescription - if (member instanceof Method) { - Field fieldMember = getFieldByAccessor((Method) member); - if (fieldMember != null) { - return (getSchemaDescription((AnnotatedElement) fieldMember)); - } - } else if (member instanceof Field) { - Method fieldGetter = getGetterOfField((Field) member); - if (fieldGetter != null) { - return (getSchemaDescription((AnnotatedElement) fieldGetter)); - } - } - - return null; - } - - private Method getGetterOfField(Field field) { - try { - Class clazz = field.getDeclaringClass(); - BeanInfo info = Introspector.getBeanInfo(clazz); - PropertyDescriptor[] props = info.getPropertyDescriptors(); - for (PropertyDescriptor pd : props) { - if (pd.getName().equals(field.getName())) { - return (pd.getReadMethod()); - } - } - } catch (IntrospectionException e) { - e.printStackTrace(); - } - - return (null); - } - - //from https://stackoverflow.com/questions/13192734/getting-a-property-field-name-using-getter-method-of-a-pojo-java-bean/13514566 - private static Field getFieldByAccessor(Method method) { - try { - Class clazz = method.getDeclaringClass(); - BeanInfo info = Introspector.getBeanInfo(clazz); - PropertyDescriptor[] props = info.getPropertyDescriptors(); - for (PropertyDescriptor pd : props) { - if (method.equals(pd.getWriteMethod()) || method.equals(pd.getReadMethod())) { - String fieldName = pd.getName(); - try { - return (clazz.getDeclaredField(fieldName)); - } catch (Throwable t) { - log.error("class '" + clazz.getName() + "' contains method '" + method.getName() + "' which is an accessor for a Field named '" + fieldName + "', error getting the field:", - t); - return (null); - } - } - } - } catch (Throwable t) { - log.error("error finding Field for accessor with name '" + method.getName() + "'", t); - } - - return null; - } - - private String getSchemaDescription(AnnotatedElement annotatedElement) { - if (annotatedElement != null) { - GraphQLDescription schemaDocumentation = annotatedElement.getAnnotation(GraphQLDescription.class); - return schemaDocumentation != null ? schemaDocumentation.value() : null; - } - - return null; - } - /** * The following method is borrowed from Streams.iterate, * however Streams.iterate is designed to create infinite streams. 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 18a6efa9e..4fcc65c8c 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 @@ -11,7 +11,7 @@ import org.mockito.Mockito; import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult; -import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult.EntityPropertyDescriptor; +import com.introproventures.graphql.jpa.query.schema.impl.IntrospectionUtils.EntityIntrospectionResult.AttributePropertyDescriptor; import com.introproventures.graphql.jpa.query.schema.model.calculated.CalculatedEntity; import com.introproventures.graphql.jpa.query.schema.model.calculated.ParentCalculatedEntity; @@ -129,7 +129,7 @@ public void testGetPropertyDescriptorsSchemaDescription() throws Exception { // then assertThat(result.getPropertyDescriptors()) - .extracting(EntityPropertyDescriptor::getSchemaDescription) + .extracting(AttributePropertyDescriptor::getSchemaDescription) .filteredOn(Optional::isPresent) .extracting(Optional::get) .containsOnly("title", @@ -147,7 +147,7 @@ public void testGetPropertyDescriptorSchemaDescriptionByAttribute() throws Excep when(attribute.getName()).thenReturn("title"); // when - Optional result = IntrospectionUtils.introspect(CalculatedEntity.class) + Optional result = IntrospectionUtils.introspect(CalculatedEntity.class) .getPropertyDescriptor(attribute); // then assertThat(result.isPresent()).isTrue(); @@ -160,6 +160,7 @@ public void testGetParentEntitySchemaDescription() throws Exception { // then assertThat(result.getSchemaDescription()).contains("ParentCalculatedEntity description"); + assertThat(result.hasSchemaDescription()).isTrue(); } } From 4990e0e66e0ca552ee467a85e58e678bab4f26f4 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 25 Aug 2019 04:27:23 -0700 Subject: [PATCH 4/6] polish --- .../graphql/jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) 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 a1cdc39ef..d49940794 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 @@ -879,7 +879,7 @@ private String getSchemaDescription(Attribute attribute) { return IntrospectionUtils.introspect(attribute.getDeclaringType() .getJavaType()) .getPropertyDescriptor(attribute) - .flatMap(it -> it.getSchemaDescription()) + .flatMap(AttributePropertyDescriptor::getSchemaDescription) .orElse(null); } From a8889da91350c6eea39ca00fd7ec334ab8a06937 Mon Sep 17 00:00:00 2001 From: Igor Dianov Date: Sun, 25 Aug 2019 04:32:16 -0700 Subject: [PATCH 5/6] polish formatting --- .../jpa/query/schema/impl/GraphQLJpaSchemaBuilder.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) 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 d49940794..8174b49d1 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 @@ -878,9 +878,9 @@ protected final boolean isValidAssociation(Attribute attribute) { private String getSchemaDescription(Attribute attribute) { return IntrospectionUtils.introspect(attribute.getDeclaringType() .getJavaType()) - .getPropertyDescriptor(attribute) - .flatMap(AttributePropertyDescriptor::getSchemaDescription) - .orElse(null); + .getPropertyDescriptor(attribute) + .flatMap(AttributePropertyDescriptor::getSchemaDescription) + .orElse(null); } private String getSchemaDescription(EntityType entityType) { From 3c545a89306159c94cb02731a12dd161862d9151 Mon Sep 17 00:00:00 2001 From: Mateusz Nowak Date: Mon, 26 Aug 2019 12:04:10 +0200 Subject: [PATCH 6/6] Add test case that validates schema after it is created (#170) --- .../query/schema/CalculatedEntityTests.java | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalculatedEntityTests.java b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalculatedEntityTests.java index e63463bfb..044d298ed 100644 --- a/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalculatedEntityTests.java +++ b/graphql-jpa-query-schema/src/test/java/com/introproventures/graphql/jpa/query/schema/CalculatedEntityTests.java @@ -1,11 +1,16 @@ package com.introproventures.graphql.jpa.query.schema; +import java.util.Optional; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.tuple; +import static org.assertj.core.api.BDDAssertions.then; import static org.assertj.core.util.Lists.list; import javax.persistence.EntityManager; +import graphql.schema.GraphQLFieldDefinition; +import graphql.schema.GraphQLSchema; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; @@ -46,6 +51,9 @@ public GraphQLSchemaBuilder graphQLSchemaBuilder(final EntityManager entityManag @Autowired private GraphQLExecutor executor; + @Autowired + private GraphQLSchemaBuilder schemaBuilder; + @Test public void contextLoads() { Assert.isAssignable(GraphQLExecutor.class, executor.getClass()); @@ -117,4 +125,33 @@ public void testIgnoreFields() { ); } + @Test + public void shouldInheritMethodDescriptionFromBaseClass() { + //when + GraphQLSchema schema = schemaBuilder.build(); + + //then + Optional field = getFieldForType("parentTransientGetter", + "CalculatedEntity", + schema); + then(field) + .isPresent().get() + .extracting("description") + .isNotNull() + .containsExactly("getParentTransientGetter"); + } + + private Optional getFieldForType(String fieldName, + String type, + GraphQLSchema schema) { + return schema.getQueryType() + .getFieldDefinition(type) + .getType() + .getChildren() + .stream() + .map(GraphQLFieldDefinition.class::cast) + .filter(graphQLFieldDefinition -> graphQLFieldDefinition.getName().equals(fieldName)) + .findFirst(); + } + }