Skip to content

Instantly share code, notes, and snippets.

@negesti
Created September 22, 2020 08:58
Show Gist options
  • Save negesti/7a74b063a0d17d87bdebbb559da9c9c8 to your computer and use it in GitHub Desktop.
Save negesti/7a74b063a0d17d87bdebbb559da9c9c8 to your computer and use it in GitHub Desktop.
graphql-jpa-query mutation
// 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;
}
}
}
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);
}
}
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");
}
}
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