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..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 @@ -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.AttributePropertyDescriptor; 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(AttributePropertyDescriptor::isTransient) + .filter(AttributePropertyDescriptor::isNotIgnored) .map(this::getJavaFieldDefinition) .collect(Collectors.toList()); } @SuppressWarnings( { "rawtypes" } ) - private GraphQLFieldDefinition getJavaFieldDefinition(PropertyDescriptor propertyDescriptor) { + private GraphQLFieldDefinition getJavaFieldDefinition(AttributePropertyDescriptor 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(AttributePropertyDescriptor::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 2921c5058..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 @@ -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; @@ -22,50 +23,85 @@ 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.AttributePropertyDescriptor; 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); } + /** + * 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); + return introspect(entity).getPropertyDescriptor(propertyName) + .map(AttributePropertyDescriptor::isTransient) + .orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName))); } - public static boolean isIgnored(Class entity, String propertyName) { - return isAnnotationPresent(entity, propertyName, GraphQLIgnore.class) - .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); + } - 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 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(annotation)); - } - - private static Optional isModifierPresent(Class entity, String propertyName, Function function){ - return introspect(entity).getField(propertyName) - .map(it -> function.apply(it.getModifiers())); + .map(AttributePropertyDescriptor::isIgnored) + .orElseThrow(() -> new RuntimeException(new NoSuchFieldException(propertyName))); } - public static class CachedIntrospectionResult { + /** + * 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 EntityIntrospectionResult { - private final Map map; + 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) { @@ -73,26 +109,33 @@ public CachedIntrospectionResult(Class entity) { } this.entity = entity; - this.map = Stream.of(beanInfo.getPropertyDescriptors()) - .map(CachedPropertyDescriptor::new) - .collect(Collectors.toMap(CachedPropertyDescriptor::getName, it -> it)); + this.descriptors = Stream.of(beanInfo.getPropertyDescriptors()) + .map(AttributePropertyDescriptor::new) + .collect(Collectors.toMap(AttributePropertyDescriptor::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())) - .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() { - return map.values(); + + public Collection getPropertyDescriptors() { + return descriptors.values(); } - public Optional getPropertyDescriptor(String fieldName) { - return Optional.ofNullable(map.getOrDefault(fieldName, null)); + public Collection getFields() { + return fields.values(); + } + + 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 map.containsKey(fieldName); + return descriptors.containsKey(fieldName); } public Optional getField(String fieldName) { @@ -106,11 +149,28 @@ 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 boolean hasSchemaDescription() { + return getSchemaDescription().isPresent(); + } + + public Stream> getClasses() { + return iterate(entity, k -> Optional.ofNullable(k.getSuperclass())); + } + + public class AttributePropertyDescriptor { - public class CachedPropertyDescriptor { private final PropertyDescriptor delegate; - public CachedPropertyDescriptor(PropertyDescriptor delegate) { + public AttributePropertyDescriptor(PropertyDescriptor delegate) { this.delegate = delegate; } @@ -125,22 +185,124 @@ public Class getPropertyType() { public String getName() { return delegate.getName(); } + + public Optional getField() { + return Optional.ofNullable(fields.get(getName())); + } + + public boolean hasField() { + return getField().isPresent(); + } + + public Optional getReadMethod() { + return Optional.ofNullable(delegate.getReadMethod()); + } + + public Optional getAnnotation(Class annotationClass) { + return getReadMethod().map(m -> m.getAnnotation(annotationClass)) + .map(Optional::of) // Java 8 support + .orElseGet(() -> getField().map(f -> f.getAnnotation(annotationClass))); + } + + public Optional getSchemaDescription() { + return getAnnotation(GraphQLDescription.class).map(GraphQLDescription::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); + } 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); + } + + @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; + AttributePropertyDescriptor other = (AttributePropertyDescriptor) 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 + "]"; + } + } /** @@ -150,7 +312,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/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(); + } + } 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..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 @@ -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.AttributePropertyDescriptor; 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,80 @@ 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(AttributePropertyDescriptor::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"); + assertThat(result.hasSchemaDescription()).isTrue(); + } + } 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; }