Created
September 22, 2020 08:58
-
-
Save negesti/7a74b063a0d17d87bdebbb559da9c9c8 to your computer and use it in GitHub Desktop.
graphql-jpa-query mutation
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// Schema builder based on the existing GraphQLJpaSchemBuilder --> A lot of private method mus be copied :( | |
package your.fancy.package; | |
import static graphql.Scalars.GraphQLBoolean; | |
import java.lang.reflect.AnnotatedElement; | |
import java.lang.reflect.Field; | |
import java.lang.reflect.Member; | |
import java.util.ArrayList; | |
import java.util.Date; | |
import java.util.HashMap; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.stream.Collectors; | |
import javax.persistence.EntityManager; | |
import javax.persistence.metamodel.Attribute; | |
import javax.persistence.metamodel.EmbeddableType; | |
import javax.persistence.metamodel.EntityType; | |
import javax.persistence.metamodel.ManagedType; | |
import javax.persistence.metamodel.PluralAttribute; | |
import javax.persistence.metamodel.SingularAttribute; | |
import javax.persistence.metamodel.Type; | |
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore; | |
import com.introproventures.graphql.jpa.query.schema.JavaScalars; | |
import com.introproventures.graphql.jpa.query.schema.impl.EntityIntrospector; | |
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder; | |
import graphql.schema.Coercing; | |
import graphql.schema.DataFetcher; | |
import graphql.schema.FieldCoordinates; | |
import graphql.schema.GraphQLArgument; | |
import graphql.schema.GraphQLCodeRegistry; | |
import graphql.schema.GraphQLEnumType; | |
import graphql.schema.GraphQLFieldDefinition; | |
import graphql.schema.GraphQLInputObjectField; | |
import graphql.schema.GraphQLInputObjectType; | |
import graphql.schema.GraphQLInputType; | |
import graphql.schema.GraphQLList; | |
import graphql.schema.GraphQLObjectType; | |
import graphql.schema.GraphQLOutputType; | |
import graphql.schema.GraphQLSchema; | |
import graphql.schema.GraphQLType; | |
import graphql.schema.GraphQLTypeReference; | |
import org.apache.commons.lang3.StringUtils; | |
import org.slf4j.Logger; | |
import org.slf4j.LoggerFactory; | |
import your.fancy.package.mutation.MyAbstractMutation; | |
public class MutationGraphQLSchemaBuilder extends GraphQLJpaSchemaBuilder { | |
private static final Logger LOGGER = LoggerFactory.getLogger(MutationGraphQLSchemaBuilder.class); | |
private static final String MUTATION_NAME = "Mutation"; | |
private Map<Class<?>, GraphQLOutputType> classCache = new HashMap<>(); | |
private Map<Class<?>, GraphQLInputObjectType> embeddableInputCache = new HashMap<>(); | |
private Map<ManagedType<?>, GraphQLInputObjectType> inputTypeCache = new HashMap<>(); | |
private Map<Attribute, GraphQLInputObjectField> assocationTypeCache = new HashMap<>(); | |
private Map<String, GraphQLInputObjectType> idInputTypeCache = new HashMap<>(); | |
private Map<String, GraphQLFieldDefinition> baseFieldDefinitionsCache = new HashMap<>(); | |
private GraphQLSchema baseSchema; | |
private GraphQLCodeRegistry.Builder codeRegistry; | |
private List<GraphQLFieldDefinition> mutations = new ArrayList<>(); | |
public MutationGraphQLSchemaBuilder(EntityManager entityManager) { | |
super(entityManager); | |
super.defaultMaxResults(999); | |
super.defaultPageLimitSize(999); | |
} | |
@Override | |
public GraphQLSchema build() { | |
throw new RuntimeException("If you call this build method, you don't understand what this class is doing"); | |
} | |
public void buildBaseSchema() { | |
this.baseSchema = super.build(); | |
this.codeRegistry = GraphQLCodeRegistry.newCodeRegistry(baseSchema.getCodeRegistry()); | |
for (GraphQLFieldDefinition fieldDefinition : baseSchema.getQueryType().getFieldDefinitions()) { | |
baseFieldDefinitionsCache.put(fieldDefinition.getName(), fieldDefinition); | |
} | |
} | |
public GraphQLSchema getMutatedSchema() { | |
return GraphQLSchema.newSchema(baseSchema) | |
.codeRegistry(codeRegistry.build()) | |
.mutation(GraphQLObjectType.newObject() | |
.name(MUTATION_NAME) | |
.fields(mutations)) | |
.build(); | |
} | |
public void addMutations(EntityType<?> managedType, MyAbstractMutation<?, ?> repository) { | |
String entityName = getEntityName(managedType); | |
List<GraphQLArgument> arguments = new ArrayList<>(); | |
GraphQLInputObjectType inputObjectType = getInputObjectType(managedType); | |
arguments.add(GraphQLArgument.newArgument() | |
.name("input") | |
.type(inputObjectType) | |
.build() | |
); | |
GraphQLInputObjectType idInputObjectType = getIdInputObjectType(managedType); | |
List<GraphQLArgument> deleteArguments = new ArrayList<>(); | |
deleteArguments.add(GraphQLArgument.newArgument() | |
.name("input") | |
.type(idInputObjectType) | |
.build() | |
); | |
List<GraphQLArgument> bulkDeleteArguments = new ArrayList<>(); | |
bulkDeleteArguments.add(GraphQLArgument.newArgument() | |
.name("input") | |
.type(new GraphQLList(idInputObjectType)) | |
.build() | |
); | |
String updateDescription = ""; | |
if (!repository.isPartialUpdateSupported()) { | |
updateDescription = "Partial update is NOT supported!"; | |
} | |
addMutation("Insert", repository.getNewOperation(), entityName, | |
baseFieldDefinitionsCache.get(entityName).getType(), arguments); | |
addMutation("Update", repository.getUpdateOperation(), entityName, | |
baseFieldDefinitionsCache.get(entityName).getType(), arguments, updateDescription); | |
addMutation("Delete", repository.getDeleteOperation(), entityName, | |
GraphQLBoolean, deleteArguments); | |
addMutation("BulkDelete", repository.getBulkDeleteOperation(), entityName, | |
GraphQLBoolean, bulkDeleteArguments); | |
addMutation("Insert", repository.getNewOperation(), entityName, | |
baseFieldDefinitionsCache.get(entityName).getType(), arguments); | |
if (repository.isBulkUpdateSupported()) { | |
List<GraphQLArgument> bulkUpdateArguments = new ArrayList<>(); | |
bulkUpdateArguments.add(GraphQLArgument.newArgument() | |
.name("input") | |
.type(new GraphQLList(inputObjectType)) | |
.build() | |
); | |
addMutation("BulkUpdate", repository.getBulkUpdateOperation(), entityName, | |
new GraphQLList(baseFieldDefinitionsCache.get(entityName).getType()), bulkUpdateArguments); | |
} | |
} | |
private void addMutation(String operation, DataFetcher<?> dataFetcher, String entityName, | |
GraphQLOutputType outputType, List<GraphQLArgument> arguments) { | |
addMutation(operation, dataFetcher, entityName, outputType, arguments, null); | |
} | |
private void addMutation(String operation, DataFetcher<?> dataFetcher, String entityName, | |
GraphQLOutputType outputType, List<GraphQLArgument> arguments, String description) { | |
GraphQLFieldDefinition.Builder builder = GraphQLFieldDefinition.newFieldDefinition() | |
.name(entityName + operation) | |
.type(outputType) | |
.arguments(arguments); | |
if (!StringUtils.isEmpty(description)) { | |
builder.description(description); | |
} | |
GraphQLFieldDefinition field = builder.build(); | |
mutations.add(field); | |
codeRegistry.dataFetcherIfAbsent(FieldCoordinates.coordinates(MUTATION_NAME, field.getName()), dataFetcher); | |
} | |
private GraphQLInputObjectType getIdInputObjectType(ManagedType<?> managedType) { | |
String entityName = getEntityName(managedType); | |
Attribute<?, ?> idAttribute = managedType.getAttributes().stream() | |
.filter(this::isIdentity) | |
.findFirst() | |
.get(); | |
String idName = String.format("%s%sMutationInputType", entityName, StringUtils.capitalize(idAttribute.getName())); | |
if (idInputTypeCache.containsKey(idName)) { | |
return idInputTypeCache.get(idName); | |
} | |
GraphQLInputObjectField idField = getInputObjectField(idAttribute); | |
List<GraphQLInputObjectField> fields = new ArrayList<>(); | |
if (isEmbeddable(idAttribute)) { | |
return (GraphQLInputObjectType) idField.getType(); | |
} else { | |
fields.add(idField); | |
} | |
GraphQLInputObjectType inputType = GraphQLInputObjectType.newInputObject() | |
.name(idName) | |
.description("Input type mapping for the Id of " + entityName) | |
.fields(fields) | |
.build(); | |
idInputTypeCache.put(idName, inputType); | |
return inputType; | |
} | |
private GraphQLInputObjectType getInputObjectType(ManagedType<?> managedType) { | |
if (inputTypeCache.containsKey(managedType)) { | |
return inputTypeCache.get(managedType); | |
} | |
String entityName = getEntityName(managedType); | |
GraphQLInputObjectType inputType = GraphQLInputObjectType.newInputObject() | |
.name(entityName + "MutationInputType") | |
.description("Input type mapping to create or update " + entityName) | |
.fields(managedType.getAttributes().stream() | |
.filter(this::isValidInput) | |
.filter(this::isNotIgnored) | |
.filter(this::isNotIgnoredFilter) | |
.map(this::getInputObjectField) | |
.collect(Collectors.toList()) | |
) | |
.field(GraphQLInputObjectField.newInputObjectField() | |
.name("partialUpdate") | |
.type(GraphQLBoolean) | |
.description("true/false... Partial Update is not supported by all entities!") | |
).fields(managedType.getAttributes().stream() | |
.filter(this::isNotIgnored) | |
.filter(this::isNotIgnoredFilter) | |
.filter(Attribute::isAssociation) | |
.filter(this::isToMany) | |
.map(this::getAssociationInputObjectField) | |
.collect(Collectors.toList()) | |
) | |
.build(); | |
inputTypeCache.put(managedType, inputType); | |
return inputType; | |
} | |
private GraphQLInputObjectField getAssociationInputObjectField(Attribute<?, ?> attribute) { | |
if (assocationTypeCache.containsKey(attribute)) { | |
return assocationTypeCache.get(attribute); | |
} | |
GraphQLInputType type = getAssociationInputType(attribute.getName(), (EntityType<?>) ((PluralAttribute<?,?,?>) attribute).getElementType()); | |
String description = getSchemaDescription(attribute); | |
GraphQLInputObjectField ret = GraphQLInputObjectField.newInputObjectField() | |
.name(attribute.getName()) | |
.description(description) | |
.type(new GraphQLList(type)) | |
.build(); | |
assocationTypeCache.put(attribute, ret); | |
return ret; | |
} | |
private GraphQLInputObjectType getAssociationInputType(String name, ManagedType<?> managedType) { | |
GraphQLInputObjectType.Builder whereInputObject = GraphQLInputObjectType.newInputObject() | |
.name(name) | |
.fields(managedType.getAttributes().stream() | |
.filter(this::isValidInput) | |
.filter(this::isNotIgnored) | |
.filter(this::isNotIgnoredFilter) | |
.map(this::getInputObjectField) | |
.collect(Collectors.toList()) | |
) | |
.fields(managedType.getAttributes().stream() | |
.filter(this::isNotIgnored) | |
.filter(this::isNotIgnoredFilter) | |
.filter(this::isToMany) | |
.map(this::getAssociationInputObjectField) | |
.collect(Collectors.toList()) | |
); | |
return whereInputObject.build(); | |
} | |
private String getEntityName(ManagedType<?> managedType) { | |
return managedType.getJavaType().getSimpleName(); | |
} | |
// ################################################################################################################### | |
// Copied from GraphQLJpaSchemaBuilder (0.3 branch) because they are private but are required to | |
// generate our schema | |
// ################################################################################################################### | |
private GraphQLInputObjectField getInputObjectField(Attribute<?, ?> attribute) { | |
GraphQLInputType type = getAttributeInputType(attribute); | |
return GraphQLInputObjectField.newInputObjectField() | |
.name(attribute.getName()) | |
.description(getSchemaDescription(attribute)) | |
.type(type) | |
.build(); | |
} | |
private GraphQLInputType getAttributeInputType(Attribute<?,?> attribute) { | |
try { | |
return (GraphQLInputType) getAttributeType(attribute); | |
} catch (ClassCastException e){ | |
throw new IllegalArgumentException("Attribute " + attribute + " cannot be mapped as an Input Argument"); | |
} | |
} | |
private GraphQLType getAttributeType(Attribute<?,?> attribute) { | |
if (isBasic(attribute)) { | |
return getGraphQLTypeFromJavaType(attribute.getJavaType()); | |
} else if (isEmbeddable(attribute)) { | |
EmbeddableType<?> embeddableType = (EmbeddableType<?>) ((SingularAttribute<?, ?>) attribute).getType(); | |
return getEmbeddableType(embeddableType); | |
} else if (isToMany(attribute)) { | |
EntityType<?> foreignType = (EntityType<?>) ((PluralAttribute<?, ?, ?>) attribute).getElementType(); | |
return new GraphQLList(new GraphQLTypeReference(foreignType.getName())); | |
} else if (isToOne(attribute)) { | |
EntityType<?> foreignType = (EntityType<?>) ((SingularAttribute<?, ?>) attribute).getType(); | |
return new GraphQLTypeReference(foreignType.getName()); | |
} | |
else if (isElementCollection(attribute)) { | |
Type<?> foreignType = ((PluralAttribute<?, ?, ?>) attribute).getElementType(); | |
if (foreignType.getPersistenceType() == Type.PersistenceType.BASIC) { | |
return new GraphQLList(getGraphQLTypeFromJavaType(foreignType.getJavaType())); | |
} | |
} | |
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 +"'"); | |
} | |
@SuppressWarnings( "unchecked" ) | |
private GraphQLType getGraphQLTypeFromJavaType(Class<?> clazz) { | |
if (clazz.isEnum()) { | |
if (classCache.containsKey(clazz)) { | |
return classCache.get(clazz); | |
} | |
GraphQLEnumType.Builder enumBuilder = GraphQLEnumType.newEnum().name(clazz.getSimpleName() + MUTATION_NAME); | |
int ordinal = 0; | |
for (Enum<?> enumValue : ((Class<Enum<?>>)clazz).getEnumConstants()) | |
enumBuilder.value(enumValue.name(), ordinal++); | |
GraphQLEnumType enumType = enumBuilder.build(); | |
setNoOpCoercing(enumType); | |
classCache.putIfAbsent(clazz, enumType); | |
return enumType; | |
} else if (clazz.isArray()) { | |
return GraphQLList.list(JavaScalars.of(clazz.getComponentType())); | |
} | |
return JavaScalars.of(clazz); | |
} | |
/** | |
* JPA will deserialize Enum's for us...we don't want GraphQL doing it. | |
* | |
* @param type | |
*/ | |
private void setNoOpCoercing(GraphQLType type) { | |
try { | |
Field coercing = type.getClass().getDeclaredField("coercing"); | |
coercing.setAccessible(true); | |
coercing.set(type, new NoOpCoercing()); | |
} catch (Exception e) { | |
LOGGER.error("Unable to set coercing for " + type, e); | |
} | |
} | |
private GraphQLInputObjectType getEmbeddableType(EmbeddableType<?> embeddableType) { | |
if (embeddableInputCache.containsKey(embeddableType.getJavaType())) { | |
return embeddableInputCache.get(embeddableType.getJavaType()); | |
} | |
String embeddableTypeName = namingStrategy.singularize(embeddableType.getJavaType().getSimpleName())+ "MutationInputEmbeddableType"; | |
GraphQLInputObjectType graphQLType = GraphQLInputObjectType.newInputObject() | |
.name(embeddableTypeName) | |
.description(getSchemaDescription(embeddableType)) | |
.fields(embeddableType.getAttributes().stream() | |
.filter(this::isNotIgnored) | |
.map(this::getInputObjectField) | |
.collect(Collectors.toList()) | |
) | |
.build(); | |
embeddableInputCache.putIfAbsent(embeddableType.getJavaType(), graphQLType); | |
return graphQLType; | |
} | |
private String getSchemaDescription(Attribute<?,?> attribute) { | |
return EntityIntrospector.introspect(attribute.getDeclaringType()) | |
.getSchemaDescription(attribute.getName()) | |
.orElse(null); | |
} | |
private String getSchemaDescription(EmbeddableType<?> embeddableType) { | |
return EntityIntrospector.introspect(embeddableType) | |
.getSchemaDescription() | |
.orElse(null); | |
} | |
private boolean isNotIgnored(Attribute<?,?> attribute) { | |
return isNotIgnored(attribute.getJavaMember()) && isNotIgnored(attribute.getJavaType()); | |
} | |
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; | |
} | |
private boolean isIdentity(Attribute<?,?> attribute) { | |
return attribute instanceof SingularAttribute && ((SingularAttribute<?,?>)attribute).isId(); | |
} | |
static class NoOpCoercing implements Coercing<Object, Object> { | |
@Override | |
public Object serialize(Object input) { | |
return input; | |
} | |
@Override | |
public Object parseValue(Object input) { | |
return input; | |
} | |
@Override | |
public Object parseLiteral(Object input) { | |
return input; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package your.fancy.package.mutation; | |
import java.io.Serializable; | |
import java.util.ArrayList; | |
import java.util.Collection; | |
import java.util.List; | |
import java.util.Map; | |
import java.util.stream.Collectors; | |
import com.fasterxml.jackson.databind.DeserializationFeature; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import graphql.language.EnumValue; | |
import graphql.language.StringValue; | |
import graphql.schema.DataFetcher; | |
import graphql.schema.DataFetchingEnvironment; | |
import org.springframework.orm.jpa.JpaTransactionManager; | |
import your.fancy.package.MyDAOInterface; | |
public abstract class MyAbstractMutation<P extends Serializable, T> { | |
protected JpaTransactionManager transactionManager; | |
protected MyDAOInterface<P, T> dao; | |
public MyAbstractMutation(MyDAOInterface<P, T> dao, JpaTransactionManager transactionManager) { | |
this.dao = dao; | |
this.transactionManager = transactionManager; | |
} | |
private static final ThreadLocal<ObjectMapper> om = ThreadLocal.withInitial(() -> { | |
ObjectMapper objectMapper = new ObjectMapper(); | |
objectMapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); | |
return objectMapper; | |
}); | |
private ObjectMapper getObjectMapper() { | |
return om.get(); | |
} | |
protected abstract Class<P> getIdClass(); | |
public String getEntityName() { | |
return dao.getEntityName(); | |
} | |
public boolean isPartialUpdateSupported() { | |
return false; | |
} | |
public boolean isBulkUpdateSupported() { | |
return false; | |
} | |
public DataFetcher<T> getNewOperation() { | |
return new TransactionalDataFetcher<T>(transactionManager) { | |
@Override | |
protected T doInTransaction(DataFetchingEnvironment environment) { | |
T entity = convertValue(environment.getArgument("input"), dao.getEntityClass()); | |
return performCreate(entity); | |
} | |
}; | |
} | |
public DataFetcher<Boolean> getDeleteOperation() { | |
return new TransactionalDataFetcher<Boolean>(transactionManager) { | |
@Override | |
protected Boolean getErrorResult() { | |
return Boolean.FALSE; | |
} | |
@Override | |
protected Boolean doInTransaction(DataFetchingEnvironment environment) { | |
Map<String, Object> params = environment.getArgument("input"); | |
P id; | |
if (params.containsKey("id")) { | |
id = getObjectMapper().convertValue(params.get("id"), getIdClass()); | |
} else { | |
id = getObjectMapper().convertValue(params, getIdClass()); | |
} | |
return performDelete(id); | |
} | |
}; | |
} | |
public DataFetcher<Boolean> getBulkDeleteOperation() { | |
return new TransactionalDataFetcher<Boolean>(transactionManager) { | |
@Override | |
protected Boolean getErrorResult() { | |
return Boolean.FALSE; | |
} | |
@Override | |
protected Boolean doInTransaction(DataFetchingEnvironment environment) { | |
List<Map<String, Object>> params = environment.getArgument("input"); | |
List<P> ids = params.stream() | |
.map(p -> getObjectMapper().convertValue(p.getOrDefault("id", p), getIdClass())) | |
.collect(Collectors.toCollection(ArrayList::new)); | |
return dao.bulkRemove(ids); | |
} | |
}; | |
} | |
public DataFetcher<T> getUpdateOperation() { | |
return new TransactionalDataFetcher<T>(transactionManager) { | |
@Override | |
protected T doInTransaction(DataFetchingEnvironment environment) { | |
Map<String, Object> data = environment.getArgument("input"); | |
T parameters = convertValue(data, dao.getEntityClass()); | |
if (data.containsKey("partialUpdate") && (Boolean)data.get("partialUpdate")) { | |
return performPartialUpdate(parameters); | |
} else { | |
return performUpdate(parameters); | |
} | |
} | |
}; | |
} | |
public DataFetcher<List<T>> getBulkUpdateOperation() { | |
return new TransactionalDataFetcher<List<T>>(transactionManager) { | |
@Override | |
protected List<T> doInTransaction(DataFetchingEnvironment environment) { | |
List<Map<String, Object>> parameterList = environment.getArgument("input"); | |
List<T> entityList = new ArrayList<>(); | |
for (Map<String, Object> params : parameterList) { | |
entityList.add(convertValue(params, dao.getEntityClass())); | |
} | |
return performBulkUpdate(entityList); | |
} | |
}; | |
} | |
public Boolean performDelete(P id) { | |
return dao.remove(id); | |
} | |
public List<T> performBulkUpdate(Collection<T> entity) { | |
return dao.bulkUpdate(entity); | |
} | |
public T performCreate(T entity) { | |
return dao.update(entity); | |
} | |
public T performUpdate(T entity) { | |
return dao.update(entity); | |
} | |
public T performPartialUpdate(T entity) { | |
throw new UnsupportedOperationException("Partial update are not supported by " + dao.getEntityName()); | |
} | |
private T convertValue(Map<String, Object> data, Class<T> clazz) { | |
for (Map.Entry<String, Object> entry : data.entrySet()) { | |
if (entry.getValue() == null) { | |
continue; | |
} | |
if (entry.getValue() instanceof String) { | |
if (entry.getValue().equals("null")) { | |
data.put(entry.getKey(), null); | |
} | |
} else if (entry.getValue() instanceof StringValue) { | |
if (((StringValue)entry.getValue()).getValue().equals("null")) { | |
data.put(entry.getKey(), null); | |
} | |
} else if (entry.getValue() instanceof EnumValue) { | |
if (((EnumValue)entry.getValue()).getName().equals("undefined")) { | |
data.put(entry.getKey(), null); | |
} else { | |
data.put(entry.getKey(), ((EnumValue) entry.getValue()).getName()); | |
} | |
} | |
} | |
return getObjectMapper().convertValue(data, clazz); | |
} | |
} | |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package your.fancy.package.config; | |
import java.util.ArrayList; | |
import java.util.List; | |
import java.util.Optional; | |
import javax.persistence.EntityManager; | |
import javax.persistence.PersistenceContext; | |
import javax.persistence.metamodel.EntityType; | |
import com.fasterxml.jackson.databind.ObjectMapper; | |
import com.introproventures.graphql.jpa.query.autoconfigure.GraphQLJpaQueryProperties; | |
import com.introproventures.graphql.jpa.query.autoconfigure.GraphQLSchemaFactoryBean; | |
import com.introproventures.graphql.jpa.query.schema.GraphQLExecutor; | |
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaExecutor; | |
import graphql.GraphQL; | |
import graphql.schema.GraphQLSchema; | |
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; | |
import org.springframework.boot.context.properties.EnableConfigurationProperties; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.ComponentScan; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.context.annotation.PropertySource; | |
import org.springframework.context.annotation.PropertySources; | |
import your.fancy.package.MyAbstractMutation; | |
import your.fancy.package.MutationGraphQLSchemaBuilder; | |
@Configuration | |
@ConditionalOnClass(GraphQL.class) | |
@EnableConfigurationProperties(GraphQLJpaQueryProperties.class) | |
@PropertySources(value= { | |
@PropertySource("classpath:graphql.properties"), | |
@PropertySource(value = "classpath:graphql-jpa.properties", ignoreResourceNotFound = true) | |
}) | |
// make all your mutations available in order to be able to autowire the List<MyAbstractMutation> availableMutations | |
@ComponentScan(basePackages = { | |
"your.fancy.package.mutation" | |
}) | |
public class MyGraphQLConfiguration { | |
@PersistenceContext | |
private EntityManager entityManager; | |
private final GraphQLJpaQueryProperties graphQlJpaProperties; | |
private final List<MyAbstractMutation<?, ?>> availableMutations; | |
public MyGraphQLConfiguration(GraphQLJpaQueryProperties graphQlJpaProperties, List<MyAbstractMutation<?, ?>> availableMutations) { | |
this.graphQlJpaProperties = graphQlJpaProperties; | |
this.availableMutations = availableMutations; | |
} | |
@Bean | |
public GraphQLExecutor graphQLExecutor(GraphQLSchema graphQLSchema) { | |
return new GraphQLJpaExecutor(graphQLSchema); | |
} | |
@Bean | |
public ObjectMapper objectMapper() { | |
return new ObjectMapper(); | |
} | |
@Bean | |
public GraphQLSchemaFactoryBean graphQLSchemaFactoryBean() { | |
MutationGraphQLSchemaBuilder schemaBuilder = new MutationGraphQLSchemaBuilder(entityManager); | |
schemaBuilder.name("GraphJpaSchema") | |
.useDistinctParameter(false) | |
.defaultDistinct(true); | |
schemaBuilder.buildBaseSchema(); | |
for (MyAbstractMutation<?, ?> mutation : availableMutations) { | |
schemaBuilder.addMutations(getEntityTypeByName(entityManager, mutation.getEntityName()), mutation); | |
} | |
List<GraphQLSchema> schemas = new ArrayList<>(); | |
schemas.add(schemaBuilder.getMutatedSchema()); | |
return new GraphQLSchemaFactoryBean(schemas.toArray(new GraphQLSchema[1] )) | |
.setQueryName(graphQlJpaProperties.getName()) | |
.setQueryDescription(graphQlJpaProperties.getDescription()); | |
} | |
private EntityType<?> getEntityTypeByName(EntityManager entityManager, String entityName) { | |
Optional<EntityType<?>> ret = entityManager.getMetamodel().getEntities().stream() | |
.filter(e -> e.getName().equals(entityName)) | |
.findFirst(); | |
if (ret.isPresent()) { | |
return ret.get(); | |
} | |
throw new UnsupportedOperationException(entityName + " is not a known entity"); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package your.fancy.package.mutation; | |
import graphql.schema.DataFetcher; | |
import graphql.schema.DataFetchingEnvironment; | |
import org.springframework.orm.jpa.JpaTransactionManager; | |
import org.springframework.security.core.context.SecurityContextHolder; | |
import org.springframework.transaction.TransactionStatus; | |
public abstract class TransactionalDataFetcher<T> implements DataFetcher<T> { | |
private final JpaTransactionManager transactionManager; | |
protected TransactionalDataFetcher(JpaTransactionManager transactionManager) { | |
this.transactionManager = transactionManager; | |
} | |
protected T getErrorResult() { | |
return null; | |
} | |
@Override | |
public T get(DataFetchingEnvironment environment) throws Exception { | |
TransactionStatus status = null; | |
try { | |
status = transactionManager.getTransaction(null); | |
return doInTransaction(environment); | |
} catch (Exception e) { | |
if (status != null) { | |
transactionManager.rollback(status); | |
} | |
return getErrorResult(); | |
} finally { | |
if (status != null && !status.isCompleted()) { | |
transactionManager.commit(status); | |
} | |
} | |
} | |
protected abstract T doInTransaction(DataFetchingEnvironment environment); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment