Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder Maven / Gradle / Ivy
Go to download
Provides GraphQL JPA Query Schema Generation and Execution Support
/*
* Copyright 2017 IntroPro Ventures Inc. and/or its affiliates.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.introproventures.graphql.jpa.query.schema.impl;
import static com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.capitalize;
import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.getAliasOrName;
import static graphql.Scalars.GraphQLBoolean;
import static graphql.Scalars.GraphQLInt;
import static graphql.schema.GraphQLArgument.newArgument;
import static graphql.schema.GraphQLEnumType.newEnum;
import static graphql.schema.GraphQLEnumValueDefinition.newEnumValueDefinition;
import static graphql.schema.GraphQLFieldDefinition.newFieldDefinition;
import static graphql.schema.GraphQLInputObjectField.newInputObjectField;
import static graphql.schema.GraphQLInputObjectType.newInputObject;
import static graphql.schema.GraphQLObjectType.newObject;
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.RestrictedKeysProvider;
import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.EntityIntrospectionResult.AttributePropertyDescriptor;
import com.introproventures.graphql.jpa.query.schema.impl.PredicateFilter.Criteria;
import com.introproventures.graphql.jpa.query.schema.relay.GraphQLJpaRelayDataFetcher;
import graphql.Assert;
import graphql.Scalars;
import graphql.relay.Relay;
import graphql.schema.Coercing;
import graphql.schema.DataFetcher;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLEnumType;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLInputObjectField;
import graphql.schema.GraphQLInputObjectType;
import graphql.schema.GraphQLInputObjectType.Builder;
import graphql.schema.GraphQLInputType;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLType;
import graphql.schema.GraphQLTypeReference;
import graphql.schema.PropertyDataFetcher;
import jakarta.persistence.Convert;
import jakarta.persistence.EntityManager;
import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.EmbeddableType;
import jakarta.persistence.metamodel.EntityType;
import jakarta.persistence.metamodel.ManagedType;
import jakarta.persistence.metamodel.PluralAttribute;
import jakarta.persistence.metamodel.SingularAttribute;
import jakarta.persistence.metamodel.Type;
import java.beans.Introspector;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Field;
import java.lang.reflect.Member;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.dataloader.MappedBatchLoaderWithContext;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* JPA specific schema builder implementation of {code #GraphQLSchemaBuilder} interface
*
* @author Igor Dianov
*
*/
public class GraphQLJpaSchemaBuilder implements GraphQLSchemaBuilder {
private static final Logger log = LoggerFactory.getLogger(GraphQLJpaSchemaBuilder.class);
private static final String NODE = "node";
public static final String PAGE_PARAM_NAME = "page";
public static final String PAGE_TOTAL_PARAM_NAME = "total";
public static final String PAGE_PAGES_PARAM_NAME = "pages";
public static final String PAGE_START_PARAM_NAME = "start";
public static final String PAGE_LIMIT_PARAM_NAME = "limit";
public static final String QUERY_SELECT_PARAM_NAME = "select";
public static final String QUERY_WHERE_PARAM_NAME = "where";
public static final String QUERY_LOGICAL_PARAM_NAME = "logical";
public static final String SELECT_DISTINCT_PARAM_NAME = "distinct";
protected NamingStrategy namingStrategy = new NamingStrategy() {};
public static final String ORDER_BY_PARAM_NAME = "orderBy";
private final Map, GraphQLOutputType> classCache = new HashMap<>();
private final Map, GraphQLObjectType> entityCache = new HashMap<>();
private final Map> entityTypeMap = new ConcurrentHashMap<>();
private final Map> embeddableTypeMap = new ConcurrentHashMap<>();
private final Map, GraphQLInputObjectType> inputObjectCache = new HashMap<>();
private final Map, GraphQLInputObjectType> subqueryInputObjectCache = new HashMap<>();
private final Map, GraphQLObjectType> embeddableOutputCache = new HashMap<>();
private final Map, GraphQLInputObjectType> embeddableInputCache = new HashMap<>();
private Function queryByIdFieldNameCustomizer = Function.identity();
private Function queryAllFieldNameCustomizer = Function.identity();
private Function queryResultTypeNameCustomizer = Function.identity();
private Function queryTypeNameCustomizer = it -> it.concat("Query");
private Function queryWhereArgumentTypeNameCustomizer = name -> name.concat("CriteriaExpression");
private Function queryEmbeddableTypeNameCustomizer = it -> it.concat("EmbeddableType");
private Function subqueryArgumentTypeNameCustomizer = it -> it.concat("SubqueryCriteriaExpression");
private Function queryWhereInputTypeNameCustomizer = it -> it.concat("RelationCriteriaExpression");
private final Function singularize = word -> namingStrategy.singularize(word);
private final Function pluralize = word -> namingStrategy.pluralize(word);
private final EntityManager entityManager;
private String name = "GraphQLJPA";
private String description = "GraphQL Schema for all entities in this JPA application";
private boolean isUseDistinctParameter = false;
private boolean isDefaultDistinct = true;
private boolean toManyDefaultOptional = true; // the many end is a collection, and it is always optional by default (empty collection)
private boolean enableSubscription = false; // experimental
private boolean enableRelay = false; // experimental
private boolean enableAggregate = false; // experimental
private int defaultMaxResults = 100;
private int defaultFetchSize = 100;
private int defaultPageLimitSize = 100;
private boolean enableDefaultMaxResults = true;
private boolean enableResultStream = false;
private boolean graphQLIDType = false;
private RestrictedKeysProvider restrictedKeysProvider = entityDescriptor -> Optional.of(Collections.emptyList());
private final Map, GraphQLScalarType> scalars = new LinkedHashMap<>();
private final Relay relay = new Relay();
private final List entityPaths = new ArrayList<>();
private final Supplier batchLoadersRegistry = BatchLoaderRegistry::getInstance;
private final Function> entityObjectTypeResolver = entityTypeMap::get;
private final Function> embeddableObjectTypeResolver = embeddableTypeMap::get;
private final GraphQLObjectTypeMetadata graphQLObjectTypeMetadata = new GraphQLObjectTypeMetadataImpl();
public GraphQLJpaSchemaBuilder(EntityManager entityManager) {
this.entityManager = entityManager;
}
public EntityManager getEntityManager() {
return entityManager;
}
/* (non-Javadoc)
* @see org.activiti.services.query.qraphql.jpa.GraphQLSchemaBuilder#getGraphQLSchema()
*/
@Override
public GraphQLSchema build() {
scalars.forEach(JavaScalars::register);
GraphQLSchema.Builder schema = GraphQLSchema.newSchema().query(getQueryType());
if (enableSubscription) {
schema.subscription(getSubscriptionType());
}
if (enableRelay) {
schema.additionalType(Relay.pageInfoType);
}
entityCache.forEach((entity, type) -> entityTypeMap.put(type.getName(), entity));
entityManager
.getMetamodel()
.getEmbeddables()
.stream()
.filter(it -> embeddableOutputCache.containsKey(it.getJavaType()))
.forEach(it -> embeddableTypeMap.put(embeddableOutputCache.get(it.getJavaType()).getName(), it));
return schema.build();
}
public GraphQLJpaSchemaBuilder scalar(Class> javaType, GraphQLScalarType scalarType) {
scalars.put(javaType, scalarType);
return this;
}
private GraphQLObjectType getQueryType() {
GraphQLObjectType.Builder queryType = newObject()
.name(queryTypeNameCustomizer.apply(this.name))
.description(this.description);
queryType.fields(
entityManager
.getMetamodel()
.getEntities()
.stream()
.filter(this::isNotIgnored)
.map(this::getQueryFieldByIdDefinition)
.toList()
);
queryType.fields(
entityManager
.getMetamodel()
.getEntities()
.stream()
.filter(this::isNotIgnored)
.map(this::getQueryFieldSelectDefinition)
.toList()
);
return queryType.build();
}
private GraphQLObjectType getSubscriptionType() {
GraphQLObjectType.Builder queryType = newObject()
.name(this.name + "Subscription")
.description(this.description);
queryType.fields(
entityManager
.getMetamodel()
.getEntities()
.stream()
.filter(this::isNotIgnored)
.map(this::getQueryFieldStreamDefinition)
.collect(Collectors.toList())
);
return queryType.build();
}
private GraphQLFieldDefinition getQueryFieldByIdDefinition(EntityType> entityType) {
GraphQLObjectType entityObjectType = getEntityObjectType(entityType);
GraphQLJpaQueryFactory queryFactory = GraphQLJpaQueryFactory
.builder()
.withEntityManager(entityManager)
.withEntityType(entityType)
.withGraphQLObjectTypeMetadata(graphQLObjectTypeMetadata)
.withEntityObjectType(entityObjectType)
.withSelectNodeName(entityObjectType.getName())
.withToManyDefaultOptional(toManyDefaultOptional)
.withRestrictedKeysProvider(restrictedKeysProvider)
.withResultStream(enableResultStream)
.build();
DataFetcher dataFetcher = GraphQLJpaSimpleDataFetcher.builder().withQueryFactory(queryFactory).build();
String fieldName = singularize.andThen(queryByIdFieldNameCustomizer).apply(entityType.getName());
return newFieldDefinition()
.name(enableRelay ? Introspector.decapitalize(fieldName) : fieldName)
.description(getSchemaDescription(entityType))
.type(entityObjectType)
.dataFetcher(dataFetcher)
.arguments(
entityType
.getAttributes()
.stream()
.filter(this::isValidInput)
.filter(this::isNotIgnored)
.filter(this::isIdentity)
.map(this::getArgument)
.collect(Collectors.toList())
)
.build();
}
private GraphQLObjectType getConnectionType(GraphQLObjectType nodeType) {
GraphQLObjectType edgeType = relay.edgeType(nodeType.getName(), nodeType, null, Collections.emptyList());
return relay.connectionType(nodeType.getName(), edgeType, Collections.emptyList());
}
private GraphQLFieldDefinition getQueryFieldSelectDefinition(EntityType> entityType) {
final GraphQLObjectType entityObjectType = getEntityObjectType(entityType);
final GraphQLObjectType outputType = enableRelay
? getConnectionType(entityObjectType)
: getSelectType(entityType);
final DataFetcher extends Object> dataFetcher;
GraphQLJpaQueryFactory queryFactory = GraphQLJpaQueryFactory
.builder()
.withEntityManager(entityManager)
.withEntityType(entityType)
.withGraphQLObjectTypeMetadata(graphQLObjectTypeMetadata)
.withEntityObjectType(entityObjectType)
.withSelectNodeName(enableRelay ? NODE : QUERY_SELECT_PARAM_NAME)
.withToManyDefaultOptional(toManyDefaultOptional)
.withDefaultDistinct(isDefaultDistinct)
.withDefaultFetchSize(defaultFetchSize)
.withRestrictedKeysProvider(restrictedKeysProvider)
.withResultStream(enableResultStream)
.build();
if (enableRelay) {
dataFetcher =
GraphQLJpaRelayDataFetcher
.builder()
.withQueryFactory(queryFactory)
.withDefaultMaxResults(defaultMaxResults)
.withEnableDefaultMaxResults(enableDefaultMaxResults)
.withDefaultFirstSize(defaultPageLimitSize)
.build();
} else {
dataFetcher =
GraphQLJpaQueryDataFetcher
.builder()
.withQueryFactory(queryFactory)
.withDefaultMaxResults(defaultMaxResults)
.withEnableDefaultMaxResults(enableDefaultMaxResults)
.withDefaultPageLimitSize(defaultPageLimitSize)
.build();
}
String fieldName = pluralize.andThen(queryAllFieldNameCustomizer).apply(entityType.getName());
GraphQLFieldDefinition.Builder fieldDefinition = newFieldDefinition()
.name(enableRelay ? Introspector.decapitalize(fieldName) : fieldName)
.description(
"Query request wrapper for " +
entityType.getName() +
" to request paginated data. " +
"Use query request arguments to specify query filter criterias. " +
"Use the '" +
QUERY_SELECT_PARAM_NAME +
"' field to request actual fields. " +
"Use the '" +
ORDER_BY_PARAM_NAME +
"' on a field to specify sort order for each field. "
)
.type(outputType)
.dataFetcher(dataFetcher)
.argument(getWhereArgument(entityType))
.arguments(
enableRelay
? relay.getForwardPaginationConnectionFieldArguments()
: Collections.singletonList(paginationArgument)
);
if (isUseDistinctParameter) {
fieldDefinition.argument(distinctArgument(entityType));
}
return fieldDefinition.build();
}
private String resolveSelectTypeName(EntityType> entityType) {
return pluralize.andThen(queryResultTypeNameCustomizer).apply(entityType.getName());
}
private GraphQLObjectType getSelectType(EntityType> entityType) {
final GraphQLObjectType selectObjectType = getEntityObjectType(entityType);
final var selectTypeName = resolveSelectTypeName(entityType);
var selectPagedResultType = newObject()
.name(selectTypeName)
.description(
"Query response wrapper object for " +
entityType.getName() +
". When page is requested, this object will be returned with query metadata."
)
.field(
newFieldDefinition()
.name(GraphQLJpaSchemaBuilder.PAGE_PAGES_PARAM_NAME)
.description("Total number of pages calculated on the database for this page size.")
.type(JavaScalars.of(Long.class))
.build()
)
.field(
newFieldDefinition()
.name(GraphQLJpaSchemaBuilder.PAGE_TOTAL_PARAM_NAME)
.description("Total number of records in the database for this query.")
.type(JavaScalars.of(Long.class))
.build()
)
.field(
newFieldDefinition()
.name(GraphQLJpaSchemaBuilder.QUERY_SELECT_PARAM_NAME)
.description("The queried records container")
.type(new GraphQLList(selectObjectType))
.build()
);
if (enableAggregate) {
selectPagedResultType.field(getAggregateFieldDefinition(entityType));
}
return selectPagedResultType.build();
}
private GraphQLFieldDefinition getAggregateFieldDefinition(EntityType> entityType) {
final var selectTypeName = resolveSelectTypeName(entityType);
final var aggregateObjectTypeName = selectTypeName.concat("Aggregate");
var aggregateObjectType = newObject()
.name(aggregateObjectTypeName)
.description("%s entity aggregate object type".formatted(selectTypeName));
DataFetcher aggregateDataFetcher = environment -> {
Map source = environment.getSource();
return source.get(getAliasOrName(environment.getField()));
};
var countFieldDefinition = newFieldDefinition()
.name("count")
.description("Count the number of records in the database for the %s aggregate".formatted(selectTypeName))
.dataFetcher(aggregateDataFetcher)
.type(GraphQLInt);
var associationEnumValueDefinitions = entityType
.getAttributes()
.stream()
.filter(it -> EntityIntrospector.introspect(entityType).isNotIgnored(it.getName()))
.filter(Attribute::isAssociation)
.map(Attribute::getName)
.map(name ->
newEnumValueDefinition()
.name(name)
.description("%s entity associated %s child entity".formatted(selectTypeName, name))
.build()
)
.toList();
var fieldsEnumValueDefinitions = entityType
.getAttributes()
.stream()
.filter(it -> EntityIntrospector.introspect(entityType).isNotIgnored(it.getName()))
.filter(it -> isBasic(it) || isEmbeddable(it))
.map(Attribute::getName)
.map(name ->
newEnumValueDefinition()
.name(name)
.description("%s entity %s attribute".formatted(selectTypeName, name))
.build()
)
.toList();
if (entityType.getAttributes().stream().anyMatch(Attribute::isAssociation)) {
countFieldDefinition.argument(
newArgument()
.name("of")
.description(
"Count the number of associated records in the database for the %s aggregate".formatted(
selectTypeName
)
)
.type(
newEnum()
.name(aggregateObjectTypeName.concat("CountOfAssociationsEnum"))
.description("%s entity associated entity name values".formatted(selectTypeName))
.values(associationEnumValueDefinitions)
.build()
)
);
}
var groupFieldDefinition = newFieldDefinition()
.name("group")
.description("Group by %s entity query field aggregated by multiple fields".formatted(selectTypeName))
.dataFetcher(aggregateDataFetcher)
.type(
new GraphQLList(
newObject()
.name(aggregateObjectTypeName.concat("GroupBy"))
.description("%s entity group by object type".formatted(selectTypeName))
.field(
newFieldDefinition()
.name("by")
.description(
"Group by %s field container used to query aggregate data by one or more fields".formatted(
selectTypeName
)
)
.dataFetcher(aggregateDataFetcher)
.argument(
newArgument()
.name("field")
.description(
"Group by field argument used to specify %s entity field name".formatted(
selectTypeName
)
)
.type(
newEnum()
.name(aggregateObjectTypeName.concat("GroupByFieldsEnum"))
.description("%s entity field name values".formatted(selectTypeName))
.values(fieldsEnumValueDefinitions)
.build()
)
)
.type(JavaScalars.GraphQLObjectScalar)
)
.field(countFieldDefinition)
.build()
)
);
var aggregateByObjectType = newObject()
.name(selectTypeName.concat("AggregateBy"))
.description("%s aggregate query type groups by nested associations".formatted(selectTypeName));
entityType
.getAttributes()
.stream()
.filter(it -> EntityIntrospector.introspect(entityType).isNotIgnored(it.getName()))
.filter(Attribute::isAssociation)
.forEach(association -> {
var javaType = isPlural(association)
? PluralAttribute.class.cast(association).getBindableJavaType()
: association.getJavaType();
var attributes = EntityIntrospector.resultOf(javaType).getAttributes();
var fields = attributes
.values()
.stream()
.filter(it -> isBasic(it) || isEmbeddable(it))
.map(Attribute::getName)
.map(name -> newEnumValueDefinition().name(name).build())
.toList();
if (!fields.isEmpty()) {
aggregateByObjectType.field(
newFieldDefinition()
.name(association.getName())
.description(
"Aggregate by %s query field definition for the associated %s entity".formatted(
selectTypeName,
association.getName()
)
)
.dataFetcher(aggregateDataFetcher)
.type(
new GraphQLList(
newObject()
.name(
aggregateObjectTypeName
.concat(capitalize(association.getName()))
.concat("GroupByNestedAssociation")
)
.description(
"Aggregate %s query object type for the associated %s entity".formatted(
selectTypeName,
association.getName()
)
)
.field(
newFieldDefinition()
.name("by")
.dataFetcher(aggregateDataFetcher)
.description(
"Group by %s attribute field used to query aggregate data by one or more fields".formatted(
association.getName()
)
)
.argument(
newArgument()
.name("field")
.description(
"Group by field argument used to specify associated %s entity field name".formatted(
association.getName()
)
)
.type(
newEnum()
.name(
aggregateObjectTypeName
.concat(capitalize(association.getName()))
.concat("GroupByNestedAssociationEnum")
)
.values(fields)
.build()
)
)
.type(JavaScalars.GraphQLObjectScalar)
)
.field(
newFieldDefinition()
.name("count")
.description(
"Count the number of records in the database for the %s associated nested aggregate".formatted(
association.getName()
)
)
.type(GraphQLInt)
)
.build()
)
)
);
}
});
aggregateObjectType.field(countFieldDefinition).field(groupFieldDefinition);
if (!aggregateByObjectType.build().getFieldDefinitions().isEmpty()) {
aggregateObjectType.field(
newFieldDefinition()
.name("by")
.description("Nested aggregate by query field for %s entity".formatted(selectTypeName))
.type(aggregateByObjectType)
);
}
var aggregateFieldDefinition = newFieldDefinition()
.name("aggregate")
.description("Aggregate data query field for %s entity".formatted(selectTypeName))
.type(aggregateObjectType);
return aggregateFieldDefinition.build();
}
private GraphQLFieldDefinition getQueryFieldStreamDefinition(EntityType> entityType) {
GraphQLObjectType entityObjectType = getEntityObjectType(entityType);
GraphQLJpaQueryFactory queryFactory = GraphQLJpaQueryFactory
.builder()
.withEntityManager(entityManager)
.withEntityType(entityType)
.withGraphQLObjectTypeMetadata(graphQLObjectTypeMetadata)
.withEntityObjectType(entityObjectType)
.withSelectNodeName(SELECT_DISTINCT_PARAM_NAME)
.withToManyDefaultOptional(toManyDefaultOptional)
.withDefaultDistinct(isDefaultDistinct)
.withRestrictedKeysProvider(restrictedKeysProvider)
.withResultStream(enableResultStream)
.build();
DataFetcher dataFetcher = GraphQLJpaStreamDataFetcher.builder().withQueryFactory(queryFactory).build();
var fieldName = pluralize.andThen(queryResultTypeNameCustomizer).apply(entityType.getName());
GraphQLFieldDefinition.Builder fieldDefinition = newFieldDefinition()
.name(fieldName)
.description(
"Query request wrapper for " +
entityType.getName() +
" to request paginated data. " +
"Use query request arguments to specify query filter criterias. " +
"Use the '" +
ORDER_BY_PARAM_NAME +
"' on a field to specify sort order for each field. "
)
.type(entityObjectType)
.dataFetcher(dataFetcher)
.argument(paginationArgument)
.argument(getWhereArgument(entityType));
if (isUseDistinctParameter) {
fieldDefinition.argument(distinctArgument(entityType));
}
return fieldDefinition.build();
}
private Map, GraphQLArgument> whereArgumentsMap = new HashMap<>();
private GraphQLArgument distinctArgument(EntityType> entityType) {
return GraphQLArgument
.newArgument()
.name(SELECT_DISTINCT_PARAM_NAME)
.description("Distinct logical specification")
.type(GraphQLBoolean)
.defaultValue(isDefaultDistinct)
.build();
}
private GraphQLArgument getWhereArgument(ManagedType> managedType) {
return whereArgumentsMap.computeIfAbsent(
managedType.getJavaType(),
javaType -> computeWhereArgument(managedType)
);
}
private GraphQLArgument computeWhereArgument(ManagedType> managedType) {
String type = resolveWhereArgumentTypeName(managedType);
GraphQLInputObjectType whereInputObject = GraphQLInputObjectType
.newInputObject()
.name(type)
.description("Where logical AND specification of the provided list of criteria expressions")
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.OR.name())
.description("Logical operation for expressions")
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.AND.name())
.description("Logical operation for expressions")
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.NOT.name())
.description("Logical operation for expression")
.type(new GraphQLTypeReference(type))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.EXISTS.name())
.description("Logical EXISTS subquery expression")
.type(new GraphQLList(getSubqueryInputType(managedType)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.NOT_EXISTS.name())
.description("Logical NOT EXISTS subquery expression")
.type(new GraphQLList(getSubqueryInputType(managedType)))
.build()
)
.fields(
managedType
.getAttributes()
.stream()
.filter(this::isValidInput)
.filter(this::isNotIgnored)
.filter(this::isNotIgnoredFilter)
.map(this::getWhereInputField)
.collect(Collectors.toList())
)
.fields(
managedType
.getAttributes()
.stream()
.filter(Attribute::isAssociation)
.filter(this::isNotIgnored)
.filter(this::isNotIgnoredFilter)
.map(this::getInputObjectField)
.collect(Collectors.toList())
)
// TODO support embedded element collections
// .fields(managedType.getAttributes().stream()
// .filter(Attribute::isCollection)
// .filter(this::isNotIgnored)
// .filter(this::isNotIgnoredFilter)
// .map(this::getInputObjectField)
// .collect(Collectors.toList())
// )
.build();
return GraphQLArgument
.newArgument()
.name(QUERY_WHERE_PARAM_NAME)
.description("Where logical specification")
.type(whereInputObject)
.build();
}
private String resolveWhereArgumentTypeName(ManagedType> managedType) {
String typeName = resolveManagedTypeName(managedType);
return pluralize.andThen(queryWhereArgumentTypeNameCustomizer).apply(typeName);
}
private String resolveSubqueryArgumentTypeName(ManagedType> managedType) {
String typeName = resolveManagedTypeName(managedType);
return pluralize.andThen(subqueryArgumentTypeNameCustomizer).apply(typeName);
}
private GraphQLInputObjectType getSubqueryInputType(ManagedType> managedType) {
return subqueryInputObjectCache.computeIfAbsent(managedType, this::computeSubqueryInputType);
}
private GraphQLInputObjectType computeSubqueryInputType(ManagedType> managedType) {
String type = resolveSubqueryArgumentTypeName(managedType);
Builder whereInputObject = GraphQLInputObjectType
.newInputObject()
.name(type)
.description("Where logical AND specification of the provided list of criteria expressions")
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.OR.name())
.description("Logical operation for expressions")
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.AND.name())
.description("Logical operation for expressions")
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.NOT.name())
.description("Logical operation for expression")
.type(new GraphQLTypeReference(type))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.EXISTS.name())
.description("Logical EXISTS subquery expression")
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.NOT_EXISTS.name())
.description("Logical NOT EXISTS subquery expression")
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.fields(
managedType
.getAttributes()
.stream()
.filter(Attribute::isAssociation)
.filter(this::isNotIgnored)
.filter(this::isNotIgnoredFilter)
.map(this::getWhereInputRelationField)
.collect(Collectors.toList())
);
return whereInputObject.build();
}
private String resolveManagedTypeName(ManagedType> managedType) {
String typeName = "";
if (managedType instanceof EmbeddableType) {
typeName = managedType.getJavaType().getSimpleName();
} else if (managedType instanceof EntityType> entityType) {
typeName = entityType.getName();
}
return typeName;
}
private GraphQLInputObjectType getWhereInputType(ManagedType> managedType) {
GraphQLInputObjectType type = inputObjectCache.get(managedType);
if (type == null) {
type = computeWhereInputType(managedType);
inputObjectCache.put(managedType, type);
return type;
}
return type;
}
private String resolveWhereInputTypeName(ManagedType> managedType) {
String typeName = resolveManagedTypeName(managedType);
return pluralize.andThen(queryWhereInputTypeNameCustomizer).apply(typeName);
}
private GraphQLInputObjectType computeWhereInputType(ManagedType> managedType) {
String type = resolveWhereInputTypeName(managedType);
Builder whereInputObject = GraphQLInputObjectType
.newInputObject()
.name(type)
.description("Where logical AND specification of the provided list of criteria expressions")
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.OR.name())
.description("Logical operation for expressions")
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.AND.name())
.description("Logical operation for expressions")
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.NOT.name())
.description("Logical operation for expression")
.type(new GraphQLTypeReference(type))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.EXISTS.name())
.description("Logical EXISTS subquery expression")
.type(new GraphQLList(getSubqueryInputType(managedType)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.NOT_EXISTS.name())
.description("Logical NOT EXISTS subquery expression")
.type(new GraphQLList(getSubqueryInputType(managedType)))
.build()
)
.fields(
managedType
.getAttributes()
.stream()
.filter(this::isValidInput)
.filter(this::isNotIgnored)
.filter(this::isNotIgnoredFilter)
.map(this::getWhereInputField)
.collect(Collectors.toList())
)
.fields(
managedType
.getAttributes()
.stream()
.filter(Attribute::isAssociation)
.filter(this::isNotIgnored)
.filter(this::isNotIgnoredFilter)
.map(this::getWhereInputRelationField)
.collect(Collectors.toList())
);
return whereInputObject.build();
}
private GraphQLInputObjectField getWhereInputRelationField(Attribute, ?> attribute) {
ManagedType> foreignType = getForeignType(attribute);
String type = resolveWhereInputTypeName(foreignType);
String description = getSchemaDescription(attribute);
return GraphQLInputObjectField
.newInputObjectField()
.name(attribute.getName())
.description(description)
.type(new GraphQLTypeReference(type))
.build();
}
private GraphQLInputObjectField getWhereInputField(Attribute, ?> attribute) {
GraphQLInputType type = getWhereAttributeType(attribute);
String description = getSchemaDescription(attribute);
if (type instanceof GraphQLInputType) {
return GraphQLInputObjectField
.newInputObjectField()
.name(attribute.getName())
.description(description)
.type(type)
.build();
}
throw new IllegalArgumentException(
"Attribute " + attribute.getName() + " cannot be mapped as an Input Argument"
);
}
private Map whereAttributesMap = new HashMap<>();
private GraphQLInputType getWhereAttributeType(Attribute, ?> attribute) {
String type =
namingStrategy.singularize(attribute.getName()) +
attribute.getDeclaringType().getJavaType().getSimpleName() +
"Criteria";
if (whereAttributesMap.containsKey(type)) return whereAttributesMap.get(type);
if (isEmbeddable(attribute)) {
EmbeddableType> embeddableType = (EmbeddableType>) ((SingularAttribute, ?>) attribute).getType();
return getWhereInputType(embeddableType);
}
GraphQLInputObjectType.Builder builder = GraphQLInputObjectType
.newInputObject()
.name(type)
.description(
"Criteria expression specification of " +
namingStrategy.singularize(attribute.getName()) +
" attribute in entity " +
attribute.getDeclaringType().getJavaType()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.OR.name())
.description("Logical OR criteria expression")
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.AND.name())
.description("Logical AND criteria expression")
.type(new GraphQLList(new GraphQLTypeReference(type)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Logical.NOT.name())
.description("Logical NOT criteria expression")
.type(new GraphQLTypeReference(type))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.EQ.name())
.description("Equals criteria")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.NE.name())
.description("Not Equals criteria")
.type(getAttributeInputType(attribute))
.build()
);
if (!attribute.getJavaType().isEnum()) {
if (!attribute.getJavaType().equals(String.class)) {
builder
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.LE.name())
.description("Less then or Equals criteria")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.GE.name())
.description("Greater or Equals criteria")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.GT.name())
.description("Greater Then criteria")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.LT.name())
.description("Less Then criteria")
.type(getAttributeInputType(attribute))
.build()
);
}
if (attribute.getJavaType().equals(String.class)) {
builder
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.LIKE.name())
.description("Like criteria, case sensitive")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.LIKE_.name())
.description("Like criteria, case insensitive")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.LOWER.name())
.description("Case insensitive match criteria")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.EQ_.name())
.description("Case equals case insensitive match criteria")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.NE_.name())
.description("Not equals case insensitive match criteria")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.CASE.name())
.description("Case sensitive match criteria")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.STARTS.name())
.description("Starts with criteria, case sensitive")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.STARTS_.name())
.description("Starts with criteria, case insensitive")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.ENDS.name())
.description("Ends with criteria, case sensitive")
.type(getAttributeInputType(attribute))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.ENDS_.name())
.description("Ends with criteria, case insensitive")
.type(getAttributeInputType(attribute))
.build()
);
} else if (
attribute.getJavaMember().getClass().isAssignableFrom(Field.class) &&
Field.class.cast(attribute.getJavaMember()).isAnnotationPresent(Convert.class)
) {
builder.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.LOCATE.name())
.description("Locate search criteria")
.type(getAttributeInputType(attribute))
.build()
);
}
}
builder
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.IS_NULL.name())
.description("Is Null criteria")
.type(GraphQLBoolean)
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.NOT_NULL.name())
.description("Is Not Null criteria")
.type(GraphQLBoolean)
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.IN.name())
.description("In criteria")
.type(new GraphQLList(getAttributeInputType(attribute)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.NIN.name())
.description("Not In criteria")
.type(new GraphQLList(getAttributeInputType(attribute)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.BETWEEN.name())
.description("Between criteria")
.type(new GraphQLList(getAttributeInputType(attribute)))
.build()
)
.field(
GraphQLInputObjectField
.newInputObjectField()
.name(Criteria.NOT_BETWEEN.name())
.description("Not Between criteria")
.type(new GraphQLList(getAttributeInputType(attribute)))
.build()
);
GraphQLInputType answer = builder.build();
whereAttributesMap.putIfAbsent(type, answer);
return answer;
}
private GraphQLArgument getArgument(Attribute, ?> attribute) {
GraphQLInputType type = getAttributeInputTypeForSearchByIdArg(attribute);
String description = getSchemaDescription(attribute);
return GraphQLArgument.newArgument().name(attribute.getName()).type(type).description(description).build();
}
private GraphQLType getEmbeddableType(EmbeddableType> embeddableType, boolean input, boolean searchByIdArg) {
GraphQLType graphQLType;
if (input) {
if (searchByIdArg) {
if (embeddableInputCache.containsKey(embeddableType.getJavaType())) {
return embeddableInputCache.get(embeddableType.getJavaType());
}
graphQLType =
GraphQLInputObjectType
.newInputObject()
.name(
namingStrategy.singularize(embeddableType.getJavaType().getSimpleName()) +
"InputEmbeddableIdType"
)
.description(getSchemaDescription(embeddableType))
.fields(
embeddableType
.getAttributes()
.stream()
.filter(this::isNotIgnored)
.map(this::getInputObjectField)
.collect(Collectors.toList())
)
.build();
embeddableInputCache.put(embeddableType.getJavaType(), (GraphQLInputObjectType) graphQLType);
return graphQLType;
}
graphQLType = getWhereInputType(embeddableType);
} else {
if (embeddableOutputCache.containsKey(embeddableType.getJavaType())) {
return embeddableOutputCache.get(embeddableType.getJavaType());
}
String embeddableTypeName = singularize
.andThen(queryEmbeddableTypeNameCustomizer)
.apply(embeddableType.getJavaType().getSimpleName());
graphQLType =
newObject()
.name(embeddableTypeName)
.description(getSchemaDescription(embeddableType))
.fields(
embeddableType
.getAttributes()
.stream()
.filter(this::isNotIgnored)
.map(this::getObjectField)
.collect(Collectors.toList())
)
.build();
embeddableOutputCache.putIfAbsent(embeddableType.getJavaType(), (GraphQLObjectType) graphQLType);
}
return graphQLType;
}
private GraphQLObjectType getEntityObjectType(EntityType> entityType) {
return entityCache.computeIfAbsent(entityType, this::computeEntityObjectType);
}
private String resolveEntityObjectTypeName(EntityType> entityType) {
var entityTypeName = resolveManagedTypeName(entityType);
return singularize.andThen(queryResultTypeNameCustomizer).apply(entityTypeName);
}
private GraphQLObjectType computeEntityObjectType(EntityType> entityType) {
var typeName = resolveEntityObjectTypeName(entityType);
return newObject()
.name(typeName)
.description(getSchemaDescription(entityType))
.fields(getEntityAttributesFields(entityType))
.fields(getTransientFields(entityType))
.build();
}
private List getEntityAttributesFields(EntityType> entityType) {
return entityType
.getAttributes()
.stream()
.filter(attribute -> EntityIntrospector.introspect(entityType).isNotIgnored(attribute.getName()))
.map(it -> getObjectField(it, entityType))
.collect(Collectors.toList());
}
private List getTransientFields(ManagedType> managedType) {
return EntityIntrospector
.introspect(managedType)
.getTransientPropertyDescriptors()
.stream()
.filter(AttributePropertyDescriptor::isNotIgnored)
.map(this::getJavaFieldDefinition)
.collect(Collectors.toList());
}
@SuppressWarnings({ "rawtypes" })
private GraphQLFieldDefinition getJavaFieldDefinition(AttributePropertyDescriptor propertyDescriptor) {
GraphQLOutputType type = getGraphQLTypeFromJavaType(propertyDescriptor.getPropertyType());
DataFetcher dataFetcher = PropertyDataFetcher.fetching(propertyDescriptor.getName());
String description = propertyDescriptor.getSchemaDescription().orElse(null);
return newFieldDefinition()
.name(propertyDescriptor.getName())
.description(description)
.type(type)
.dataFetcher(dataFetcher)
.build();
}
private GraphQLFieldDefinition getObjectField(Attribute, ?> attribute) {
return getObjectField(attribute, null);
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private GraphQLFieldDefinition getObjectField(Attribute attribute, EntityType baseEntity) {
GraphQLOutputType type = getAttributeOutputType(attribute);
List arguments = new ArrayList<>();
DataFetcher dataFetcher = PropertyDataFetcher.fetching(attribute.getName());
// Only add the orderBy argument for basic attribute types
if (isBasic(attribute) && isNotIgnoredOrder(attribute)) {
arguments.add(
GraphQLArgument
.newArgument()
.name(ORDER_BY_PARAM_NAME)
.description("Specifies field sort direction in the query results.")
.type(orderByDirectionEnum)
.defaultValue("ASC")
.build()
);
}
// Get the fields that can be queried on (i.e. Simple Types, no Sub-Objects)
if (isSingular(attribute)) {
ManagedType foreignType = getForeignType(attribute);
SingularAttribute, ?> singularAttribute = SingularAttribute.class.cast(attribute);
// TODO fix page count query
arguments.add(getWhereArgument(foreignType));
// to-one end could be optional
arguments.add(optionalArgument(singularAttribute.isOptional()));
GraphQLObjectType entityObjectType = newObject().name(resolveEntityObjectTypeName(baseEntity)).build();
GraphQLJpaQueryFactory graphQLJpaQueryFactory = GraphQLJpaQueryFactory
.builder()
.withEntityManager(entityManager)
.withEntityType(baseEntity)
.withGraphQLObjectTypeMetadata(graphQLObjectTypeMetadata)
.withEntityObjectType(entityObjectType)
.withSelectNodeName(baseEntity.getName())
.withDefaultDistinct(isDefaultDistinct)
.withRestrictedKeysProvider(restrictedKeysProvider)
.withResultStream(enableResultStream)
.build();
String dataLoaderKey = entityObjectType.getName() + "." + attribute.getName();
MappedBatchLoaderWithContext mappedBatchLoader = new GraphQLJpaToOneMappedBatchLoader(
graphQLJpaQueryFactory
);
batchLoadersRegistry.get().registerToOne(dataLoaderKey, mappedBatchLoader);
dataFetcher = new GraphQLJpaToOneDataFetcher(graphQLJpaQueryFactory, (SingularAttribute) attribute);
} // Get Sub-Objects fields queries via DataFetcher
else if (isPlural(attribute)) {
Assert.assertNotNull(
baseEntity,
() -> "For attribute " + attribute.getName() + " cannot find declaring type!"
);
EntityType elementType = (EntityType) ((PluralAttribute) attribute).getElementType();
arguments.add(getWhereArgument(elementType));
// make it configurable via builder api
arguments.add(optionalArgument(toManyDefaultOptional));
GraphQLObjectType entityObjectType = newObject().name(resolveEntityObjectTypeName(baseEntity)).build();
GraphQLJpaQueryFactory graphQLJpaQueryFactory = GraphQLJpaQueryFactory
.builder()
.withEntityManager(entityManager)
.withEntityType(baseEntity)
.withGraphQLObjectTypeMetadata(graphQLObjectTypeMetadata)
.withEntityObjectType(entityObjectType)
.withSelectNodeName(baseEntity.getName())
.withDefaultDistinct(isDefaultDistinct)
.withRestrictedKeysProvider(restrictedKeysProvider)
.withResultStream(enableResultStream)
.build();
String dataLoaderKey = entityObjectType.getName() + "." + attribute.getName();
MappedBatchLoaderWithContext> mappedBatchLoader = new GraphQLJpaToManyMappedBatchLoader(
graphQLJpaQueryFactory
);
batchLoadersRegistry.get().registerToMany(dataLoaderKey, mappedBatchLoader);
dataFetcher = new GraphQLJpaToManyDataFetcher(graphQLJpaQueryFactory, (PluralAttribute) attribute);
}
return newFieldDefinition()
.name(attribute.getName())
.description(getSchemaDescription(attribute))
.type(type)
.dataFetcher(dataFetcher)
.arguments(arguments)
.build();
}
private GraphQLArgument optionalArgument(Boolean defaultValue) {
return GraphQLArgument
.newArgument()
.name("optional")
.description("Optional association specification")
.type(GraphQLBoolean)
.defaultValue(defaultValue)
.build();
}
protected ManagedType> getForeignType(Attribute, ?> attribute) {
if (SingularAttribute.class.isInstance(attribute)) return (ManagedType>) (
(SingularAttribute, ?>) attribute
).getType(); else return (EntityType>) ((PluralAttribute, ?, ?>) attribute).getElementType();
}
@SuppressWarnings({ "rawtypes" })
private GraphQLInputObjectField getInputObjectField(Attribute attribute) {
GraphQLInputType type = getAttributeInputType(attribute);
return GraphQLInputObjectField
.newInputObjectField()
.name(attribute.getName())
.description(getSchemaDescription(attribute))
.type(type)
.build();
}
private Stream> findBasicAttributes(Collection> attributes) {
return attributes
.stream()
.filter(it -> it.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC);
}
private GraphQLInputType getAttributeInputType(Attribute, ?> attribute) {
return getAttributeInputType(attribute, false);
}
private GraphQLInputType getAttributeInputTypeForSearchByIdArg(Attribute, ?> attribute) {
return getAttributeInputType(attribute, true);
}
private GraphQLInputType getAttributeInputType(Attribute, ?> attribute, boolean searchByIdArgType) {
try {
return (GraphQLInputType) getAttributeType(attribute, true, searchByIdArgType);
} catch (ClassCastException e) {
throw new IllegalArgumentException("Attribute " + attribute + " cannot be mapped as an Input Argument");
}
}
private GraphQLOutputType getAttributeOutputType(Attribute, ?> attribute) {
try {
return (GraphQLOutputType) getAttributeType(attribute, false, false);
} catch (ClassCastException e) {
throw new IllegalArgumentException("Attribute " + attribute + " cannot be mapped as an Output Argument");
}
}
@SuppressWarnings("rawtypes")
protected GraphQLType getAttributeType(Attribute, ?> attribute, boolean input, boolean searchByIdArgType) {
if (isBasic(attribute)) {
return searchByIdArgType && isGraphQLIDType()
? Scalars.GraphQLID
: getGraphQLTypeFromJavaType(attribute.getJavaType());
} else if (isEmbeddable(attribute)) {
EmbeddableType embeddableType = (EmbeddableType) ((SingularAttribute) attribute).getType();
return getEmbeddableType(embeddableType, input, searchByIdArgType);
} else if (isToMany(attribute)) {
EntityType foreignType = (EntityType) ((PluralAttribute) attribute).getElementType();
return input
? getWhereInputType(foreignType)
: new GraphQLList(new GraphQLTypeReference(resolveEntityObjectTypeName(foreignType)));
} else if (isToOne(attribute)) {
EntityType foreignType = (EntityType) ((SingularAttribute) attribute).getType();
return input
? getWhereInputType(foreignType)
: new GraphQLTypeReference(resolveEntityObjectTypeName(foreignType));
} else if (isElementCollection(attribute)) {
Type foreignType = ((PluralAttribute) attribute).getElementType();
if (foreignType.getPersistenceType() == Type.PersistenceType.BASIC) {
GraphQLType graphQLType = getGraphQLTypeFromJavaType(foreignType.getJavaType());
return input ? graphQLType : new GraphQLList(graphQLType);
} else if (foreignType.getPersistenceType() == Type.PersistenceType.EMBEDDABLE) {
EmbeddableType embeddableType = EmbeddableType.class.cast(foreignType);
GraphQLType graphQLType = getEmbeddableType(embeddableType, input, searchByIdArgType);
return input ? graphQLType : new GraphQLList(graphQLType);
}
}
final String declaringType = attribute.getDeclaringType().getJavaType().getName(); // fully qualified name of the entity class
final String declaringMember = attribute.getJavaMember().getName(); // field name in the entity class
throw new UnsupportedOperationException(
"Attribute could not be mapped to GraphQL: field '" +
declaringMember +
"' of entity class '" +
declaringType +
"'"
);
}
protected final boolean isEmbeddable(Attribute, ?> attribute) {
return attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED;
}
protected final boolean isBasic(Attribute, ?> attribute) {
return (
attribute instanceof SingularAttribute &&
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC
);
}
protected final boolean isElementCollection(Attribute, ?> attribute) {
return attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION;
}
protected final boolean isToMany(Attribute, ?> attribute) {
return (
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_MANY ||
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_MANY
);
}
protected final boolean isOneToMany(Attribute, ?> attribute) {
return attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_MANY;
}
protected final boolean isToOne(Attribute, ?> attribute) {
return (
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE ||
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE
);
}
private boolean isPlural(Attribute, ?> attribute) {
return (
attribute instanceof PluralAttribute &&
(
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_MANY ||
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_MANY
)
);
}
private boolean isSingular(Attribute, ?> attribute) {
return (
attribute instanceof SingularAttribute &&
attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.BASIC
);
}
protected final boolean isValidInput(Attribute, ?> attribute) {
return (
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.BASIC ||
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ELEMENT_COLLECTION ||
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.EMBEDDED
);
}
private String getSchemaDescription(Attribute, ?> attribute) {
return EntityIntrospector
.introspect(attribute.getDeclaringType())
.getSchemaDescription(attribute.getName())
.orElse(null);
}
private String getSchemaDescription(EntityType> entityType) {
return EntityIntrospector.introspect(entityType).getSchemaDescription().orElse(null);
}
private String getSchemaDescription(EmbeddableType> embeddableType) {
return EntityIntrospector.introspect(embeddableType).getSchemaDescription().orElse(null);
}
private boolean isNotIgnored(EmbeddableType> attribute) {
return isNotIgnored(attribute.getJavaType());
}
private boolean isNotIgnored(Attribute, ?> attribute) {
return isNotIgnored(attribute.getJavaMember()) && isNotIgnored(attribute.getJavaType());
}
private boolean isIdentity(Attribute, ?> attribute) {
return attribute instanceof SingularAttribute && ((SingularAttribute, ?>) attribute).isId();
}
private boolean isNotIgnored(EntityType> entityType) {
return isNotIgnored(entityType.getJavaType()) && isNotIgnored(entityType.getJavaType().getName());
}
private boolean isNotIgnored(String name) {
return entityPaths.isEmpty() || entityPaths.stream().anyMatch(prefix -> name.startsWith(prefix));
}
private boolean isNotIgnored(Member member) {
return member instanceof AnnotatedElement && isNotIgnored((AnnotatedElement) member);
}
private boolean isNotIgnored(AnnotatedElement annotatedElement) {
return annotatedElement != null && annotatedElement.getAnnotation(GraphQLIgnore.class) == null;
}
protected boolean isNotIgnoredFilter(Attribute, ?> attribute) {
return isNotIgnoredFilter(attribute.getJavaMember()) && isNotIgnoredFilter(attribute.getJavaType());
}
protected boolean isNotIgnoredFilter(EntityType> entityType) {
return isNotIgnoredFilter(entityType.getJavaType());
}
protected boolean isNotIgnoredFilter(Member member) {
return member instanceof AnnotatedElement && isNotIgnoredFilter((AnnotatedElement) member);
}
protected boolean isNotIgnoredFilter(AnnotatedElement annotatedElement) {
if (annotatedElement != null) {
GraphQLIgnoreFilter schemaDocumentation = annotatedElement.getAnnotation(GraphQLIgnoreFilter.class);
return schemaDocumentation == null;
}
return false;
}
protected boolean isNotIgnoredOrder(Attribute, ?> attribute) {
AnnotatedElement annotatedElement = (AnnotatedElement) attribute.getJavaMember();
if (annotatedElement != null) {
GraphQLIgnoreOrder schemaDocumentation = annotatedElement.getAnnotation(GraphQLIgnoreOrder.class);
return schemaDocumentation == null;
}
return false;
}
@SuppressWarnings("unchecked")
private GraphQLOutputType getGraphQLTypeFromJavaType(Class> clazz) {
if (clazz.isEnum()) {
if (classCache.containsKey(clazz)) return classCache.get(clazz);
GraphQLEnumType.Builder enumBuilder = newEnum().name(clazz.getSimpleName());
int ordinal = 0;
for (Enum> enumValue : ((Class>) clazz).getEnumConstants()) enumBuilder.value(enumValue.name());
GraphQLEnumType enumType = enumBuilder.build();
classCache.putIfAbsent(clazz, enumType);
return enumType;
} else if (clazz.isArray() && !JavaScalars.contains(clazz)) {
return GraphQLList.list(JavaScalars.of(clazz.getComponentType()));
}
return JavaScalars.of(clazz);
}
protected GraphQLInputType getFieldsEnumType(EntityType> entityType) {
GraphQLEnumType.Builder enumBuilder = newEnum().name(entityType.getName() + "FieldsEnum");
final AtomicInteger ordinal = new AtomicInteger();
entityType
.getAttributes()
.stream()
.filter(this::isValidInput)
.filter(this::isNotIgnored)
.forEach(it -> enumBuilder.value(it.getName()));
GraphQLInputType answer = enumBuilder.build();
return answer;
}
private static final GraphQLArgument paginationArgument = newArgument()
.name(PAGE_PARAM_NAME)
.description("Page object for pageble requests, specifying the requested start page and limit size.")
.type(
newInputObject()
.name("Page")
.description("Page fields for pageble requests.")
.field(
newInputObjectField()
.name(PAGE_START_PARAM_NAME)
.description("Start page that should be returned. Page numbers start with 1 (1-indexed)")
.defaultValue(1)
.type(Scalars.GraphQLInt)
.build()
)
.field(
newInputObjectField()
.name(PAGE_LIMIT_PARAM_NAME)
.description("Limit how many results should this page contain")
.type(Scalars.GraphQLInt)
.build()
)
.build()
)
.build();
private static final GraphQLEnumType orderByDirectionEnum = newEnum()
.name("OrderBy")
.description("Specifies the direction (Ascending / Descending) to sort a field.")
.value("ASC", "ASC", "Ascending")
.value("DESC", "DESC", "Descending")
.build();
/**
* @return the name
*/
public String getName() {
return this.name;
}
/**
* @param name the name to set
*/
@Override
public GraphQLJpaSchemaBuilder name(String name) {
this.name = name;
return this;
}
/**
* @return the description
*/
public String getDescription() {
return this.description;
}
/**
* @param description the description to set
*/
@Override
public GraphQLJpaSchemaBuilder description(String description) {
this.description = description;
return this;
}
public GraphQLJpaSchemaBuilder useDistinctParameter(boolean isUseDistinctParameter) {
this.isUseDistinctParameter = isUseDistinctParameter;
return this;
}
public boolean isDistinctParameter() {
return isUseDistinctParameter;
}
public boolean isDistinctFetcher() {
return isDefaultDistinct;
}
@Deprecated
public GraphQLJpaSchemaBuilder setDefaultDistinct(boolean distinctFetcher) {
this.isDefaultDistinct = distinctFetcher;
return this;
}
public GraphQLJpaSchemaBuilder defaultDistinct(boolean isDefaultDistinct) {
this.isDefaultDistinct = isDefaultDistinct;
return this;
}
/**
* @param namingStrategy the namingStrategy to set
*/
@Deprecated
public void setNamingStrategy(NamingStrategy namingStrategy) {
this.namingStrategy = namingStrategy;
}
public GraphQLSchemaBuilder enableAggregate(boolean enableAggregate) {
this.enableAggregate = enableAggregate;
return this;
}
static class NoOpCoercing implements Coercing {
@Override
public Object serialize(Object input) {
return input;
}
@Override
public Object parseValue(Object input) {
return input;
}
@Override
public Object parseLiteral(Object input) {
return input;
}
}
@Override
public GraphQLJpaSchemaBuilder entityPath(String path) {
Assert.assertNotNull(path, () -> "path is null");
entityPaths.add(path);
return this;
}
public GraphQLJpaSchemaBuilder queryByIdFieldNameCustomizer(Function queryByIdFieldNameCustomizer) {
this.queryByIdFieldNameCustomizer = queryByIdFieldNameCustomizer;
return this;
}
public GraphQLJpaSchemaBuilder queryAllFieldNameCustomizer(Function queryAllFieldNameCustomizer) {
this.queryAllFieldNameCustomizer = queryAllFieldNameCustomizer;
return this;
}
public GraphQLJpaSchemaBuilder queryTypeNameCustomizer(Function queryTypeNameCustomizer) {
this.queryTypeNameCustomizer = queryTypeNameCustomizer;
return this;
}
public GraphQLJpaSchemaBuilder queryResultTypeNameCustomizer(
Function queryResultTypeNameCustomizer
) {
this.queryResultTypeNameCustomizer = queryResultTypeNameCustomizer;
return this;
}
public GraphQLJpaSchemaBuilder queryWhereArgumentTypeNameCustomizer(
Function queryWhereArgumentTypeNameCustomizer
) {
this.queryWhereArgumentTypeNameCustomizer = queryWhereArgumentTypeNameCustomizer;
return this;
}
public GraphQLJpaSchemaBuilder queryEmbeddableTypeNameCustomizer(
Function queryEmbeddableTypeNameCustomizer
) {
this.queryEmbeddableTypeNameCustomizer = queryEmbeddableTypeNameCustomizer;
return this;
}
public GraphQLJpaSchemaBuilder subqueryArgumentTypeNameCustomizer(
Function subqueryArgumentTypeNameCustomizer
) {
this.subqueryArgumentTypeNameCustomizer = subqueryArgumentTypeNameCustomizer;
return this;
}
public GraphQLJpaSchemaBuilder queryWhereInputTypeNameCustomizer(
Function queryWhereInputTypeNameCustomizer
) {
this.queryWhereInputTypeNameCustomizer = queryWhereInputTypeNameCustomizer;
return this;
}
@Override
public GraphQLJpaSchemaBuilder namingStrategy(NamingStrategy instance) {
Assert.assertNotNull(instance, () -> "instance is null");
this.namingStrategy = instance;
return this;
}
public boolean isToManyDefaultOptional() {
return toManyDefaultOptional;
}
@Deprecated
public void setToManyDefaultOptional(boolean toManyDefaultOptional) {
this.toManyDefaultOptional = toManyDefaultOptional;
}
public GraphQLJpaSchemaBuilder toManyDefaultOptional(boolean toManyDefaultOptional) {
this.toManyDefaultOptional = toManyDefaultOptional;
return this;
}
public boolean isEnableSubscription() {
return enableSubscription;
}
public GraphQLJpaSchemaBuilder enableSubscription(boolean enableSubscription) {
this.enableSubscription = enableSubscription;
return this;
}
public boolean isEnableRelay() {
return enableRelay;
}
public GraphQLJpaSchemaBuilder enableRelay(boolean enableRelay) {
this.enableRelay = enableRelay;
return this;
}
public int getDefaultMaxResults() {
return defaultMaxResults;
}
public GraphQLJpaSchemaBuilder defaultMaxResults(int defaultMaxResults) {
this.defaultMaxResults = defaultMaxResults;
return this;
}
public int getDefaultPageLimitSize() {
return defaultPageLimitSize;
}
public GraphQLJpaSchemaBuilder defaultPageLimitSize(int defaultPageLimitSize) {
this.defaultPageLimitSize = defaultPageLimitSize;
return this;
}
public int getDefaultFetchSize() {
return defaultFetchSize;
}
public GraphQLJpaSchemaBuilder defaultFetchSize(int defaultFetchSize) {
this.defaultFetchSize = defaultFetchSize;
return this;
}
public boolean isEnableDefaultMaxResults() {
return enableDefaultMaxResults;
}
public GraphQLJpaSchemaBuilder enableDefaultMaxResults(boolean enableDefaultMaxResults) {
this.enableDefaultMaxResults = enableDefaultMaxResults;
return this;
}
public GraphQLJpaSchemaBuilder restrictedKeysProvider(RestrictedKeysProvider restrictedKeysProvider) {
this.restrictedKeysProvider = restrictedKeysProvider;
return this;
}
public RestrictedKeysProvider getRestrictedKeysProvider() {
return restrictedKeysProvider;
}
public boolean isEnableResultStream() {
return enableResultStream;
}
public GraphQLJpaSchemaBuilder enableResultStream(boolean enableResultStream) {
this.enableResultStream = enableResultStream;
return this;
}
@Override
public GraphQLJpaSchemaBuilder graphQLIDType(boolean graphQLIDType) {
this.graphQLIDType = graphQLIDType;
return this;
}
public boolean isGraphQLIDType() {
return this.graphQLIDType;
}
final class GraphQLObjectTypeMetadataImpl implements GraphQLObjectTypeMetadata {
@Override
public EntityType> entity(String objectType) {
return entityObjectTypeResolver.apply(objectType);
}
@Override
public EmbeddableType> embeddable(String objectType) {
return embeddableObjectTypeResolver.apply(objectType);
}
}
}