Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Mutation #213

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.introproventures.graphql.jpa.query.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;


@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface GraphQLReadEntityForRole {
String[] value();
}
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import javax.persistence.OneToMany;
import javax.persistence.OrderBy;

import com.introproventures.graphql.jpa.query.annotation.GraphQLReadEntityForRole;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
Expand All @@ -41,6 +42,7 @@
@Setter
@ToString
@EqualsAndHashCode(exclude={"books","phoneNumbers"}) // Fixes NPE in Hibernate when initializing loaded collections #1
@GraphQLReadEntityForRole({"admin"})
public class Author {
@Id
Long id;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,17 +29,15 @@
import javax.persistence.ManyToOne;
import javax.persistence.Transient;

import com.introproventures.graphql.jpa.query.annotation.GraphQLDescription;
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnore;
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreFilter;
import com.introproventures.graphql.jpa.query.annotation.GraphQLIgnoreOrder;
import com.introproventures.graphql.jpa.query.annotation.*;

import lombok.Data;
import lombok.EqualsAndHashCode;

@Data
@Entity
@EqualsAndHashCode(exclude= {"author", "tags"})
@GraphQLReadEntityForRole({"user", "admin"})
public class Book {
@Id
Long id;
Expand Down
76 changes: 76 additions & 0 deletions graphql-jpa-query-schema-mutations/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.introproventures</groupId>
<artifactId>graphql-jpa-query-build</artifactId>
<version>0.3.36-SNAPSHOT</version>
<relativePath>../graphql-jpa-query-build</relativePath>
</parent>
<modelVersion>4.0.0</modelVersion>

<artifactId>graphql-jpa-query-schema-mutations</artifactId>
<name>graphql-jpa-query-schema-mutations</name>

<dependencies>
<dependency>
<groupId>com.introproventures</groupId>
<artifactId>graphql-jpa-query-annotations</artifactId>
</dependency>

<dependency>
<groupId>com.introproventures</groupId>
<artifactId>graphql-jpa-query-schema</artifactId>
</dependency>

<dependency>
<groupId>javax.persistence</groupId>
<artifactId>javax.persistence-api</artifactId>
</dependency>

<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
</dependency>

<dependency>
<groupId>javax.interceptor</groupId>
<artifactId>javax.interceptor-api</artifactId>
</dependency>

<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
</dependency>

<!-- https://mvnrepository.com/artifact/com.fasterxml.jackson.datatype/jackson-datatype-jsr310 -->
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
<version>2.9.8</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.json/json -->
<dependency>
<groupId>org.json</groupId>
<artifactId>json</artifactId>
<version>20180813</version>
</dependency>

<!-- https://mvnrepository.com/artifact/org.apache.commons/commons-lang3 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.8.1</version>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
package com.introproventures.graphql.jpa.query.mutations;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.function.Predicate;
import java.util.stream.Collectors;

import com.introproventures.graphql.jpa.query.mutations.fetcher.impl.*;

import javax.persistence.EntityManager;
import javax.persistence.metamodel.Attribute;
import javax.persistence.metamodel.EmbeddableType;
import javax.persistence.metamodel.EntityType;
import javax.persistence.metamodel.PluralAttribute;
import javax.persistence.metamodel.SingularAttribute;
import javax.persistence.metamodel.Type;

import com.introproventures.graphql.jpa.query.schema.impl.FetcherParams;
import com.introproventures.graphql.jpa.query.schema.impl.GraphQLJpaSchemaBuilder;

import graphql.schema.*;


public class GraphQLJpaSchemaBuilderWithMutation extends GraphQLJpaSchemaBuilder {
public class GqlMutationOper {
private String operation;
private String description;
private Class<? extends GraphQLJpaEntityInputFetcher> fetcher;

public GqlMutationOper(String oper, String description, Class<? extends GraphQLJpaEntityInputFetcher> fetcher) {
this.operation = oper;
this.description = description;
this.fetcher = fetcher;
}

public String getOperation() {
return operation;
}

public void setOperation(String operation) {
this.operation = operation;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public Class<? extends GraphQLJpaEntityInputFetcher> getFetcher() {
return fetcher;
}

public void setFetcher(Class<? extends GraphQLJpaEntityInputFetcher> fetcher) {
this.fetcher = fetcher;
}
}

protected String suffixInputObjectType = "Input";
private List<GqlMutationOper> operations = new ArrayList<>();
private Map<EntityType<?>, GraphQLInputObjectType> entityInputCache = new HashMap<>();

public GraphQLJpaSchemaBuilderWithMutation(EntityManager em) {
super(em);

operations.add(new GqlMutationOper("insert", "This insert entity", InsertFecher.class));
operations.add(new GqlMutationOper("update", "This update entity", UpdateFetcher.class));
operations.add(new GqlMutationOper("merge", "This update entity", MergeFetcher.class));
operations.add(new GqlMutationOper("delete", "This delete entity", DeleteFetcher.class));
}

public GraphQLJpaSchemaBuilderWithMutation suffixInputObjectType(String suffixInputObjectType) {
this.suffixInputObjectType = suffixInputObjectType;
return this;
}

@Override
public GraphQLSchema build() {
createFetcherParams();
return GraphQLSchema.newSchema()
.query(getQueryType())
.mutation(getMutation())
.build();
}

protected GraphQLObjectType getMutation() {
GraphQLObjectType.Builder queryType =
GraphQLObjectType.newObject()
.name("editEntities")
.description(this.getDescription());

List<GraphQLFieldDefinition> fields = new ArrayList<>();

for (EntityType<?> entityType : entityManager.getMetamodel().getEntities()) {
if (!isNotIgnored(entityType)) continue;

for (GqlMutationOper oper : operations) {
fields.add(getMutationDefinition(entityType, oper));
}
}

queryType.fields(fields);
return queryType.build();
}

private GraphQLFieldDefinition getMutationDefinition(EntityType<?> entityType, GqlMutationOper oper) {
return GraphQLFieldDefinition.newFieldDefinition()
.name(oper.getOperation()+entityType.getName())
.description(oper.getDescription())
.type(getObjectType(entityType))
.dataFetcher(getInputFetcher(entityType, oper))
.argument(getArgumentInputEntity(entityType))
.build();
}

private GraphQLJpaEntityInputFetcher getInputFetcher(EntityType<?> entityType, GqlMutationOper oper) {
Class<? extends GraphQLJpaEntityInputFetcher> classFetch = oper.getFetcher();
try {
return classFetch.getDeclaredConstructor(EntityManager.class, FetcherParams.class, EntityType.class)
.newInstance(entityManager, fetcherParams, entityType);
} catch(Exception e) {
throw new RuntimeException(e);
}
}


private GraphQLArgument getArgumentInputEntity(EntityType<?> entityType) {
return GraphQLArgument.newArgument()
.name("entity")
.type(getObjectInputType(entityType))
.description("this description entity " + entityType.getName())
.build();
}

private GraphQLInputObjectType getObjectInputType(EntityType<?> entityType) {
if (entityInputCache.containsKey(entityType))
return entityInputCache.get(entityType);

GraphQLInputObjectType objectInputType = GraphQLInputObjectType.newInputObject()
.name(entityType.getName()+suffixInputObjectType)
.description("Input type GraphQL for entity "+entityType.getName())

.fields(entityType.getAttributes().stream()
.filter(this::isNotIgnored)
.map(this::getObjectInputAttribute)
.collect(Collectors.toList())
)
.build();

entityInputCache.putIfAbsent(entityType, objectInputType);

return objectInputType;
}

public GraphQLInputObjectField getObjectInputAttribute(Attribute<?,?> attribute) {
GraphQLType type = getAttributeInputType(attribute);

if (!(type instanceof GraphQLInputType)) {
throw new IllegalArgumentException("Attribute " + attribute + " cannot be instanceof GraphQLInputType");
}

GraphQLInputObjectField field = GraphQLInputObjectField.newInputObjectField()
.name(attribute.getName())
.type((GraphQLInputType)type)
.build();

return field;
}

@SuppressWarnings( "rawtypes" )
protected GraphQLType getAttributeInputType(Attribute<?,?> attribute) {

if (isBasic(attribute)) {
return getGraphQLTypeFromJavaType(attribute.getJavaType());
}
else if (isEmbeddable(attribute)) {
EmbeddableType embeddableType = (EmbeddableType) ((SingularAttribute) attribute).getType();
return getEmbeddableType(embeddableType, true );
}
else if (isToMany(attribute)) {
EntityType foreignType = (EntityType) ((PluralAttribute) attribute).getElementType();
return new GraphQLList(new GraphQLTypeReference(foreignType.getName()+suffixInputObjectType));
}
else if (isToOne(attribute)) {
EntityType foreignType = (EntityType) ((SingularAttribute) attribute).getType();
return new GraphQLTypeReference(foreignType.getName()+suffixInputObjectType);
}
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 +"'");
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.introproventures.graphql.jpa.query.mutations.annotation;


import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface GraphQLWriteEntityForRole {
String[] value();

GraphQLWriteType[] operations() default {GraphQLWriteType.ALL};
}


Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.introproventures.graphql.jpa.query.mutations.annotation;

import com.introproventures.graphql.jpa.query.mutations.annotation.GraphQLWriteEntityForRole;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface GraphQLWriteEntityList {
GraphQLWriteEntityForRole[] value();
}



Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.introproventures.graphql.jpa.query.mutations.annotation;

public enum GraphQLWriteType {
ALL,
INSERT,
UPDATE,
DELETE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.introproventures.graphql.jpa.query.mutations.fetcher;

import graphql.schema.DataFetcher;

public interface IGraphQLJpaEntityInputFetcher extends DataFetcher<Object> {
Object executeMutation(Object entity, MutationContext mutationContext);
}
Loading