diff --git a/graphql-jpa-query-annotations/src/main/java/com/introproventures/graphql/jpa/query/annotation/GraphQLReadEntityForRole.java b/graphql-jpa-query-annotations/src/main/java/com/introproventures/graphql/jpa/query/annotation/GraphQLReadEntityForRole.java
new file mode 100644
index 000000000..f3e577502
--- /dev/null
+++ b/graphql-jpa-query-annotations/src/main/java/com/introproventures/graphql/jpa/query/annotation/GraphQLReadEntityForRole.java
@@ -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();
+}
diff --git a/graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Author.java b/graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Author.java
index 406c81b6e..e4b91886c 100644
--- a/graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Author.java
+++ b/graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Author.java
@@ -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;
@@ -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;
diff --git a/graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java b/graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java
index ce8045270..2fe40307d 100644
--- a/graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java
+++ b/graphql-jpa-query-example-model-books/src/main/java/com/introproventures/graphql/jpa/query/schema/model/book/Book.java
@@ -29,10 +29,7 @@
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;
@@ -40,6 +37,7 @@
@Data
@Entity
@EqualsAndHashCode(exclude= {"author", "tags"})
+@GraphQLReadEntityForRole({"user", "admin"})
public class Book {
@Id
Long id;
diff --git a/graphql-jpa-query-schema-mutations/pom.xml b/graphql-jpa-query-schema-mutations/pom.xml
new file mode 100644
index 000000000..2cfdb74f0
--- /dev/null
+++ b/graphql-jpa-query-schema-mutations/pom.xml
@@ -0,0 +1,76 @@
+
+
+
+ com.introproventures
+ graphql-jpa-query-build
+ 0.3.36-SNAPSHOT
+ ../graphql-jpa-query-build
+
+ 4.0.0
+
+ graphql-jpa-query-schema-mutations
+ graphql-jpa-query-schema-mutations
+
+
+
+ com.introproventures
+ graphql-jpa-query-annotations
+
+
+
+ com.introproventures
+ graphql-jpa-query-schema
+
+
+
+ javax.persistence
+ javax.persistence-api
+
+
+
+ javax.transaction
+ javax.transaction-api
+
+
+
+ javax.interceptor
+ javax.interceptor-api
+
+
+
+ org.slf4j
+ slf4j-api
+
+
+
+
+ com.fasterxml.jackson.datatype
+ jackson-datatype-jsr310
+ 2.9.8
+
+
+
+
+ org.json
+ json
+ 20180813
+
+
+
+
+ org.apache.commons
+ commons-lang3
+ 3.8.1
+
+
+
+ org.springframework.boot
+ spring-boot-starter-data-jpa
+ test
+
+
+
+
+
\ No newline at end of file
diff --git a/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/GraphQLJpaSchemaBuilderWithMutation.java b/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/GraphQLJpaSchemaBuilderWithMutation.java
new file mode 100644
index 000000000..058e45d98
--- /dev/null
+++ b/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/GraphQLJpaSchemaBuilderWithMutation.java
@@ -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 operations = new ArrayList<>();
+ private Map, 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 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 +"'");
+ }
+}
diff --git a/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/annotation/GraphQLWriteEntityForRole.java b/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/annotation/GraphQLWriteEntityForRole.java
new file mode 100644
index 000000000..d6ec0318d
--- /dev/null
+++ b/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/annotation/GraphQLWriteEntityForRole.java
@@ -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};
+}
+
+
diff --git a/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/annotation/GraphQLWriteEntityList.java b/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/annotation/GraphQLWriteEntityList.java
new file mode 100644
index 000000000..6c4eeae44
--- /dev/null
+++ b/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/annotation/GraphQLWriteEntityList.java
@@ -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();
+}
+
+
+
diff --git a/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/annotation/GraphQLWriteType.java b/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/annotation/GraphQLWriteType.java
new file mode 100644
index 000000000..374acd797
--- /dev/null
+++ b/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/annotation/GraphQLWriteType.java
@@ -0,0 +1,8 @@
+package com.introproventures.graphql.jpa.query.mutations.annotation;
+
+public enum GraphQLWriteType {
+ ALL,
+ INSERT,
+ UPDATE,
+ DELETE
+}
diff --git a/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/fetcher/IGraphQLJpaEntityInputFetcher.java b/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/fetcher/IGraphQLJpaEntityInputFetcher.java
new file mode 100644
index 000000000..9f2b9c66c
--- /dev/null
+++ b/graphql-jpa-query-schema-mutations/src/main/java/com/introproventures/graphql/jpa/query/mutations/fetcher/IGraphQLJpaEntityInputFetcher.java
@@ -0,0 +1,7 @@
+package com.introproventures.graphql.jpa.query.mutations.fetcher;
+
+import graphql.schema.DataFetcher;
+
+public interface IGraphQLJpaEntityInputFetcher extends DataFetcher