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.GraphQLJpaQueryFactory 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.GraphQLJpaSchemaBuilder.SELECT_DISTINCT_PARAM_NAME;
import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.isAfterArgument;
import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.isDistinctArgument;
import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.isFirstArgument;
import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.isLogicalArgument;
import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.isPageArgument;
import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.isWhereArgument;
import static com.introproventures.graphql.jpa.query.support.GraphQLSupport.selections;
import static graphql.introspection.Introspection.SchemaMetaFieldDef;
import static graphql.introspection.Introspection.TypeMetaFieldDef;
import static graphql.introspection.Introspection.TypeNameMetaFieldDef;
import static java.lang.Integer.min;
import static java.util.stream.Collectors.groupingBy;
import com.introproventures.graphql.jpa.query.annotation.GraphQLDefaultOrderBy;
import com.introproventures.graphql.jpa.query.introspection.ReflectionUtil;
import com.introproventures.graphql.jpa.query.schema.JavaScalars;
import com.introproventures.graphql.jpa.query.schema.RestrictedKeysProvider;
import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector.EntityIntrospectionResult;
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.support.GraphQLSupport;
import graphql.GraphQLException;
import graphql.execution.CoercedVariables;
import graphql.execution.MergedField;
import graphql.execution.ValuesResolver;
import graphql.language.Argument;
import graphql.language.ArrayValue;
import graphql.language.BooleanValue;
import graphql.language.EnumValue;
import graphql.language.Field;
import graphql.language.FloatValue;
import graphql.language.IntValue;
import graphql.language.NullValue;
import graphql.language.ObjectField;
import graphql.language.ObjectValue;
import graphql.language.StringValue;
import graphql.language.Value;
import graphql.language.VariableReference;
import graphql.schema.Coercing;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.GraphQLList;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import graphql.schema.GraphQLType;
import jakarta.persistence.EntityGraph;
import jakarta.persistence.EntityManager;
import jakarta.persistence.Subgraph;
import jakarta.persistence.TypedQuery;
import jakarta.persistence.criteria.AbstractQuery;
import jakarta.persistence.criteria.CriteriaBuilder;
import jakarta.persistence.criteria.CriteriaQuery;
import jakarta.persistence.criteria.Expression;
import jakarta.persistence.criteria.Fetch;
import jakarta.persistence.criteria.From;
import jakarta.persistence.criteria.Join;
import jakarta.persistence.criteria.JoinType;
import jakarta.persistence.criteria.Order;
import jakarta.persistence.criteria.Path;
import jakarta.persistence.criteria.Predicate;
import jakarta.persistence.criteria.Root;
import jakarta.persistence.criteria.Selection;
import jakarta.persistence.criteria.Subquery;
import jakarta.persistence.metamodel.Attribute;
import jakarta.persistence.metamodel.Attribute.PersistentAttributeType;
import jakarta.persistence.metamodel.EmbeddableType;
import jakarta.persistence.metamodel.EntityType;
import jakarta.persistence.metamodel.PluralAttribute;
import jakarta.persistence.metamodel.SingularAttribute;
import jakarta.persistence.metamodel.Type;
import java.beans.BeanInfo;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Constructor;
import java.lang.reflect.Member;
import java.lang.reflect.Method;
import java.util.AbstractMap.SimpleEntry;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Function;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import java.util.stream.Stream;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Provides implemetation for GraphQL JPA Query Factory
*
* @author Igor Dianov
*
*/
public final class GraphQLJpaQueryFactory {
private static final String DESC = "DESC";
private static final Logger logger = LoggerFactory.getLogger(GraphQLJpaQueryFactory.class);
public static final String JAKARTA_PERSISTENCE_FETCHGRAPH = "jakarta.persistence.fetchgraph";
private static Function unproxy;
static {
try {
Class> hibernateClass = Class.forName("org.hibernate.Hibernate");
Method unproxyMethod = hibernateClass.getDeclaredMethod("unproxy", Object.class);
unproxy =
proxy -> {
try {
return unproxyMethod.invoke(null, proxy);
} catch (Exception ignored) {}
return proxy;
};
} catch (Exception ignored) {
unproxy = Function.identity();
}
}
protected static final String WHERE = "where";
protected static final String OPTIONAL = "optional";
protected static final String ORG_HIBERNATE_CACHEABLE = "org.hibernate.cacheable";
protected static final String ORG_HIBERNATE_FETCH_SIZE = "org.hibernate.fetchSize";
protected static final String ORG_HIBERNATE_READ_ONLY = "org.hibernate.readOnly";
private final Map entityTypeMap = new ConcurrentHashMap<>();
private final Map embeddableTypeMap = new ConcurrentHashMap<>();
protected final EntityManager entityManager;
protected final EntityType> entityType;
private final boolean toManyDefaultOptional;
private final boolean defaultDistinct;
private final String selectNodeName;
private final GraphQLObjectType entityObjectType;
private final int defaultFetchSize;
private final RestrictedKeysProvider restrictedKeysProvider;
private final boolean resultStream;
private final GraphQLObjectTypeMetadata graphQLObjectTypeMetadata;
private GraphQLJpaQueryFactory(Builder builder) {
this.entityManager = builder.entityManager;
this.entityType = builder.entityType;
this.entityObjectType = builder.entityObjectType;
this.selectNodeName = builder.selectNodeName;
this.toManyDefaultOptional = builder.toManyDefaultOptional;
this.defaultDistinct = builder.defaultDistinct;
this.defaultFetchSize = builder.defaultFetchSize;
this.restrictedKeysProvider = builder.restrictedKeysProvider;
this.resultStream = builder.resultStream;
this.graphQLObjectTypeMetadata = builder.graphQLObjectTypeMetadata;
}
public DataFetchingEnvironment getQueryEnvironment(DataFetchingEnvironment environment, MergedField queryField) {
// Override query environment with associated entity object type and select field
return DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.fieldType(getEntityObjectType())
.mergedField(queryField)
.build();
}
public Optional> getRestrictedKeys(DataFetchingEnvironment environment) {
EntityIntrospectionResult entityDescriptor = EntityIntrospector.introspect(entityType);
Optional> restrictedKeys = restrictedKeysProvider.apply(entityDescriptor);
List restrictedKeysValues = new ArrayList<>();
if (restrictedKeys.isPresent()) {
restrictedKeys
.get()
.stream()
.filter(key -> !"*".equals(key))
.map(key -> {
Type> idType = entityType.getIdType();
Class> clazz = idType.getJavaType();
GraphQLScalarType scalar = JavaScalars.of(clazz);
return scalar.getCoercing().parseValue(key);
})
.forEach(restrictedKeysValues::add);
return Optional.of(restrictedKeysValues);
}
return Optional.empty();
}
public List queryKeys(
DataFetchingEnvironment environment,
int firstResult,
int maxResults,
List restrictedKeys
) {
MergedField queryField = resolveQueryField(environment.getField());
// Override query environment with associated entity object type and
final DataFetchingEnvironment queryEnvironment = getQueryEnvironment(environment, queryField);
TypedQuery keysQuery = getKeysQuery(queryEnvironment, queryEnvironment.getField(), restrictedKeys);
keysQuery.setFirstResult(firstResult).setMaxResults(maxResults);
if (logger.isDebugEnabled()) {
logger.info("\nGraphQL JPQL Keys Query String:\n {}", getJPQLQueryString(keysQuery));
}
if (logger.isTraceEnabled()) {
logger.trace(
"Query keys in session {} for field {} on {}",
entityManager,
environment.getField().getName(),
Thread.currentThread()
);
}
return keysQuery.getResultList();
}
public List queryResultList(DataFetchingEnvironment environment, int maxResults, List keys) {
// Let's execute query and get result as stream
Stream resultStream = queryResultStream(environment, maxResults, keys);
var size = keys.isEmpty() ? maxResults : min(keys.size(), maxResults);
// Let's wrap stream into lazy list to pass it downstream
return ResultStreamWrapper.wrap(resultStream, size);
}
protected Stream queryResultStream(DataFetchingEnvironment environment, int maxResults, List keys) {
MergedField queryField = resolveQueryField(environment.getField());
// Override query environment with associated entity object type and
final DataFetchingEnvironment queryEnvironment = getQueryEnvironment(environment, queryField);
final int fetchSize = min(maxResults, defaultFetchSize);
final boolean isDistinct = resolveDistinctArgument(queryEnvironment.getField());
final TypedQuery query = getQuery(
queryEnvironment,
queryEnvironment.getField(),
isDistinct,
keys.toArray()
);
// Let's create entity graph from selection
var entityGraph = createEntityGraph(queryEnvironment);
// Let's execute query and get wrap result into stream
return getResultStream(query, fetchSize, isDistinct, entityGraph);
}
protected Stream getResultStream(
TypedQuery query,
int fetchSize,
boolean isDistinct,
EntityGraph> entityGraph
) {
// Let's try to reduce overhead and disable all caching
query.setHint(ORG_HIBERNATE_READ_ONLY, true);
query.setHint(ORG_HIBERNATE_FETCH_SIZE, fetchSize);
query.setHint(ORG_HIBERNATE_CACHEABLE, false);
if (entityGraph != null) {
query.setHint(JAKARTA_PERSISTENCE_FETCHGRAPH, entityGraph);
}
if (logger.isDebugEnabled()) {
logger.info("\nGraphQL JPQL Fetch Query String:\n {}", getJPQLQueryString(query));
}
if (logger.isTraceEnabled()) {
logger.trace(
"Query results in session {} for query {} running on {}",
entityManager,
getJPQLQueryString(query),
Thread.currentThread()
);
}
// Let's execute query and wrap result into stream
final Stream resultStream = this.resultStream ? query.getResultStream() : query.getResultList().stream();
return resultStream.map(this::unproxy).peek(this::detach);
}
protected Object querySingleResult(final DataFetchingEnvironment environment) {
final MergedField queryField = flattenEmbeddedIdArguments(environment.getField());
final DataFetchingEnvironment queryEnvironment = getQueryEnvironment(environment, queryField);
Optional> restrictedKeys = getRestrictedKeys(queryEnvironment);
if (restrictedKeys.isPresent()) {
TypedQuery query = getQuery(
queryEnvironment,
queryEnvironment.getField(),
true,
restrictedKeys.get().toArray()
);
if (logger.isDebugEnabled()) {
logger.info("\nGraphQL JPQL Single Result Query String:\n {}", getJPQLQueryString(query));
}
return Optional.ofNullable(query.getSingleResult()).map(this::unproxyAndThenDetach).orElse(null);
}
return null;
}
public Long queryTotalCount(DataFetchingEnvironment environment, Optional> restrictedKeys) {
final MergedField queryField = flattenEmbeddedIdArguments(environment.getField());
final DataFetchingEnvironment queryEnvironment = getQueryEnvironment(environment, queryField);
if (restrictedKeys.isPresent()) {
TypedQuery countQuery = getCountQuery(
queryEnvironment,
queryEnvironment.getField(),
restrictedKeys.get()
);
if (logger.isDebugEnabled()) {
logger.info("\nGraphQL JPQL Count Query String:\n {}", getJPQLQueryString(countQuery));
}
return countQuery.getSingleResult();
}
return 0L;
}
public Long queryAggregateCount(
String aggregate,
DataFetchingEnvironment environment,
Optional> restrictedKeys
) {
final MergedField queryField = flattenEmbeddedIdArguments(environment.getField());
final DataFetchingEnvironment queryEnvironment = getQueryEnvironment(environment, queryField);
if (restrictedKeys.isPresent()) {
TypedQuery countQuery = getAggregateCountQuery(
queryEnvironment,
queryEnvironment.getField(),
aggregate,
restrictedKeys.get()
);
if (logger.isDebugEnabled()) {
logger.info("\nGraphQL JPQL Count Query String:\n {}", getJPQLQueryString(countQuery));
}
return countQuery.getSingleResult();
}
return 0L;
}
public List queryAggregateGroupByCount(
String alias,
Optional countOf,
DataFetchingEnvironment environment,
Optional> restrictedKeys,
Map.Entry... groupings
) {
final MergedField queryField = flattenEmbeddedIdArguments(environment.getField());
final DataFetchingEnvironment queryEnvironment = getQueryEnvironment(environment, queryField);
if (restrictedKeys.isPresent()) {
TypedQuery countQuery = getAggregateGroupByCountQuery(
queryEnvironment,
queryEnvironment.getField(),
alias,
countOf,
restrictedKeys.get(),
groupings
);
if (logger.isDebugEnabled()) {
logger.info("\nGraphQL JPQL Count Query String:\n {}", getJPQLQueryString(countQuery));
}
return countQuery.getResultList();
}
return Collections.emptyList();
}
public List queryAggregateGroupByAssociationCount(
String countAlias,
String association,
DataFetchingEnvironment environment,
Optional> restrictedKeys,
Map.Entry... groupings
) {
final MergedField queryField = flattenEmbeddedIdArguments(environment.getField());
final DataFetchingEnvironment queryEnvironment = getQueryEnvironment(environment, queryField);
if (restrictedKeys.isPresent()) {
TypedQuery countQuery = getAggregateGroupByAssociationCountQuery(
queryEnvironment,
queryEnvironment.getField(),
countAlias,
association,
restrictedKeys.get(),
groupings
);
if (logger.isDebugEnabled()) {
logger.info("\nGraphQL JPQL Count Query String:\n {}", getJPQLQueryString(countQuery));
}
return countQuery.getResultList();
}
return Collections.emptyList();
}
protected TypedQuery getQuery(
DataFetchingEnvironment environment,
Field field,
boolean isDistinct,
Object... keys
) {
DataFetchingEnvironment queryEnvironment = DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.localContext(Boolean.TRUE) // Fetch mode
.build();
CriteriaQuery criteriaQuery = getCriteriaQuery(queryEnvironment, field, isDistinct, keys);
return entityManager.createQuery(criteriaQuery);
}
protected TypedQuery getCountQuery(DataFetchingEnvironment environment, Field field, List keys) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(Long.class);
Root> root = query.from(entityType);
DataFetchingEnvironment queryEnvironment = DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.root(query)
.localContext(Boolean.FALSE) // Join mode
.build();
root.alias("root");
query.select(cb.count(root));
List predicates = field
.getArguments()
.stream()
.map(it -> getPredicate(field, cb, root, null, queryEnvironment, it))
.filter(it -> it != null)
.collect(Collectors.toList());
if (!keys.isEmpty() && hasIdAttribute()) {
Predicate restrictions = root.get(idAttributeName()).in(keys);
predicates.add(restrictions);
}
query.where(predicates.toArray(new Predicate[0]));
return entityManager.createQuery(query);
}
protected TypedQuery getAggregateCountQuery(
DataFetchingEnvironment environment,
Field field,
String aggregate,
List keys,
String... groupings
) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(Long.class);
Root> root = query.from(entityType);
Join, ?> join = root.join(aggregate);
DataFetchingEnvironment queryEnvironment = DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.root(query)
.localContext(Boolean.FALSE) // Join mode
.build();
query.select(cb.count(join));
List predicates = field
.getArguments()
.stream()
.map(it -> getPredicate(field, cb, root, null, queryEnvironment, it))
.filter(it -> it != null)
.collect(Collectors.toList());
if (!keys.isEmpty() && hasIdAttribute()) {
Predicate restrictions = root.get(idAttributeName()).in(keys);
predicates.add(restrictions);
}
query.where(predicates.toArray(new Predicate[0]));
return entityManager.createQuery(query);
}
protected TypedQuery getAggregateGroupByCountQuery(
DataFetchingEnvironment environment,
Field field,
String alias,
Optional countOfJoin,
List keys,
Map.Entry... groupBy
) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery query = cb.createQuery(Map.class);
final Root> root = query.from(entityType);
final DataFetchingEnvironment queryEnvironment = DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.root(query)
.localContext(Boolean.FALSE) // Join mode
.build();
final List> selections = new ArrayList<>();
Stream.of(groupBy).map(group -> root.get(group.getValue()).alias(group.getKey())).forEach(selections::add);
final Expression>[] groupings = Stream
.of(groupBy)
.map(group -> root.get(group.getValue()))
.toArray(Expression[]::new);
countOfJoin.ifPresentOrElse(
it -> selections.add(cb.count(root.join(it)).alias(alias)),
() -> selections.add(cb.count(root).alias(alias))
);
query.multiselect(selections).groupBy(groupings);
List predicates = field
.getArguments()
.stream()
.map(it -> getPredicate(field, cb, root, null, queryEnvironment, it))
.filter(it -> it != null)
.collect(Collectors.toList());
if (!keys.isEmpty() && hasIdAttribute()) {
Predicate restrictions = root.get(idAttributeName()).in(keys);
predicates.add(restrictions);
}
query.where(predicates.toArray(new Predicate[0]));
return entityManager.createQuery(query);
}
protected TypedQuery getAggregateGroupByAssociationCountQuery(
DataFetchingEnvironment environment,
Field field,
String countAlias,
String association,
List keys,
Map.Entry... groupBy
) {
final CriteriaBuilder cb = entityManager.getCriteriaBuilder();
final CriteriaQuery query = cb.createQuery(Map.class);
final Root> root = query.from(entityType);
final Join, ?> join = root.join(association);
final DataFetchingEnvironment queryEnvironment = DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.root(query)
.localContext(Boolean.FALSE) // Join mode
.build();
final List> selections = new ArrayList<>();
Stream.of(groupBy).map(group -> join.get(group.getValue()).alias(group.getKey())).forEach(selections::add);
selections.add(cb.count(join).alias(countAlias));
final Expression>[] groupings = Stream
.of(groupBy)
.map(group -> join.get(group.getValue()))
.toArray(Expression[]::new);
query.multiselect(selections).groupBy(groupings);
List predicates = field
.getArguments()
.stream()
.map(it -> getPredicate(field, cb, root, null, queryEnvironment, it))
.filter(it -> it != null)
.collect(Collectors.toList());
if (!keys.isEmpty() && hasIdAttribute()) {
Predicate restrictions = root.get(idAttributeName()).in(keys);
predicates.add(restrictions);
}
query.where(predicates.toArray(new Predicate[0]));
return entityManager.createQuery(query);
}
protected TypedQuery getKeysQuery(DataFetchingEnvironment environment, Field field, List keys) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery(Object.class);
Root> from = query.from(entityType);
from.alias("root");
DataFetchingEnvironment queryEnvironment = DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.root(query)
.localContext(Boolean.FALSE)
.build();
if (hasIdAttribute()) {
query.select(from.get(idAttributeName()));
} else if (hasIdClassAttribue()) {
List> selection = Stream
.of(idClassAttributeNames())
.map(from::get)
.collect(Collectors.toList());
query.multiselect(selection);
}
List predicates = field
.getArguments()
.stream()
.map(it -> getPredicate(field, cb, from, null, queryEnvironment, it))
.filter(it -> it != null)
.collect(Collectors.toList());
if (!keys.isEmpty() && hasIdAttribute()) {
Predicate restrictions = from.get(idAttributeName()).in(keys);
predicates.add(restrictions);
}
query.where(predicates.toArray(new Predicate[0]));
GraphQLSupport
.fields(field.getSelectionSet())
.filter(it -> isPersistent(environment, it.getName()))
.forEach(selection -> {
Path> selectionPath = from.get(selection.getName());
// Process the orderBy clause
mayBeAddOrderBy(selection, query, cb, selectionPath, queryEnvironment);
});
mayBeAddDefaultOrderBy(query, from, cb);
return entityManager.createQuery(query);
}
protected Map> loadOneToMany(DataFetchingEnvironment environment, Set keys) {
Field field = environment.getField();
TypedQuery query = getBatchQuery(environment, field, isDefaultDistinct(), keys);
var entityGraph = createEntityGraph(environment);
List resultList = getResultList(query, entityGraph);
if (logger.isTraceEnabled()) {
logger.trace(
"loadOneToMany in session {} for field {} with keys {} on {}",
entityManager,
environment.getField().getName(),
keys,
Thread.currentThread()
);
}
Map> batch = resultList
.stream()
.collect(
groupingBy(
t -> t[0],
Collectors.mapping(t -> this.unproxyAndThenDetach(t[1]), GraphQLSupport.toResultList())
)
);
Map> resultMap = new LinkedHashMap<>(keys.size());
keys.forEach(it -> {
List list = batch.getOrDefault(it, Collections.emptyList());
resultMap.put(it, list);
});
return resultMap;
}
protected Map loadManyToOne(DataFetchingEnvironment environment, Set keys) {
Field field = environment.getField();
TypedQuery query = getBatchQuery(environment, field, isDefaultDistinct(), keys);
var entityGraph = createEntityGraph(environment);
List resultList = getResultList(query, entityGraph);
Map resultMap = new LinkedHashMap<>(resultList.size());
resultList.forEach(item -> resultMap.put(item[0], this.unproxyAndThenDetach(item[1])));
return resultMap;
}
protected List getResultList(TypedQuery query, EntityGraph> entityGraph) {
if (logger.isDebugEnabled()) {
logger.info("\nGraphQL JPQL Batch Query String:\n {}", getJPQLQueryString(query));
}
// Let' try reduce overhead and disable all caching
query.setHint(ORG_HIBERNATE_READ_ONLY, true);
query.setHint(ORG_HIBERNATE_FETCH_SIZE, defaultFetchSize);
query.setHint(ORG_HIBERNATE_CACHEABLE, false);
if (entityGraph != null) {
query.setHint(JAKARTA_PERSISTENCE_FETCHGRAPH, entityGraph);
}
return query.getResultList();
}
@SuppressWarnings({ "rawtypes", "unchecked" })
protected TypedQuery getBatchQuery(
DataFetchingEnvironment environment,
Field field,
boolean isDistinct,
Set keys
) {
SingularAttribute parentIdAttribute = entityType.getId(Object.class);
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
//CriteriaQuery query = cb.createQuery((Class) entityType.getJavaType());
CriteriaQuery query = cb.createQuery(Object[].class);
Root> from = query.from(entityType);
DataFetchingEnvironment queryEnvironment = DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.root(query)
.localContext(Boolean.TRUE)
.build();
from.alias("owner");
// Must use inner join in parent context
Join join = from.join(field.getName()).on(from.get(parentIdAttribute.getName()).in(keys));
query.multiselect(from.get(parentIdAttribute.getName()), join.alias(field.getName()));
List predicates = getFieldPredicates(field, query, cb, from, join, queryEnvironment);
query.where(predicates.toArray(new Predicate[0]));
// optionally add default ordering
// FIXME need to extract correct id attribute from collection entity
// mayBeAddDefaultOrderBy(query, join, cb);
return entityManager.createQuery(query.distinct(isDistinct));
}
@SuppressWarnings({ "rawtypes", "unchecked" })
protected TypedQuery getBatchCollectionQuery(
DataFetchingEnvironment environment,
Field field,
boolean isDistinct,
Set keys
) {
SingularAttribute parentIdAttribute = entityType.getId(Object.class);
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
//CriteriaQuery query = cb.createQuery((Class) entityType.getJavaType());
CriteriaQuery query = cb.createQuery();
Root> from = query.from(entityType);
DataFetchingEnvironment queryEnvironment = DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.root(query)
.localContext(Boolean.TRUE)
.build();
from.alias("owner");
// Must use inner join in parent context
Join join = from.join(field.getName()).on(from.get(parentIdAttribute.getName()).in(keys));
query.select(join.alias(field.getName()));
List predicates = getFieldPredicates(field, query, cb, from, join, queryEnvironment);
query.where(predicates.toArray(new Predicate[0]));
// optionally add default ordering
mayBeAddDefaultOrderBy(query, join, cb);
return entityManager.createQuery(query.distinct(isDistinct));
}
@SuppressWarnings("unchecked")
protected CriteriaQuery getCriteriaQuery(
DataFetchingEnvironment environment,
Field field,
boolean isDistinct,
Object... keys
) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery query = cb.createQuery((Class) entityType.getJavaType());
Root> from = query.from(entityType);
DataFetchingEnvironment queryEnvironment = DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.root(query)
.build();
from.alias(from.getModel().getName().toLowerCase());
// Build predicates from query arguments
List predicates = getFieldPredicates(field, query, cb, from, from, queryEnvironment);
if (keys.length > 0) {
if (hasIdAttribute()) {
predicates.add(from.get(idAttributeName()).in(keys));
} // array of idClass attributes
else if (hasIdClassAttribue()) {
String[] names = idClassAttributeNames();
Map> idKeys = new HashMap<>();
IntStream
.range(0, keys.length)
.mapToObj(i -> (Object[]) keys[i])
.forEach(values -> {
IntStream
.range(0, values.length)
.forEach(i -> {
idKeys.computeIfAbsent(names[i], key -> new ArrayList<>()).add(values[i]);
});
});
List idPredicates = Stream
.of(names)
.map(name -> {
return from.get(name).in(idKeys.get(name).toArray(new Object[0]));
})
.collect(Collectors.toList());
predicates.add(cb.and(idPredicates.toArray(new Predicate[0])));
}
}
// Use AND clause to filter results
if (!predicates.isEmpty()) query.where(predicates.toArray(new Predicate[0]));
// optionally add default ordering
mayBeAddDefaultOrderBy(query, from, cb);
return query.distinct(isDistinct);
}
protected void mayBeAddOrderBy(
Field selectedField,
CriteriaQuery> query,
CriteriaBuilder cb,
Path> fieldPath,
DataFetchingEnvironment environment
) {
Attribute, ?> attribute = getAttribute(environment, selectedField.getName());
// Singular attributes only
if (attribute instanceof SingularAttribute) {
selectedField
.getArguments()
.stream()
.filter(this::isOrderByArgument)
.findFirst()
.map(argument -> getOrderByValue(argument, environment))
.ifPresent(orderBy -> {
List orders = new ArrayList<>(query.getOrderList());
if (DESC.equals(orderBy.getName())) {
orders.add(cb.desc(fieldPath));
} else {
orders.add(cb.asc(fieldPath));
}
query.orderBy(orders);
});
}
}
protected List getFieldPredicates(
Field field,
CriteriaQuery> query,
CriteriaBuilder cb,
Root> root,
From, ?> from,
DataFetchingEnvironment environment
) {
List arguments = new ArrayList<>();
List predicates = new ArrayList<>();
// Loop through all of the fields being requested
GraphQLSupport
.fields(field.getSelectionSet())
.filter(selection -> isPersistent(environment, selection.getName()))
.forEach(selection -> {
Path> fieldPath = from.get(selection.getName());
From, ?> fetch = null;
final Optional whereArgument = getArgument(selection, WHERE);
final Attribute, ?> fieldAttribute = getAttribute(environment, selection.getName());
// Build predicate arguments for singular attributes only
if (fieldAttribute instanceof SingularAttribute) {
// Process the orderBy clause
mayBeAddOrderBy(selection, query, cb, fieldPath, environment);
// Check if it's an object and the foreign side is One. Then we can eagerly join causing an inner join instead of 2 queries
SingularAttribute, ?> attribute = (SingularAttribute, ?>) fieldAttribute;
if (
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.MANY_TO_ONE ||
attribute.getPersistentAttributeType() == Attribute.PersistentAttributeType.ONE_TO_ONE
) {
// Let's do fugly conversion
Boolean isOptional = getOptionalArgumentValue(environment, selection, attribute);
// Let's apply left outer join to retrieve optional associations
if (!isOptional || !whereArgument.isPresent()) {
fetch = reuseFetch(from, selection.getName(), isOptional);
}
} else if (attribute.getPersistentAttributeType() == PersistentAttributeType.EMBEDDED) {
// Process where arguments clauses.
arguments.addAll(
selection
.getArguments()
.stream()
.filter(this::isPredicateArgument)
.map(it -> new Argument(selection.getName() + "." + it.getName(), it.getValue()))
.collect(Collectors.toList())
);
}
} else {
GraphQLObjectType objectType = getObjectType(environment);
EntityType> entityType = getEntityType(objectType);
PluralAttribute, ?, ?> attribute = (PluralAttribute, ?, ?>) entityType.getAttribute(
selection.getName()
);
// We must add plural attributes with explicit join fetch
// the many end is a collection, and it is always optional by default (empty collection)
Boolean isOptional = getOptionalArgumentValue(environment, selection, attribute);
// Let's join fetch element collections to avoid filtering their values used where search criteria
if (PersistentAttributeType.ELEMENT_COLLECTION == attribute.getPersistentAttributeType()) {
from.fetch(selection.getName(), JoinType.LEFT);
} else if (!whereArgument.isPresent() && !hasAnySelectionOrderBy(selection)) {
fetch = reuseFetch(from, selection.getName(), isOptional);
}
}
// Let's build join fetch graph to avoid Hibernate error:
// "query specified join fetching, but the owner of the fetched association was not present in the select list"
if (selection.getSelectionSet() != null && fetch != null) {
Map variables = environment.getVariables();
GraphQLFieldDefinition fieldDefinition = getFieldDefinition(
environment.getGraphQLSchema(),
this.getObjectType(environment),
selection
);
List values = whereArgument
.map(Collections::singletonList)
.orElse(Collections.emptyList());
Map fieldArguments = ValuesResolver.getArgumentValues(
fieldDefinition.getArguments(),
values,
new CoercedVariables(variables),
environment.getGraphQlContext(),
Locale.ROOT
);
DataFetchingEnvironment fieldEnvironment = wherePredicateEnvironment(
environment,
fieldDefinition,
fieldArguments
);
predicates.addAll(getFieldPredicates(selection, query, cb, root, fetch, fieldEnvironment));
}
});
arguments.addAll(field.getArguments());
arguments
.stream()
.filter(this::isPredicateArgument)
.map(it -> getPredicate(field, cb, root, from, environment, it))
.filter(it -> it != null)
.forEach(predicates::add);
return predicates;
}
protected Boolean getOptionalArgumentValue(
DataFetchingEnvironment environment,
Field selection,
Attribute, ?> attribute
) {
return getArgument(selection, OPTIONAL)
.map(it -> getArgumentValue(environment, it, Boolean.class))
.orElseGet(() -> isOptionalAttribute(attribute));
}
/**
* if query orders are empty, then apply default ascending ordering
* by root id attribute to prevent paging inconsistencies
*
* @param query
* @param from
* @param cb
*/
protected void mayBeAddDefaultOrderBy(CriteriaQuery> query, From, ?> from, CriteriaBuilder cb) {
if (query.getOrderList() == null || query.getOrderList().isEmpty()) {
Optional attributePropertyDescriptor = EntityIntrospector
.introspect(entityType)
.getPersistentPropertyDescriptors()
.stream()
.filter(AttributePropertyDescriptor::hasDefaultOrderBy)
.findFirst();
if (!attributePropertyDescriptor.isPresent()) {
if (hasIdAttribute()) {
query.orderBy(cb.asc(from.get(idAttributeName())));
} else if (hasIdClassAttribue()) {
List orders = Stream
.of(idClassAttributeNames())
.map(name -> cb.asc(from.get(name)))
.collect(Collectors.toList());
query.orderBy(orders);
}
} else {
AttributePropertyDescriptor attribute = attributePropertyDescriptor.get();
GraphQLDefaultOrderBy order = attribute.getDefaultOrderBy().get();
if (order.asc()) {
query.orderBy(cb.asc(from.get(attribute.getName())));
} else {
query.orderBy(cb.desc(from.get(attribute.getName())));
}
}
}
}
protected boolean isPredicateArgument(Argument argument) {
return !isOrderByArgument(argument) && !isOptionalArgument(argument);
}
protected boolean isOrderByArgument(Argument argument) {
return GraphQLJpaSchemaBuilder.ORDER_BY_PARAM_NAME.equals(argument.getName());
}
protected boolean isOptionalArgument(Argument argument) {
return OPTIONAL.equals(argument.getName());
}
protected Optional getArgument(Field selectedField, String argumentName) {
return selectedField.getArguments().stream().filter(it -> it.getName().equals(argumentName)).findFirst();
}
protected > R getAttribute(String attributeName) {
return (R) entityType.getAttribute(attributeName);
}
@SuppressWarnings("unchecked")
protected Predicate getPredicate(
Field field,
CriteriaBuilder cb,
Root> from,
From, ?> path,
DataFetchingEnvironment environment,
Argument argument
) {
if (
isLogicalArgument(argument) ||
isDistinctArgument(argument) ||
isPageArgument(argument) ||
isAfterArgument(argument) ||
isFirstArgument(argument)
) {
return null;
} else if (isWhereArgument(argument)) {
return getWherePredicate(cb, from, path, argumentEnvironment(environment, argument), argument);
} else if (!argument.getName().contains(".")) {
Attribute, ?> argumentEntityAttribute = getAttribute(environment, argument.getName());
// If the argument is a list, let's assume we need to join and do an 'in' clause
if (argumentEntityAttribute instanceof PluralAttribute) {
// Apply left outer join to retrieve optional associations
Boolean isFetch = environment.getLocalContext();
return (
isFetch ? reuseFetch(from, argument.getName(), false) : reuseJoin(from, argument.getName(), false)
).in(convertValue(environment, argument, argument.getValue()));
}
return cb.equal(path.get(argument.getName()), convertValue(environment, argument, argument.getValue()));
} else {
if (!argument.getName().endsWith(".where")) {
Path> argumentPath = getCompoundJoinedPath(path, argument.getName(), false);
return cb.equal(argumentPath, convertValue(environment, argument, argument.getValue()));
} else {
String fieldName = argument.getName().split("\\.")[0];
From, ?> join = getCompoundJoin(path, argument.getName(), true);
Argument where = new Argument(WHERE, argument.getValue());
Map variables = environment.getVariables();
GraphQLFieldDefinition fieldDef = getFieldDefinition(
environment.getGraphQLSchema(),
this.getObjectType(environment),
new Field(fieldName)
);
Map arguments = (Map) ValuesResolver
.getArgumentValues(
fieldDef.getArguments(),
Collections.singletonList(where),
new CoercedVariables(variables),
environment.getGraphQlContext(),
Locale.ROOT
)
.get(WHERE);
return getWherePredicate(
cb,
from,
join,
wherePredicateEnvironment(environment, fieldDef, arguments),
where
);
}
}
}
@SuppressWarnings("unchecked")
private > R getValue(Argument argument, DataFetchingEnvironment environment) {
Value> value = argument.getValue();
if (VariableReference.class.isInstance(value)) {
Object variableValue = getVariableReferenceValue((VariableReference) value, environment);
GraphQLArgument graphQLArgument = environment
.getExecutionStepInfo()
.getFieldDefinition()
.getArgument(argument.getName());
return (R) AstValueHelper.astFromValue(variableValue, graphQLArgument.getType());
}
return (R) value;
}
private EnumValue getOrderByValue(Argument argument, DataFetchingEnvironment environment) {
Value> value = argument.getValue();
if (VariableReference.class.isInstance(value)) {
Object variableValue = getVariableReferenceValue((VariableReference) value, environment);
return EnumValue.newEnumValue(variableValue.toString()).build();
}
return (EnumValue) value;
}
private Object getVariableReferenceValue(VariableReference variableReference, DataFetchingEnvironment env) {
return env.getVariables().get(variableReference.getName());
}
protected Predicate getWherePredicate(
CriteriaBuilder cb,
Root> root,
From, ?> path,
DataFetchingEnvironment environment,
Argument argument
) {
ObjectValue whereValue = getValue(argument, environment);
if (whereValue.getChildren().isEmpty()) return cb.conjunction();
Logical logical = extractLogical(argument);
Map predicateArguments = new LinkedHashMap<>();
predicateArguments.put(logical.name(), environment.getArguments());
DataFetchingEnvironment predicateDataFetchingEnvironment = DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.arguments(predicateArguments)
.build();
Argument predicateArgument = new Argument(logical.name(), whereValue);
return getArgumentPredicate(
cb,
(path != null) ? path : root,
predicateDataFetchingEnvironment,
predicateArgument
);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Predicate getArgumentPredicate(
CriteriaBuilder cb,
From, ?> from,
DataFetchingEnvironment environment,
Argument argument
) {
ObjectValue whereValue = getValue(argument, environment);
if (whereValue.getChildren().isEmpty()) return cb.disjunction();
Logical logical = extractLogical(argument);
List predicates = new ArrayList<>();
whereValue
.getObjectFields()
.stream()
.filter(it -> Logical.names().contains(it.getName()))
.map(it -> {
Map arguments = getFieldArguments(environment, it, argument);
if (it.getValue() instanceof ArrayValue) {
return getArgumentsPredicate(
cb,
from,
argumentEnvironment(environment, arguments),
new Argument(it.getName(), it.getValue())
);
}
return getArgumentPredicate(
cb,
from,
argumentEnvironment(environment, arguments),
new Argument(it.getName(), it.getValue())
);
})
.forEach(predicates::add);
whereValue
.getObjectFields()
.stream()
.filter(it -> !Logical.names().contains(it.getName()))
.map(it -> {
Map args = getFieldArguments(environment, it, argument);
Argument arg = new Argument(it.getName(), it.getValue());
return getObjectFieldPredicate(environment, cb, from, logical, it, arg, args);
})
.filter(predicate -> predicate != null)
.forEach(predicates::add);
return getCompoundPredicate(cb, predicates, logical);
}
protected Predicate getObjectFieldPredicate(
DataFetchingEnvironment environment,
CriteriaBuilder cb,
From, ?> from,
Logical logical,
ObjectField objectField,
Argument argument,
Map arguments
) {
if (isEntityType(environment)) {
Attribute, ?> attribute = getAttribute(environment, argument.getName());
// TODO add support for embedded element collection, i.e. attribute.isCollection()
if (attribute.isAssociation()) {
GraphQLFieldDefinition fieldDefinition = getFieldDefinition(
environment.getGraphQLSchema(),
this.getObjectType(environment),
new Field(objectField.getName())
);
List> logicalArguments = Optional
.ofNullable(environment.getArgument(logical.name()))
.filter(List.class::isInstance)
.map(List.class::cast)
.orElseGet(List::of);
final var isMultipleLogical =
logicalArguments.stream().filter(it -> it.containsKey(objectField.getName())).count() >= 1;
if (Arrays.asList(Logical.EXISTS, Logical.NOT_EXISTS).contains(logical) || isMultipleLogical) {
AbstractQuery> query = environment.getRoot();
Subquery> subquery = query.subquery(attribute.getJavaType());
From, ?> correlation = Root.class.isInstance(from)
? subquery.correlate((Root>) from)
: subquery.correlate((Join, ?>) from);
Join, ?> correlationJoin = correlation.join(objectField.getName());
DataFetchingEnvironment existsEnvironment = DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.root(subquery)
.build();
Predicate restriction = getArgumentPredicate(
cb,
correlationJoin,
wherePredicateEnvironment(existsEnvironment, fieldDefinition, arguments),
argument
);
Predicate exists = cb.exists(subquery.select((Join) correlationJoin).where(restriction));
return logical == Logical.NOT_EXISTS ? cb.not(exists) : exists;
}
final AbstractQuery> query = environment.getRoot();
final Boolean isFetch = environment.getLocalContext();
final boolean isOptional = isOptionalAttribute(attribute);
final From, ?> context = (isSubquery(query) || isCountQuery(query) || !isFetch)
? reuseJoin(from, objectField.getName(), isOptional)
: reuseFetch(from, objectField.getName(), isOptional);
return getArgumentPredicate(
cb,
context,
wherePredicateEnvironment(environment, fieldDefinition, arguments),
argument
);
}
}
return getLogicalPredicate(
objectField.getName(),
cb,
from,
objectField,
argumentEnvironment(environment, arguments),
argument
);
}
protected boolean isSubquery(AbstractQuery> query) {
return Subquery.class.isInstance(query);
}
protected boolean isCountQuery(AbstractQuery> query) {
return Optional
.ofNullable(query.getSelection())
.map(Selection::getJavaType)
.map(Long.class::equals)
.orElse(false);
}
protected Predicate getArgumentsPredicate(
CriteriaBuilder cb,
From, ?> from,
DataFetchingEnvironment environment,
Argument argument
) {
ArrayValue whereValue = getValue(argument, environment);
if (whereValue.getValues().isEmpty()) return cb.disjunction();
Logical logical = extractLogical(argument);
List predicates = new ArrayList<>();
List> arguments = environment.getArgument(logical.name());
List values = whereValue
.getValues()
.stream()
.map(ObjectValue.class::cast)
.collect(Collectors.toList());
List>> tuples = IntStream
.range(0, values.size())
.mapToObj(i -> new SimpleEntry>(values.get(i), arguments.get(i)))
.collect(Collectors.toList());
tuples
.stream()
.flatMap(e ->
e
.getKey()
.getObjectFields()
.stream()
.filter(it -> Logical.names().contains(it.getName()))
.map(it -> {
Map args = e.getValue();
Argument arg = new Argument(it.getName(), it.getValue());
if (ArrayValue.class.isInstance(it.getValue())) {
return getArgumentsPredicate(cb, from, argumentEnvironment(environment, args), arg);
}
return getArgumentPredicate(cb, from, argumentEnvironment(environment, args), arg);
})
)
.forEach(predicates::add);
tuples
.stream()
.flatMap(e ->
e
.getKey()
.getObjectFields()
.stream()
.filter(it -> !Logical.names().contains(it.getName()))
.map(it -> {
Map args = e.getValue();
Argument arg = new Argument(it.getName(), it.getValue());
return getObjectFieldPredicate(environment, cb, from, logical, it, arg, args);
})
)
.filter(predicate -> predicate != null)
.forEach(predicates::add);
return getCompoundPredicate(cb, predicates, logical);
}
private Map getFieldArguments(
DataFetchingEnvironment environment,
ObjectField field,
Argument argument
) {
Map arguments;
if (environment.getArgument(argument.getName()) instanceof Collection) {
Collection> list = environment.getArgument(argument.getName());
arguments =
list
.stream()
.filter(args -> args.get(field.getName()) != null)
.findFirst()
.orElse(list.stream().findFirst().get());
} else {
arguments = environment.getArgument(argument.getName());
}
return arguments;
}
private Logical extractLogical(Argument argument) {
return Optional
.of(argument.getName())
.filter(it -> Logical.names().contains(it))
.map(it -> Logical.valueOf(it))
.orElse(Logical.AND);
}
private Predicate getLogicalPredicates(
String fieldName,
CriteriaBuilder cb,
From, ?> path,
ObjectField objectField,
DataFetchingEnvironment environment,
Argument argument
) {
ArrayValue value = ArrayValue.class.cast(objectField.getValue());
Logical logical = extractLogical(argument);
List predicates = new ArrayList<>();
value
.getValues()
.stream()
.map(ObjectValue.class::cast)
.flatMap(it -> it.getObjectFields().stream())
.map(it -> {
Map args = getFieldArguments(environment, it, argument);
Argument arg = new Argument(it.getName(), it.getValue());
return getLogicalPredicate(it.getName(), cb, path, it, argumentEnvironment(environment, args), arg);
})
.forEach(predicates::add);
return getCompoundPredicate(cb, predicates, logical);
}
private Predicate getLogicalPredicate(
String fieldName,
CriteriaBuilder cb,
From, ?> path,
ObjectField objectField,
DataFetchingEnvironment environment,
Argument argument
) {
ObjectValue expressionValue;
if (objectField.getValue() instanceof ObjectValue) expressionValue =
(ObjectValue) objectField.getValue(); else expressionValue = new ObjectValue(Arrays.asList(objectField));
if (expressionValue.getChildren().isEmpty()) return cb.disjunction();
Logical logical = extractLogical(argument);
List predicates = new ArrayList<>();
// Let's parse logical expressions, i.e. AND, OR
expressionValue
.getObjectFields()
.stream()
.filter(it -> Logical.names().contains(it.getName()))
.map(it -> {
Map args = getFieldArguments(environment, it, argument);
Argument arg = new Argument(it.getName(), it.getValue());
if (it.getValue() instanceof ArrayValue) {
return getLogicalPredicates(fieldName, cb, path, it, argumentEnvironment(environment, args), arg);
}
return getLogicalPredicate(fieldName, cb, path, it, argumentEnvironment(environment, args), arg);
})
.forEach(predicates::add);
// Let's parse relation criteria expressions if present, i.e. books, author, etc.
if (
expressionValue
.getObjectFields()
.stream()
.anyMatch(it -> !Logical.names().contains(it.getName()) && !Criteria.names().contains(it.getName()))
) {
GraphQLFieldDefinition fieldDefinition = getFieldDefinition(
environment.getGraphQLSchema(),
this.getObjectType(environment),
new Field(fieldName)
);
Map args = new LinkedHashMap<>();
Argument arg = new Argument(logical.name(), expressionValue);
boolean isOptional = false;
if (Logical.names().contains(argument.getName())) {
args.put(logical.name(), environment.getArgument(argument.getName()));
} else {
args.put(logical.name(), environment.getArgument(fieldName));
isOptional = isOptionalAttribute(getAttribute(environment, argument.getName()));
}
return getArgumentPredicate(
cb,
reuseJoin(path, fieldName, isOptional),
wherePredicateEnvironment(environment, fieldDefinition, args),
arg
);
}
// Let's parse simple Criteria expressions, i.e. EQ, LIKE, etc.
JpaPredicateBuilder pb = new JpaPredicateBuilder(cb);
expressionValue
.getObjectFields()
.stream()
.filter(it -> Criteria.names().contains(it.getName()))
.map(it ->
getPredicateFilter(
new ObjectField(fieldName, it.getValue()),
argumentEnvironment(environment, argument),
new Argument(it.getName(), it.getValue())
)
)
.sorted()
.map(it -> pb.getPredicate(path, path.get(it.getField()), it))
.filter(predicate -> predicate != null)
.forEach(predicates::add);
return getCompoundPredicate(cb, predicates, logical);
}
private Predicate getCompoundPredicate(CriteriaBuilder cb, List predicates, Logical logical) {
if (predicates.isEmpty()) return cb.disjunction();
if (predicates.size() == 1) {
if (logical == Logical.NOT) {
return cb.not(predicates.get(0));
}
return predicates.get(0);
}
switch (logical) {
case OR:
return cb.or(predicates.toArray(new Predicate[0]));
case AND:
case EXISTS:
case NOT_EXISTS:
return cb.and(predicates.toArray(new Predicate[0]));
case NOT:
throw new RuntimeException("NOT expression cannot be applied to multiple predicates at once");
default:
throw new RuntimeException(
"Unable to resolve applicable compound predicate for logical operand " + logical
);
}
}
private PredicateFilter getPredicateFilter(
ObjectField objectField,
DataFetchingEnvironment environment,
Argument argument
) {
EnumSet options = EnumSet.of(PredicateFilter.Criteria.valueOf(argument.getName()));
Map valueArguments = new LinkedHashMap();
valueArguments.put(objectField.getName(), environment.getArgument(argument.getName()));
DataFetchingEnvironment dataFetchingEnvironment = DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.arguments(valueArguments)
.build();
Argument dataFetchingArgument = new Argument(objectField.getName(), argument.getValue());
Object filterValue = convertValue(dataFetchingEnvironment, dataFetchingArgument, argument.getValue());
Attribute attribute = getAttribute(environment, objectField.getName());
return new PredicateFilter(objectField.getName(), filterValue, options, attribute);
}
protected DataFetchingEnvironment argumentEnvironment(
DataFetchingEnvironment environment,
Map arguments
) {
return DataFetchingEnvironmentBuilder.newDataFetchingEnvironment(environment).arguments(arguments).build();
}
protected DataFetchingEnvironment argumentEnvironment(DataFetchingEnvironment environment, Argument argument) {
Map arguments = environment.getArgument(argument.getName());
return DataFetchingEnvironmentBuilder.newDataFetchingEnvironment(environment).arguments(arguments).build();
}
protected DataFetchingEnvironment wherePredicateEnvironment(
DataFetchingEnvironment environment,
GraphQLFieldDefinition fieldDefinition,
Map arguments
) {
return DataFetchingEnvironmentBuilder
.newDataFetchingEnvironment(environment)
.arguments(arguments)
.fieldDefinition(fieldDefinition)
.fieldType(fieldDefinition.getType())
.build();
}
/**
* @param fieldName
* @return Path of compound field to the primitive type
*/
private From, ?> getCompoundJoin(From, ?> rootPath, String fieldName, boolean outer) {
String[] compoundField = fieldName.split("\\.");
From, ?> join;
if (compoundField.length == 1) {
return rootPath;
} else {
join = reuseJoin(rootPath, compoundField[0], outer);
}
for (int i = 1; i < compoundField.length; i++) {
if (i < (compoundField.length - 1)) {
join = reuseJoin(join, compoundField[i], outer);
} else {
return join;
}
}
return null;
}
/**
* @param fieldName
* @return Path of compound field to the primitive type
*/
private Path> getCompoundJoinedPath(From, ?> rootPath, String fieldName, boolean outer) {
String[] compoundField = fieldName.split("\\.");
From, ?> join;
if (compoundField.length == 1) {
return rootPath.get(compoundField[0]);
} else {
join = reuseJoin(rootPath, compoundField[0], outer);
}
for (int i = 1; i < compoundField.length; i++) {
if (i < (compoundField.length - 1)) {
join = reuseJoin(join, compoundField[i], outer);
} else {
return join.get(compoundField[i]);
}
}
return null;
}
// trying to find already existing joins to reuse
private From, ?> reuseJoin(From, ?> from, String fieldName, boolean outer) {
for (Join, ?> join : from.getJoins()) {
if (join.getAttribute().getName().equals(fieldName)) {
return join;
}
}
return outer ? from.join(fieldName, JoinType.LEFT) : from.join(fieldName);
}
// trying to find already existing fetch joins to reuse
private From, ?> reuseFetch(From, ?> from, String fieldName, boolean outer) {
for (Fetch, ?> fetch : from.getFetches()) {
if (fetch.getAttribute().getName().equals(fieldName)) {
return (From, ?>) fetch;
}
}
return outer ? (From, ?>) from.fetch(fieldName, JoinType.LEFT) : (From, ?>) from.fetch(fieldName);
}
@SuppressWarnings({ "unchecked", "rawtypes" })
protected Object convertValue(DataFetchingEnvironment environment, Argument argument, Value value) {
if (value instanceof NullValue) {
return value;
} else if (value instanceof StringValue) {
Object convertedValue = environment.getArgument(argument.getName());
if (convertedValue != null) {
Class javaType = getJavaType(environment, argument);
// Return real typed resolved value even if the Value is a StringValue
return javaType.isInstance(convertedValue)
? convertedValue
: JavaScalars.of(javaType).getCoercing().parseValue(convertedValue);
} else {
// Return provided StringValue
return ((StringValue) value).getValue();
}
} else if (value instanceof VariableReference variableReference) {
Class javaType = getJavaType(environment, argument);
Object argumentValue = environment.getVariables().get(variableReference.getName());
if (javaType.isEnum()) {
if (argumentValue instanceof Collection argumentValues) {
List values = new ArrayList<>();
argumentValues.forEach(it -> values.add(Enum.valueOf(javaType, it.toString())));
return values;
} else {
return Enum.valueOf(javaType, argumentValue.toString());
}
} else {
Coercing, ?> coercing = JavaScalars.of(javaType).getCoercing();
Function valueConverter = it -> javaType.isInstance(it) ? it : coercing.parseValue(it);
if (argumentValue instanceof Collection> argumentValues) {
return argumentValues.stream().map(valueConverter).toList();
} else {
// Get resolved variable in environment arguments
return valueConverter.apply(argumentValue);
}
}
} else if (value instanceof ArrayValue) {
Collection arrayValue = environment.getArgument(argument.getName());
if (arrayValue != null) {
// Let's unwrap array of array values
if (arrayValue.stream().allMatch(it -> it instanceof Collection)) {
arrayValue = Collection.class.cast(arrayValue.iterator().next());
}
// Let's convert enum types, i.e. array of strings or EnumValue into Java type
if (getJavaType(environment, argument).isEnum()) {
Function objectValue = obj ->
Value.class.isInstance(obj) ? Value.class.cast(obj) : new EnumValue(obj.toString());
// Return real typed resolved array values converted into Java enums
return arrayValue
.stream()
.map(it -> convertValue(environment, argument, objectValue.apply(it)))
.collect(Collectors.toList());
}
// Let's try handle Ast Value types
else if (arrayValue.stream().anyMatch(it -> it instanceof Value)) {
return arrayValue
.stream()
.map(it -> convertValue(environment, argument, Value.class.cast(it)))
.collect(Collectors.toList());
}
// Return real typed resolved array value, i.e. Date, UUID, Long
else {
return arrayValue;
}
} else {
// Wrap converted values in ArrayList
return ((ArrayValue) value).getValues()
.stream()
.map(it -> convertValue(environment, argument, it))
.collect(Collectors.toList());
}
} else if (value instanceof EnumValue) {
Class enumType = getJavaType(environment, argument);
return Enum.valueOf(enumType, ((EnumValue) value).getName());
} else if (value instanceof IntValue) {
return ((IntValue) value).getValue();
} else if (value instanceof BooleanValue) {
return ((BooleanValue) value).isValue();
} else if (value instanceof FloatValue) {
return ((FloatValue) value).getValue();
} else if (value instanceof ObjectValue) {
Class javaType = getJavaType(environment, argument);
Map values = environment.getArgument(argument.getName());
try {
return getJavaBeanValue(javaType, values);
} catch (Exception cause) {
throw new RuntimeException(cause);
}
}
return value;
}
private Object getJavaBeanValue(Class> javaType, Map values) throws Exception {
Constructor> constructor = javaType.getConstructor();
constructor.setAccessible(true);
Object javaBean = constructor.newInstance();
values
.entrySet()
.stream()
.forEach(entry -> {
setPropertyValue(javaBean, entry.getKey(), entry.getValue());
});
return javaBean;
}
private void setPropertyValue(Object javaBean, String propertyName, Object propertyValue) {
try {
BeanInfo bi = Introspector.getBeanInfo(javaBean.getClass());
PropertyDescriptor pds[] = bi.getPropertyDescriptors();
for (PropertyDescriptor pd : pds) {
if (pd.getName().equals(propertyName)) {
Method setter = pd.getWriteMethod();
setter.setAccessible(true);
if (setter != null) {
setter.invoke(javaBean, new Object[] { propertyValue });
}
}
}
} catch (Exception ignored) {
// ignore
}
}
/**
* Resolve Java type from associated query argument JPA model attribute
*
* @param environment
* @param argument
* @return Java class type
*/
protected Class> getJavaType(DataFetchingEnvironment environment, Argument argument) {
Attribute, ?> argumentEntityAttribute = getAttribute(environment, argument.getName());
if (argumentEntityAttribute instanceof PluralAttribute) return (
(PluralAttribute, ?, ?>) argumentEntityAttribute
).getElementType()
.getJavaType();
return argumentEntityAttribute.getJavaType();
}
/**
* Resolve JPA model persistent attribute from query argument instance
*
* @param environment
* @param argument
* @return JPA model attribute
*/
private Attribute, ?> getAttribute(DataFetchingEnvironment environment, String argument) {
GraphQLObjectType objectType = getObjectType(environment);
if (!isEntityType(objectType)) {
return getEmbeddableType(objectType).getAttribute(argument);
}
return getEntityType(objectType).getAttribute(argument);
}
private boolean isOptionalAttribute(Attribute, ?> attribute) {
if (SingularAttribute.class.isInstance(attribute)) {
return SingularAttribute.class.cast(attribute).isOptional();
} else if (PluralAttribute.class.isInstance(attribute)) {
return true;
}
return false;
}
/**
* Resolve JPA model entity type from GraphQL objectType
*
* @param objectType
* @return JPA model entity type or null if there is no match.
*/
private EntityType> getEntityType(GraphQLObjectType objectType) {
return entityTypeMap.computeIfAbsent(objectType, this::computeEntityType);
}
private boolean isEntityType(DataFetchingEnvironment environment) {
GraphQLObjectType objectType = getObjectType(environment);
return isEntityType(objectType);
}
private boolean isEntityType(GraphQLObjectType objectType) {
return getEntityType(objectType) != null;
}
private EntityType> computeEntityType(GraphQLObjectType objectType) {
return graphQLObjectTypeMetadata.entity(objectType.getName());
}
private EmbeddableType> getEmbeddableType(GraphQLObjectType objectType) {
return embeddableTypeMap.computeIfAbsent(objectType, this::computeEmbeddableType);
}
private EmbeddableType> computeEmbeddableType(GraphQLObjectType objectType) {
return graphQLObjectTypeMetadata.embeddable(objectType.getName());
}
/**
* Resolve GraphQL object type from Argument output type.
*
* @param environment
* @return resolved GraphQL object type or null if no output type is provided
*/
private GraphQLObjectType getObjectType(DataFetchingEnvironment environment) {
return getObjectType(environment.getFieldType());
}
private GraphQLObjectType getObjectType(GraphQLType outputType) {
if (outputType instanceof GraphQLList) outputType = ((GraphQLList) outputType).getWrappedType();
if (outputType instanceof GraphQLObjectType) return (GraphQLObjectType) outputType;
return null;
}
protected Optional extractArgument(Field field, String argumentName) {
return field.getArguments().stream().filter(it -> argumentName.equals(it.getName())).findFirst();
}
protected Argument extractArgument(Field field, String argumentName, Value defaultValue) {
return extractArgument(field, argumentName).orElse(new Argument(argumentName, defaultValue));
}
protected GraphQLFieldDefinition getFieldDefinition(
GraphQLSchema schema,
GraphQLObjectType parentType,
Field field
) {
if (schema.getQueryType() == parentType) {
if (field.getName().equals(SchemaMetaFieldDef.getName())) {
return SchemaMetaFieldDef;
}
if (field.getName().equals(TypeMetaFieldDef.getName())) {
return TypeMetaFieldDef;
}
}
if (field.getName().equals(TypeNameMetaFieldDef.getName())) {
return TypeNameMetaFieldDef;
}
GraphQLFieldDefinition fieldDefinition = parentType.getFieldDefinition(field.getName());
if (fieldDefinition != null) {
return fieldDefinition;
}
throw new GraphQLException("unknown field " + field.getName());
}
protected boolean hasSelectionSet(Field field) {
return field.getSelectionSet() != null;
}
@SuppressWarnings("unchecked")
protected T getArgumentValue(DataFetchingEnvironment environment, Argument argument, Class type) {
Value> value = argument.getValue();
if (VariableReference.class.isInstance(value)) {
return (T) environment.getVariables().get(VariableReference.class.cast(value).getName());
} else if (BooleanValue.class.isInstance(value)) {
return (T) Boolean.valueOf(BooleanValue.class.cast(value).isValue());
} else if (IntValue.class.isInstance(value)) {
return (T) IntValue.class.cast(value).getValue();
} else if (StringValue.class.isInstance(value)) {
return (T) StringValue.class.cast(value).getValue();
} else if (FloatValue.class.isInstance(value)) {
return (T) FloatValue.class.cast(value).getValue();
} else if (NullValue.class.isInstance(value)) {
return null;
}
throw new IllegalArgumentException("Not supported");
}
protected boolean isPersistent(DataFetchingEnvironment environment, String attributeName) {
GraphQLObjectType objectType = getObjectType(environment);
EntityType> entityType = getEntityType(objectType);
return isPersistent(entityType, attributeName);
}
protected boolean isPersistent(EntityType> entityType, String attributeName) {
try {
return entityType.getAttribute(attributeName) != null;
} catch (Exception ignored) {}
return false;
}
protected String getJPQLQueryString(TypedQuery> query) {
try {
Method getQueryString = ReflectionUtil.getMethod(query.getClass(), "getQueryString");
if (getQueryString != null) {
return getQueryString.invoke(query).toString();
}
} catch (Exception ignored) {
logger.error("Error getting JPQL string", ignored);
}
return null;
}
protected boolean hasIdAttribute() {
return entityType.getIdType() != null;
}
protected String idAttributeName() {
return entityType.getId(entityType.getIdType().getJavaType()).getName();
}
protected boolean hasIdClassAttribue() {
return entityType.getIdClassAttributes() != null;
}
protected String[] idClassAttributeNames() {
return entityType
.getIdClassAttributes()
.stream()
.map(SingularAttribute::getName)
.sorted()
.collect(Collectors.toList())
.toArray(new String[0]);
}
protected T getParentIdAttributeValue(T entity) {
SingularAttribute, Object> parentIdAttribute = entityType.getId(Object.class);
return (T) getAttributeValue(entity, parentIdAttribute);
}
/**
* Fetches the value of the given SingularAttribute on the given
* entity.
*
* http://stackoverflow.com/questions/7077464/how-to-get-singularattribute-mapped-value-of-a-persistent-object
*/
@SuppressWarnings("unchecked")
protected T getAttributeValue(T entity, SingularAttribute field) {
try {
Member member = field.getJavaMember();
if (member instanceof Method) {
// this should be a getter method:
return (T) ((Method) member).invoke(entity);
} else if (member instanceof java.lang.reflect.Field) {
return (T) ((java.lang.reflect.Field) member).get(entity);
} else {
throw new IllegalArgumentException(
"Unexpected java member type. Expecting method or field, found: " + member
);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
/**
* Fetches the value of the given SingularAttribute on the given
* entity.
*
* http://stackoverflow.com/questions/7077464/how-to-get-singularattribute-mapped-value-of-a-persistent-object
*/
@SuppressWarnings("unchecked")
protected FieldType getAttributeValue(
EntityType entity,
PluralAttribute field
) {
try {
Member member = field.getJavaMember();
if (member instanceof Method) {
// this should be a getter method:
return (FieldType) ((Method) member).invoke(entity);
} else if (member instanceof java.lang.reflect.Field) {
return (FieldType) ((java.lang.reflect.Field) member).get(entity);
} else {
throw new IllegalArgumentException(
"Unexpected java member type. Expecting method or field, found: " + member
);
}
} catch (Exception e) {
throw new RuntimeException(e);
}
}
protected boolean resolveDistinctArgument(Field field) {
Argument distinctArg = extractArgument(field, SELECT_DISTINCT_PARAM_NAME, new BooleanValue(defaultDistinct));
return BooleanValue.class.cast(distinctArg.getValue()).isValue();
}
public boolean isDefaultDistinct() {
return defaultDistinct;
}
public String getSelectNodeName() {
return selectNodeName;
}
public MergedField resolveQueryField(Field rootNode) {
Optional recordsSelection = GraphQLSupport.searchByFieldName(rootNode, getSelectNodeName());
Field queryField = recordsSelection
.map(selectNode ->
Field
.newField(selectNode.getName())
.selectionSet(selectNode.getSelectionSet())
.arguments(rootNode.getArguments())
.directives(selectNode.getDirectives())
.build()
)
.orElse(rootNode);
return MergedField.newMergedField(queryField).build();
}
public GraphQLObjectType getEntityObjectType() {
return entityObjectType;
}
public int getDefaultFetchSize() {
return defaultFetchSize;
}
private MergedField flattenEmbeddedIdArguments(Field field) {
// manage object arguments (EmbeddedId)
final List argumentsWhereObjectsAreFlattened = field
.getArguments()
.stream()
.flatMap(argument -> {
if (
!isWhereArgument(argument) &&
!isPageArgument(argument) &&
argument.getValue() instanceof ObjectValue
) {
return ((ObjectValue) argument.getValue()).getObjectFields()
.stream()
.map(objectField ->
new Argument(argument.getName() + "." + objectField.getName(), objectField.getValue())
);
} else {
return Stream.of(argument);
}
})
.collect(Collectors.toList());
return MergedField
.newMergedField(field.transform(builder -> builder.arguments(argumentsWhereObjectsAreFlattened)))
.build();
}
protected boolean hasAnySelectionOrderBy(Field field) {
if (!hasSelectionSet(field)) return false;
// Loop through all of the fields being requested
return field
.getSelectionSet()
.getSelections()
.stream()
.filter(Field.class::isInstance)
.map(Field.class::cast)
.anyMatch(selectedField -> {
// Optional orderBy argument
Optional orderBy = selectedField
.getArguments()
.stream()
.filter(this::isOrderByArgument)
.findFirst();
if (orderBy.isPresent()) {
return true;
}
return false;
});
}
private T unproxy(T entityProxy) {
return (T) unproxy.apply(entityProxy);
}
private T unproxyAndThenDetach(T entityProxy) {
return (T) unproxy.andThen(this::detach).apply(entityProxy);
}
private T detach(T entity) {
entityManager.detach(entity);
return entity;
}
EntityGraph> createEntityGraph(DataFetchingEnvironment environment) {
Field root = environment.getMergedField().getSingleField();
GraphQLObjectType fieldType = getObjectType(environment);
EntityType> entityType = getEntityType(fieldType);
EntityGraph> entityGraph = entityManager.createEntityGraph(entityType.getJavaType());
var entityDescriptor = EntityIntrospector.introspect(entityType);
selections(root)
.forEach(selectedField -> {
var propertyDescriptor = entityDescriptor.getPropertyDescriptor(selectedField.getName());
propertyDescriptor
.flatMap(AttributePropertyDescriptor::getAttribute)
.ifPresent(attribute -> {
if (
isManagedType(attribute) && hasSelectionSet(selectedField) && hasNoArguments(selectedField)
) {
var attributeFieldDefinition = fieldType.getFieldDefinition(attribute.getName());
entityGraph.addAttributeNodes(attribute.getName());
addSubgraph(
selectedField,
attributeFieldDefinition,
entityGraph.addSubgraph(attribute.getName())
);
} else if (isBasic(attribute)) {
entityGraph.addAttributeNodes(attribute.getName());
}
});
});
return entityGraph;
}
void addSubgraph(Field field, GraphQLFieldDefinition fieldDefinition, Subgraph> subgraph) {
var fieldObjectType = getObjectType(fieldDefinition.getType());
var fieldEntityType = getEntityType(fieldObjectType);
var fieldEntityDescriptor = EntityIntrospector.introspect(fieldEntityType);
selections(field)
.forEach(selectedField -> {
var propertyDescriptor = fieldEntityDescriptor.getPropertyDescriptor(selectedField.getName());
propertyDescriptor
.flatMap(AttributePropertyDescriptor::getAttribute)
.ifPresent(attribute -> {
var selectedName = selectedField.getName();
if (
hasSelectionSet(selectedField) && isManagedType(attribute) && hasNoArguments(selectedField)
) {
var selectedFieldDefinition = fieldObjectType.getFieldDefinition(selectedName);
subgraph.addAttributeNodes(selectedName);
addSubgraph(selectedField, selectedFieldDefinition, subgraph.addSubgraph(selectedName));
} else if (isBasic(attribute)) {
subgraph.addAttributeNodes(selectedName);
}
});
});
}
static boolean isManagedType(Attribute, ?> attribute) {
return (
attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.EMBEDDED &&
attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.BASIC &&
attribute.getPersistentAttributeType() != Attribute.PersistentAttributeType.ELEMENT_COLLECTION
);
}
static boolean isBasic(Attribute, ?> attribute) {
return !isManagedType(attribute);
}
static boolean hasNoArguments(Field field) {
return !hasArguments(field);
}
static boolean hasArguments(Field field) {
return field.getArguments() != null && !field.getArguments().isEmpty();
}
/**
* Creates builder to build {@link GraphQLJpaQueryFactory}.
* @return created builder
*/
public static IEntityManagerStage builder() {
return new Builder();
}
/**
* Definition of a stage for staged builder.
*/
public interface IEntityManagerStage {
/**
* Builder method for entityManager parameter.
* @param entityManager field to set
* @return builder
*/
public IEntityTypeStage withEntityManager(EntityManager entityManager);
}
/**
* Definition of a stage for staged builder.
*/
public interface IEntityTypeStage {
/**
* Builder method for entityType parameter.
* @param entityType field to set
* @return builder
*/
public IGraphQLObjectTypeMetadataStage withEntityType(EntityType> entityType);
}
public interface IGraphQLObjectTypeMetadataStage {
/**
* Builder method for graphQLObjectTypeMetadata parameter.
* @param graphQLObjectTypeMetadata metadata
* @return builder
*/
public IEntityObjectTypeStage withGraphQLObjectTypeMetadata(
GraphQLObjectTypeMetadata graphQLObjectTypeMetadata
);
}
/**
* Definition of a stage for staged builder.
*/
public interface IEntityObjectTypeStage {
/**
* Builder method for entityObjectType parameter.
* @param entityObjectType field to set
* @return builder
*/
public ISelectNodeNameStage withEntityObjectType(GraphQLObjectType entityObjectType);
}
/**
* Definition of a stage for staged builder.
*/
public interface ISelectNodeNameStage {
/**
* Builder method for selectNodeName parameter.
* @param selectNodeName field to set
* @return builder
*/
public IBuildStage withSelectNodeName(String selectNodeName);
}
/**
* Definition of a stage for staged builder.
*/
public interface IBuildStage {
/**
* Builder method for toManyDefaultOptional parameter.
* @param toManyDefaultOptional field to set
* @return builder
*/
IBuildStage withToManyDefaultOptional(boolean toManyDefaultOptional);
/**
* Builder method for defaultDistinct parameter.
* @param defaultDistinct field to set
* @return builder
*/
IBuildStage withDefaultDistinct(boolean defaultDistinct);
/**
* Builder method for resultStream parameter.
* @param resultStream field to set
* @return builder
*/
IBuildStage withResultStream(boolean resultStream);
/**
* Builder method for defaultFetchSize parameter.
* @param defaultFetchSize field to set
* @return builder
*/
IBuildStage withDefaultFetchSize(int defaultFetchSize);
/**
* Builder method for restrictedKeysProvider parameter.
* @param restrictedKeysProvider field to set
* @return builder
*/
IBuildStage withRestrictedKeysProvider(RestrictedKeysProvider restrictedKeysProvider);
/**
* Builder method of the builder.
* @return built class
*/
GraphQLJpaQueryFactory build();
}
/**
* Builder to build {@link GraphQLJpaQueryFactory}.
*/
public static final class Builder
implements
IEntityManagerStage,
IEntityTypeStage,
IGraphQLObjectTypeMetadataStage,
IEntityObjectTypeStage,
ISelectNodeNameStage,
IBuildStage {
private RestrictedKeysProvider restrictedKeysProvider;
private EntityManager entityManager;
private EntityType> entityType;
private GraphQLObjectType entityObjectType;
private String selectNodeName;
private boolean toManyDefaultOptional = true;
private boolean defaultDistinct = true;
private int defaultFetchSize = 100;
private boolean resultStream = false;
private GraphQLObjectTypeMetadata graphQLObjectTypeMetadata;
private Builder() {}
@Override
public IEntityTypeStage withEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
return this;
}
@Override
public IGraphQLObjectTypeMetadataStage withEntityType(EntityType> entityType) {
this.entityType = entityType;
return this;
}
@Override
public IEntityObjectTypeStage withGraphQLObjectTypeMetadata(
GraphQLObjectTypeMetadata graphQLObjectTypeMetadata
) {
this.graphQLObjectTypeMetadata = graphQLObjectTypeMetadata;
return this;
}
@Override
public ISelectNodeNameStage withEntityObjectType(GraphQLObjectType entityObjectType) {
this.entityObjectType = entityObjectType;
return this;
}
@Override
public IBuildStage withSelectNodeName(String selectNodeName) {
this.selectNodeName = selectNodeName;
return this;
}
@Override
public IBuildStage withToManyDefaultOptional(boolean toManyDefaultOptional) {
this.toManyDefaultOptional = toManyDefaultOptional;
return this;
}
@Override
public IBuildStage withDefaultDistinct(boolean defaultDistinct) {
this.defaultDistinct = defaultDistinct;
return this;
}
@Override
public IBuildStage withResultStream(boolean resultStream) {
this.resultStream = resultStream;
return this;
}
@Override
public IBuildStage withDefaultFetchSize(int defaultFetchSize) {
this.defaultFetchSize = defaultFetchSize;
return this;
}
@Override
public IBuildStage withRestrictedKeysProvider(RestrictedKeysProvider restrictedKeysProvider) {
this.restrictedKeysProvider = restrictedKeysProvider;
return this;
}
@Override
public GraphQLJpaQueryFactory build() {
Objects.requireNonNull(restrictedKeysProvider, "restrictedKeysProvider must not be null");
return new GraphQLJpaQueryFactory(this);
}
}
public RestrictedKeysProvider getRestrictedKeysProvider() {
return restrictedKeysProvider;
}
}