com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of graphql-jpa-query-schema Show documentation
Show all versions of graphql-jpa-query-schema Show documentation
Provides GraphQL JPA Query Schema Generation and Execution Support
The newest version!
package com.introproventures.graphql.jpa.query.schema.impl;
import static java.util.Locale.ENGLISH;
import com.introproventures.graphql.jpa.query.annotation.GraphQLDefaultOrderBy;
import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription;
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore;
import com.introproventures.graphql.jpa.query.introspection.ClassDescriptor;
import com.introproventures.graphql.jpa.query.introspection.ClassIntrospector;
import com.introproventures.graphql.jpa.query.introspection.FieldDescriptor;
import com.introproventures.graphql.jpa.query.introspection.MethodDescriptor;
import com.introproventures.graphql.jpa.query.introspection.PropertyDescriptor;
import jakarta.persistence.OrderBy;
import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.ManagedType;
import java.lang.annotation.Annotation;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.Optional;
import java.util.Spliterator;
import java.util.Spliterators;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class EntityIntrospector {
private static final Logger LOGGER = LoggerFactory.getLogger(EntityIntrospector.class);
private static final Map, EntityIntrospectionResult> map = new LinkedHashMap<>();
private static ClassIntrospector introspector = ClassIntrospector
.builder()
.withScanAccessible(false)
.withEnhancedProperties(true)
.withIncludeFieldsAsProperties(false)
.withScanStatics(false)
.build();
/**
* Get existing EntityIntrospectionResult for Java type
*
* @param entity Java type of the entity
* @return EntityIntrospectionResult result
* @throws NoSuchElementException if not found
*/
public static EntityIntrospectionResult resultOf(Class> entity) {
return Optional.ofNullable(map.get(entity)).orElseThrow(() -> new NoSuchElementException(entity.getName()));
}
/**
* Introspect entity type represented by ManagedType instance
*
* @param entityType ManagedType representing persistent entity
* @return EntityIntrospectionResult result
*/
public static EntityIntrospectionResult introspect(ManagedType> entityType) {
return map.computeIfAbsent(entityType.getJavaType(), cls -> new EntityIntrospectionResult(entityType));
}
public static class EntityIntrospectionResult {
private final Map descriptors;
private final Class> entity;
private final ClassDescriptor classDescriptor;
private final ManagedType> managedType;
private final Map> attributes;
public EntityIntrospectionResult(ManagedType> managedType) {
this.managedType = managedType;
this.attributes =
managedType.getAttributes().stream().collect(Collectors.toMap(Attribute::getName, Function.identity()));
this.entity = managedType.getJavaType();
this.classDescriptor = introspector.introspect(entity);
this.descriptors =
Stream
.of(classDescriptor.getAllPropertyDescriptors())
.filter(it -> !"class".equals(it.getName()))
.map(AttributePropertyDescriptor::new)
.collect(Collectors.toMap(AttributePropertyDescriptor::getName, Function.identity()));
}
public Collection getTransientPropertyDescriptors() {
return descriptors
.values()
.stream()
.filter(AttributePropertyDescriptor::isTransient)
.collect(Collectors.toList());
}
public Collection getPersistentPropertyDescriptors() {
return descriptors
.values()
.stream()
.filter(AttributePropertyDescriptor::isPersistent)
.collect(Collectors.toList());
}
public Collection getIgnoredPropertyDescriptors() {
return descriptors
.values()
.stream()
.filter(AttributePropertyDescriptor::isIgnored)
.collect(Collectors.toList());
}
public Map> getAttributes() {
return attributes;
}
/**
* Test if entity property is annotated with GraphQLIgnore
*
* @param propertyName the name of the property
* @return true if property has GraphQLIgnore annotation
* @throws NoSuchElementException if property does not exists
*/
public Boolean isIgnored(String propertyName) {
return getPropertyDescriptor(propertyName)
.map(AttributePropertyDescriptor::isIgnored)
.orElseThrow(() -> noSuchElementException(entity, propertyName));
}
/**
* Test if entity property is not ignored
*
* @param propertyName the name of the property
* @return true if property has no GraphQLIgnore annotation
* @throws NoSuchElementException if property does not exists
*/
public Boolean isNotIgnored(String propertyName) {
return getPropertyDescriptor(propertyName)
.map(AttributePropertyDescriptor::isNotIgnored)
.orElseThrow(() -> noSuchElementException(entity, propertyName));
}
public Collection getPropertyDescriptors() {
return descriptors.values();
}
public Optional getPropertyDescriptor(String fieldName) {
return Optional.ofNullable(descriptors.get(fieldName));
}
public Optional getPropertyDescriptor(Attribute, ?> attribute) {
return getPropertyDescriptor(attribute.getName());
}
public boolean hasPropertyDescriptor(String fieldName) {
return descriptors.containsKey(fieldName);
}
/**
* Test if Java bean property is transient according to JPA specification
*
* @param propertyName the name of the property
* @return true if property has Transient annotation or transient field modifier
* @throws NoSuchElementException if property does not exists
*/
public Boolean isTransient(String propertyName) {
return getPropertyDescriptor(propertyName)
.map(AttributePropertyDescriptor::isTransient)
.orElseThrow(() -> noSuchElementException(entity, propertyName));
}
/**
* Test if Java bean property is persistent according to JPA specification
*
* @param propertyName the name of the property
* @return true if property is persitent
* @throws NoSuchElementException if property does not exists
*/
public Boolean isPersistent(String propertyName) {
return !isTransient(propertyName);
}
public Class> getEntity() {
return entity;
}
public ManagedType> getManagedType() {
return managedType;
}
public ClassDescriptor getClassDescriptor() {
return classDescriptor;
}
public Optional getSchemaDescription() {
return getClasses()
.filter(cls -> !Object.class.equals(cls))
.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 Optional getSchemaDescription(String propertyName) {
return getPropertyDescriptor(propertyName).flatMap(AttributePropertyDescriptor::getSchemaDescription);
}
public Stream> getClasses() {
return iterate(entity, k -> Optional.ofNullable(k.getSuperclass()));
}
public class AttributePropertyDescriptor {
private final PropertyDescriptor delegate;
private final Optional> attribute;
private final Optional field;
private final Optional readMethod;
public AttributePropertyDescriptor(PropertyDescriptor delegate) {
this.delegate = delegate;
String name = delegate.getName();
this.readMethod =
Optional
.ofNullable(delegate.getReadMethodDescriptor())
.map(MethodDescriptor::getMethod)
.filter(m -> !Modifier.isPrivate(m.getModifiers()));
this.attribute = Optional.ofNullable(attributes.getOrDefault(name, attributes.get(capitalize(name))));
this.field =
attribute
.map(Attribute::getJavaMember)
.filter(Field.class::isInstance)
.map(Field.class::cast)
.map(Optional::of)
.orElseGet(() ->
Optional.ofNullable(delegate.getFieldDescriptor()).map(FieldDescriptor::getField)
);
}
public ManagedType> getManagedType() {
return managedType;
}
public PropertyDescriptor getDelegate() {
return delegate;
}
public Class> getPropertyType() {
return delegate.getType();
}
public String getName() {
return attribute.map(Attribute::getName).orElseGet(() -> delegate.getName());
}
public Optional getReadMethod() {
return readMethod;
}
public Optional getAnnotation(Class annotationClass) {
return getReadMethod()
.map(m -> m.getAnnotation(annotationClass))
.map(Optional::of)
.orElseGet(() -> getField().map(f -> f.getAnnotation(annotationClass)));
}
public Optional> getAttribute() {
return attribute;
}
public Optional getField() {
return field;
}
public Optional getSchemaDescription() {
return getAnnotation(GraphQLDescription.class).map(GraphQLDescription::value);
}
public boolean hasSchemaDescription() {
return getSchemaDescription().isPresent();
}
public boolean hasDefaultOrderBy() {
return getDefaultOrderBy().isPresent();
}
public Optional getDefaultOrderBy() {
return getAnnotation(GraphQLDefaultOrderBy.class);
}
public boolean hasOrderBy() {
return getAnnotation(OrderBy.class).isPresent();
}
public Optional getOrderBy() {
return getAnnotation(OrderBy.class);
}
public boolean isTransient() {
return !attribute.isPresent();
}
public boolean isPersistent() {
return attribute.isPresent();
}
public boolean isIgnored() {
return isAnnotationPresent(GraphQLIgnore.class);
}
public boolean isNotIgnored() {
return !isIgnored();
}
public boolean hasReadMethod() {
return getReadMethod().isPresent();
}
public boolean isAnnotationPresent(Class extends Annotation> annotation) {
return getAnnotation(annotation).isPresent();
}
@Override
public String toString() {
return "AttributePropertyDescriptor [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(classDescriptor, 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(classDescriptor, other.classDescriptor) && Objects.equals(entity, other.entity);
}
@Override
public String toString() {
return "EntityIntrospectionResult [beanInfo=" + classDescriptor + "]";
}
}
/**
* The following method is borrowed from Streams.iterate,
* however Streams.iterate is designed to create infinite streams.
*
* This version has been modified to end when Optional.empty()
* is returned from the fetchNextFunction.
* @param the type of stream elements
* @param seed the initial element
* @param f a function to be applied to the previous element to produce
* a new element
* @return a new sequential {@code Stream}
*
*/
public static Stream iterate(T seed, Function> f) {
Objects.requireNonNull(f);
Iterator iterator = new Iterator() {
private Optional t = Optional.ofNullable(seed);
@Override
public boolean hasNext() {
return t.isPresent();
}
@Override
public T next() {
T v = t.get();
t = f.apply(v);
return v;
}
};
return StreamSupport.stream(
Spliterators.spliteratorUnknownSize(iterator, Spliterator.ORDERED | Spliterator.IMMUTABLE),
false
);
}
private static NoSuchElementException noSuchElementException(Class> containerClass, String propertyName) {
return new NoSuchElementException(
String.format(
Locale.ROOT,
"Could not locate field name [%s] on class [%s]",
propertyName,
containerClass.getName()
)
);
}
/**
* Returns a String which capitalizes the first letter of the string.
*/
public static String capitalize(String name) {
if (name == null || name.length() == 0) {
return name;
}
return name.substring(0, 1).toUpperCase(ENGLISH) + name.substring(1);
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy