diff --git a/examples/microprofile-graphql/README.md b/examples/microprofile-graphql/README.md
new file mode 100644
index 0000000000..c925d922f6
--- /dev/null
+++ b/examples/microprofile-graphql/README.md
@@ -0,0 +1,12 @@
+Blaze-Persistence Examples Spring Data GraphQL
+==========
+This is an GraphQL sample application showcasing how the Blaze-Persistence GraphQL
+integration can be used to develop GraphQL interfaces with ease.
+
+## How to use it?
+
+Just run `mvn spring-boot:run` and navigate to http://localhost:8080/graphiql where you can run your GraphQL queries.
+You will see that the queries exposed there use the GraphQL Relay spec to implement pagination through cursors.
+You can take a look at the generated queries to further understand what happens or you take a look into the [documentation](https://persistence.blazebit.com/documentation/core/manual/en_US/index.html#anchor-keyset-pagination) for more information on the topic.
+
+Another thing that you will see from the generated queries is that the GraphQL selection list actually alters what is selected in the SQL query!
\ No newline at end of file
diff --git a/examples/microprofile-graphql/pom.xml b/examples/microprofile-graphql/pom.xml
new file mode 100644
index 0000000000..7527ba99c6
--- /dev/null
+++ b/examples/microprofile-graphql/pom.xml
@@ -0,0 +1,182 @@
+
+
+
+
+ 4.0.0
+
+ com.blazebit
+ blaze-persistence-examples
+ 1.6.0-SNAPSHOT
+ ../pom.xml
+
+
+ blaze-persistence-examples-microprofile-graphql
+ jar
+
+ Blazebit Persistence Examples MicroProfile GraphQL
+
+
+ com.blazebit.persistence.examples.microprofile.graphql
+ UTF-8
+ 1.8
+ 1.8
+
+ 1.10.3.Final
+ 5.4.22.Final
+
+
+
+
+
+ io.quarkus
+ quarkus-bom
+ ${version.quarkus}
+ pom
+ import
+
+
+
+
+
+
+ io.quarkus
+ quarkus-smallrye-graphql
+
+
+ io.quarkus
+ quarkus-resteasy-jackson
+
+
+ io.quarkus
+ quarkus-arc
+
+
+ io.quarkus
+ quarkus-resteasy
+
+
+ io.quarkus
+ quarkus-hibernate-orm
+
+
+ com.blazebit
+ blaze-persistence-integration-quarkus
+
+
+ com.graphql-java
+ graphql-java
+ 16.1
+
+
+ ${project.groupId}
+ blaze-persistence-integration-hibernate-base
+
+
+ ${project.groupId}
+ blaze-persistence-integration-hibernate-5.3
+
+
+ ${project.groupId}
+ blaze-persistence-integration-graphql
+
+
+ ${project.groupId}
+ blaze-persistence-integration-jaxrs
+
+
+
+
+ junit
+ junit
+ test
+
+
+ io.quarkus
+ quarkus-jdbc-h2
+
+
+ io.quarkus
+ quarkus-test-h2
+
+
+ io.quarkus
+ quarkus-junit5
+ test
+
+
+ io.rest-assured
+ rest-assured
+ test
+
+
+
+
+ javax.xml.bind
+ jaxb-api
+ ${version.jaxb}
+
+
+ com.sun.xml.bind
+ jaxb-core
+ ${version.jaxb}
+
+
+ com.sun.xml.bind
+ jaxb-impl
+ ${version.jaxb}
+
+
+ javax.annotation
+ javax.annotation-api
+ ${version.annotation}
+
+
+
+
+
+
+ io.quarkus
+ quarkus-maven-plugin
+ ${version.quarkus}
+ true
+
+
+
+ build
+ generate-code
+ generate-code-tests
+
+
+
+
+
+ maven-surefire-plugin
+
+
+ org.jboss.logmanager.LogManager
+
+ true
+
+
+ h2
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/Application.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/Application.java
new file mode 100644
index 0000000000..3579bf65d6
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/Application.java
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql;
+
+import com.blazebit.persistence.examples.microprofile.graphql.model.Cat;
+import com.blazebit.persistence.examples.microprofile.graphql.model.Person;
+import io.quarkus.runtime.StartupEvent;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.transaction.Transactional;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+@ApplicationScoped
+public class Application {
+
+ @Inject
+ EntityManager em;
+
+ @Transactional
+ void onStart(@Observes StartupEvent ev) {
+ ThreadLocalRandom random = ThreadLocalRandom.current();
+ List people = new ArrayList<>();
+ for (int i = 0; i < 4; i++) {
+ Person p = new Person("Person " + i);
+ people.add(p);
+ em.persist(p);
+ }
+ for (int i = 0; i < 100; i++) {
+ Cat c = new Cat("Cat " + i, random.nextInt(20), people.get(random.nextInt(4)));
+ em.persist(c);
+ }
+ }
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/GraphQLEntityViewSupport.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/GraphQLEntityViewSupport.java
new file mode 100644
index 0000000000..3fbb60c84e
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/GraphQLEntityViewSupport.java
@@ -0,0 +1,477 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql;
+
+import com.blazebit.persistence.CriteriaBuilder;
+import com.blazebit.persistence.DefaultKeyset;
+import com.blazebit.persistence.DefaultKeysetPage;
+import com.blazebit.persistence.KeysetPage;
+import com.blazebit.persistence.PaginatedCriteriaBuilder;
+import com.blazebit.persistence.integration.graphql.GraphQLCursor;
+import com.blazebit.persistence.integration.graphql.GraphQLCursorObjectInputStream;
+import com.blazebit.persistence.view.ConfigurationProperties;
+import com.blazebit.persistence.view.EntityViewSetting;
+import graphql.schema.DataFetchingEnvironment;
+import graphql.schema.GraphQLList;
+import graphql.schema.GraphQLNamedType;
+import graphql.schema.GraphQLNonNull;
+import graphql.schema.GraphQLObjectType;
+import graphql.schema.GraphQLType;
+import graphql.schema.SelectedField;
+
+import java.io.ByteArrayInputStream;
+import java.io.ObjectInputStream;
+import java.io.Serializable;
+import java.util.Base64;
+import java.util.Collection;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A support class to interact with entity views in a GraphQL environment.
+ *
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+public class GraphQLEntityViewSupport {
+
+ /**
+ * Default name for the page size field.
+ */
+ public static final String PAGE_SIZE_NAME = "first";
+ /**
+ * Default name for the page size field.
+ */
+ public static final String RELAY_LAST_NAME = "last";
+ /**
+ * Default name for the offset field.
+ */
+ public static final String OFFSET_NAME = "offset";
+ /**
+ * Default name for the before cursor field.
+ */
+ public static final String BEFORE_CURSOR_NAME = "before";
+ /**
+ * Default name for the after cursor field.
+ */
+ public static final String AFTER_CURSOR_NAME = "after";
+ /**
+ * Default name for the edges field.
+ */
+ public static final String EDGES_NAME = "edges";
+ /**
+ * Default name for the node field.
+ */
+ public static final String EDGE_NODE_NAME = "node";
+ /**
+ * Default name for the cursor field.
+ */
+ public static final String EDGE_CURSOR_NAME = "cursor";
+ /**
+ * Default name for the total count field.
+ */
+ public static final String TOTAL_COUNT_NAME = "totalCount";
+
+ private final Map> typeNameToClass;
+ private final Set serializableBasicTypes;
+
+ private final String pageSizeName;
+ private final String offsetName;
+ private final String beforeCursorName;
+ private final String afterCursorName;
+ private final String totalCountName;
+ private final String pageElementsName;
+ private final String pageElementObjectName;
+ private final String elementCursorName;
+
+ /**
+ * Creates a new {@link GraphQLEntityViewSupport} instance with the given type name to class mapping and serializable basic type whitelist.
+ * It uses the GraphQL Relay specification names for accessing page info fields for paginated settings.
+ *
+ * @param typeNameToClass The mapping from GraphQL type names to entity view class names
+ * @param serializableBasicTypes The whitelist of allowed serializable basic types to use for cursor deserialization
+ */
+ public GraphQLEntityViewSupport(Map> typeNameToClass, Set serializableBasicTypes) {
+ this(typeNameToClass, serializableBasicTypes, PAGE_SIZE_NAME, OFFSET_NAME, BEFORE_CURSOR_NAME, AFTER_CURSOR_NAME, TOTAL_COUNT_NAME, EDGES_NAME, EDGE_NODE_NAME, EDGE_CURSOR_NAME);
+ }
+
+ /**
+ * Creates a new {@link GraphQLEntityViewSupport} instance with the given type name to class mapping and serializable basic type whitelist.
+ * @param typeNameToClass The mapping from GraphQL type names to entity view class names
+ * @param serializableBasicTypes The whitelist of allowed serializable basic types to use for cursor deserialization
+ * @param pageSizeName The name of the page size field
+ * @param offsetName The name of the offset field
+ * @param beforeCursorName The name of the beforeCursor field
+ * @param afterCursorName The name of the afterCursor field
+ * @param totalCountName The name of the totalCount field
+ * @param pageElementsName The name of the elements field
+ * @param pageElementObjectName The name of the element object field within elements
+ * @param elementCursorName The name of the cursor field within elements
+ */
+ public GraphQLEntityViewSupport(Map> typeNameToClass, Set serializableBasicTypes, String pageSizeName, String offsetName, String beforeCursorName, String afterCursorName, String totalCountName, String pageElementsName, String pageElementObjectName, String elementCursorName) {
+ this.pageSizeName = pageSizeName;
+ this.offsetName = offsetName;
+ this.beforeCursorName = beforeCursorName;
+ this.afterCursorName = afterCursorName;
+ this.totalCountName = totalCountName;
+ this.pageElementsName = pageElementsName;
+ this.typeNameToClass = typeNameToClass;
+ this.serializableBasicTypes = serializableBasicTypes;
+ this.pageElementObjectName = pageElementObjectName;
+ this.elementCursorName = elementCursorName;
+ }
+
+ /**
+ * Returns a new entity view setting for the given data fetching environment.
+ *
+ * @param dataFetchingEnvironment The GraphQL data fetching environment
+ * @param The entity view type
+ * @return the entity view setting
+ */
+ public EntityViewSetting> createPaginatedSetting(DataFetchingEnvironment dataFetchingEnvironment) {
+ return createPaginatedSetting(dataFetchingEnvironment, pageElementsName);
+ }
+
+ /**
+ * Returns a new entity view setting for the given data fetching environment.
+ *
+ * @param dataFetchingEnvironment The GraphQL data fetching environment
+ * @param elementRoot The field at which to find the elements for fetch extraction
+ * @param The entity view type
+ * @return the entity view setting
+ */
+ public EntityViewSetting> createPaginatedSetting(DataFetchingEnvironment dataFetchingEnvironment, String elementRoot) {
+ String typeName = getElementTypeName(dataFetchingEnvironment, elementRoot);
+ Class> entityViewClass = typeNameToClass.get(typeName);
+ if (entityViewClass == null) {
+ throw new IllegalArgumentException("No entity view type is registered for the name: " + typeName);
+ }
+ return createPaginatedSetting((Class) entityViewClass, dataFetchingEnvironment, elementRoot);
+ }
+
+ /**
+ * Like calling {{@link #createSetting(Class, DataFetchingEnvironment, String)}} with the configured page elements name.
+ *
+ * @param entityViewClass The entity view class
+ * @param dataFetchingEnvironment The GraphQL data fetching environment
+ * @param The entity view type
+ * @return the entity view setting
+ */
+ public EntityViewSetting> createPaginatedSetting(Class entityViewClass, DataFetchingEnvironment dataFetchingEnvironment) {
+ return createPaginatedSetting(entityViewClass, dataFetchingEnvironment, pageElementsName);
+ }
+
+ /**
+ * Like calling {{@link #createSetting(Class, DataFetchingEnvironment, String)}} with the configured page elements name.
+ *
+ * @param entityViewClass The entity view class
+ * @param dataFetchingEnvironment The GraphQL data fetching environment
+ * @param elementRoot The field at which to find the elements for fetch extraction
+ * @param The entity view type
+ * @return the entity view setting
+ */
+ public EntityViewSetting> createPaginatedSetting(Class entityViewClass, DataFetchingEnvironment dataFetchingEnvironment, String elementRoot) {
+ String objectRoot;
+ if (pageElementObjectName == null || pageElementObjectName.isEmpty()) {
+ objectRoot = elementRoot;
+ } else if (elementRoot == null || elementRoot.isEmpty()) {
+ objectRoot = pageElementObjectName;
+ } else {
+ objectRoot = elementRoot + "/" + pageElementObjectName;
+ }
+ EntityViewSetting> setting = (EntityViewSetting>) (EntityViewSetting, ?>) createSetting(entityViewClass, dataFetchingEnvironment, objectRoot);
+
+ Map> map = dataFetchingEnvironment.getSelectionSet().getFieldsGroupedByResultKey();
+
+ if (!map.containsKey(totalCountName)) {
+ setting.setProperty(ConfigurationProperties.PAGINATION_DISABLE_COUNT_QUERY, Boolean.TRUE);
+ }
+
+ if (elementCursorName != null && !elementCursorName.isEmpty()) {
+ String elementCursorPath;
+ if (elementRoot == null || elementRoot.isEmpty()) {
+ elementCursorPath = elementCursorName;
+ } else {
+ elementCursorPath = elementRoot + "/" + elementCursorName;
+ }
+
+ if (map.containsKey(elementCursorPath)) {
+ setting.setProperty(ConfigurationProperties.PAGINATION_EXTRACT_ALL_KEYSETS, Boolean.TRUE);
+ }
+ }
+ return setting;
+ }
+
+ /**
+ * Like calling {{@link #createSetting(DataFetchingEnvironment, String)}} with an empty element root.
+ *
+ * @param dataFetchingEnvironment The GraphQL data fetching environment
+ * @param The entity view type
+ * @return the entity view setting
+ */
+ public EntityViewSetting> createSetting(DataFetchingEnvironment dataFetchingEnvironment) {
+ return createSetting(dataFetchingEnvironment, "");
+ }
+
+ /**
+ * Returns a new entity view setting for the given data fetching environment and element root.
+ * Determines the entity view class by using the type of the element root as resolved with the {@link DataFetchingEnvironment}.
+ * Like calling {{@link #createSetting(Class, DataFetchingEnvironment, String)}} with the explicit entity view class.
+ *
+ * @param dataFetchingEnvironment The GraphQL data fetching environment
+ * @param elementRoot The field at which to find the elements for fetch extraction
+ * @param The entity view type
+ * @return the entity view setting
+ */
+ public EntityViewSetting> createSetting(DataFetchingEnvironment dataFetchingEnvironment, String elementRoot) {
+ String typeName = getElementTypeName(dataFetchingEnvironment, elementRoot);
+ Class> entityViewClass = typeNameToClass.get(typeName);
+ if (entityViewClass == null) {
+ throw new IllegalArgumentException("No entity view type is registered for the name: " + typeName);
+ }
+ return createSetting((Class) entityViewClass, dataFetchingEnvironment, elementRoot);
+ }
+
+ public String getElementTypeName(DataFetchingEnvironment dataFetchingEnvironment, String elementRoot) {
+ GraphQLType type = dataFetchingEnvironment.getFieldType();
+ if (type instanceof GraphQLNonNull) {
+ type = ((GraphQLNonNull) type).getWrappedType();
+ }
+ if (type instanceof GraphQLList) {
+ type = ((GraphQLList) type).getWrappedType();
+ }
+ if (type instanceof GraphQLNonNull) {
+ type = ((GraphQLNonNull) type).getWrappedType();
+ }
+
+ String[] parts = elementRoot.split("/");
+ for (int i = 0; i < parts.length; i++) {
+ if (type instanceof GraphQLObjectType) {
+ if (parts[i].length() > 0) {
+ type = ((GraphQLObjectType) type).getFieldDefinition(parts[i]).getType();
+ }
+ if (type instanceof GraphQLNonNull) {
+ type = ((GraphQLNonNull) type).getWrappedType();
+ }
+ if (type instanceof GraphQLList) {
+ type = ((GraphQLList) type).getWrappedType();
+ }
+ if (type instanceof GraphQLNonNull) {
+ type = ((GraphQLNonNull) type).getWrappedType();
+ }
+ } else {
+ throw new IllegalArgumentException("The element root part '" + parts[i] + "' wasn't found on type: " + type);
+ }
+ }
+
+ return ((GraphQLNamedType) type).getName();
+ }
+
+ /**
+ * Like calling {{@link #createSetting(Class, DataFetchingEnvironment, String)}} with an empty element root.
+ *
+ * @param dataFetchingEnvironment The GraphQL data fetching environment
+ * @param The entity view type
+ * @return the entity view setting
+ */
+ public EntityViewSetting> createSetting(Class entityViewClass, DataFetchingEnvironment dataFetchingEnvironment) {
+ return createSetting(entityViewClass, dataFetchingEnvironment, "");
+ }
+
+ /**
+ * Returns a new entity view setting for the given data fetching environment.
+ *
+ * @param entityViewClass The entity view class
+ * @param dataFetchingEnvironment The GraphQL data fetching environment
+ * @param elementRoot The field at which to find the elements for fetch extraction
+ * @param The entity view type
+ * @return the entity view setting
+ */
+ public EntityViewSetting> createSetting(Class entityViewClass, DataFetchingEnvironment dataFetchingEnvironment, String elementRoot) {
+ KeysetPage keysetPage = extractKeysetPage(dataFetchingEnvironment);
+ EntityViewSetting> setting;
+ boolean forceUseKeyset = false;
+ if (keysetPage == null) {
+ setting = EntityViewSetting.create(entityViewClass);
+ } else {
+ Integer pageSize = dataFetchingEnvironment.getArgument(pageSizeName);
+ Integer offset = dataFetchingEnvironment.getArgument(offsetName);
+ Integer last = dataFetchingEnvironment.getArgument(RELAY_LAST_NAME);
+
+ if (pageSize == null) {
+ pageSize = Integer.MAX_VALUE;
+ } else if (pageSize < 0) {
+ throw new RuntimeException("Illegal negative " + pageSizeName + " parameter: " + pageSize);
+ }
+ if (last != null) {
+ if (last < 0) {
+ throw new RuntimeException("Illegal negative " + RELAY_LAST_NAME + " parameter: " + last);
+ }
+ if (Integer.MAX_VALUE == pageSize) {
+ pageSize = last;
+ if (offset == null) {
+ forceUseKeyset = true;
+ }
+ } else {
+ if (offset == null) {
+ offset = pageSize - last;
+ pageSize = last;
+ } else {
+ offset += pageSize - last;
+ pageSize = last;
+ }
+ if (offset < 0) {
+ offset = 0;
+ }
+ if (keysetPage.getLowest() != null || keysetPage.getHighest() != null) {
+ forceUseKeyset = true;
+ }
+ }
+ } else if (offset == null) {
+ forceUseKeyset = true;
+ } else if (offset < 0) {
+ throw new RuntimeException("Illegal negative " + offsetName + " parameter: " + offset);
+ }
+ setting = (EntityViewSetting>) (EntityViewSetting, ?>) EntityViewSetting.create(entityViewClass, offset == null ? 0 : (int) offset, (int) pageSize);
+ setting.withKeysetPage(keysetPage);
+ }
+
+ if (forceUseKeyset) {
+ setting.setProperty(ConfigurationProperties.PAGINATION_FORCE_USE_KEYSET, true);
+ }
+ applyFetches(dataFetchingEnvironment, setting, elementRoot);
+ return setting;
+ }
+
+ /**
+ * Extracts the {@link KeysetPage} from the {@link DataFetchingEnvironment} by extracting page size and offset,
+ * as well as deserializing before or afterCursors.
+ *
+ * @param dataFetchingEnvironment The GraphQL data fetching environment
+ * @return the {@link KeysetPage} or null
+ */
+ public KeysetPage extractKeysetPage(DataFetchingEnvironment dataFetchingEnvironment) {
+ Integer pageSize = dataFetchingEnvironment.getArgument(pageSizeName);
+ Integer last = dataFetchingEnvironment.getArgument(RELAY_LAST_NAME);
+ String beforeCursor = dataFetchingEnvironment.getArgument(beforeCursorName);
+ String afterCursor = dataFetchingEnvironment.getArgument(afterCursorName);
+ if (pageSize == null && last == null && beforeCursor == null && afterCursor == null) {
+ return null;
+ } else {
+ KeysetPage keysetPage;
+
+ if (beforeCursor != null) {
+ if (afterCursor != null) {
+ throw new RuntimeException("Can't provide both beforeCursor and afterCursor!");
+ }
+ GraphQLCursor cursor = deserialize(beforeCursor);
+ keysetPage = new DefaultKeysetPage(cursor.getOffset(), cursor.getPageSize(), new DefaultKeyset(cursor.getTuple()), null);
+ } else if (afterCursor != null) {
+ if (last != null) {
+ // Using an after cursor with last does not make sense, so skip using the cursor
+ // The only problem with that is, that the cursor could refer to the last element
+ // If that is the case, we would still get a result, which is IMO an edge case and can be ignored
+ keysetPage = new DefaultKeysetPage(0, last, new DefaultKeyset(null), null);
+ } else {
+ GraphQLCursor cursor = deserialize(afterCursor);
+ keysetPage = new DefaultKeysetPage(cursor.getOffset(), cursor.getPageSize(), null, new DefaultKeyset(cursor.getTuple()));
+ }
+ } else if (pageSize != null) {
+ keysetPage = new DefaultKeysetPage(0, pageSize, null, null);
+ } else {
+ // Keyset with empty tuple is a special case for traversing the result list in reverse order
+ keysetPage = new DefaultKeysetPage(0, last, new DefaultKeyset(null), null);
+ }
+
+ return keysetPage;
+ }
+ }
+
+ /**
+ * Like {{@link #applyFetches(DataFetchingEnvironment, EntityViewSetting, String)}} but with an empty element root.
+ *
+ * @param dataFetchingEnvironment The GraphQL data fetching environment
+ * @param setting The entity view setting
+ */
+ public void applyFetches(DataFetchingEnvironment dataFetchingEnvironment, EntityViewSetting, ?> setting) {
+ applyFetches(dataFetchingEnvironment, setting, "");
+ }
+
+ /**
+ * Applies the fetches to the {@link EntityViewSetting} as requested by the selection set of {@link DataFetchingEnvironment}
+ * and interpreting the only paths below the given element root.
+ *
+ * @param dataFetchingEnvironment The GraphQL data fetching environment
+ * @param setting The entity view setting
+ * @param elementRoot The element root
+ */
+ public void applyFetches(DataFetchingEnvironment dataFetchingEnvironment, EntityViewSetting, ?> setting, String elementRoot) {
+ String prefix = elementRoot == null || elementRoot.isEmpty() ? "" : elementRoot + "/";
+ Collection keys = dataFetchingEnvironment.getSelectionSet().getFieldsGroupedByResultKey().keySet();
+ StringBuilder sb = new StringBuilder();
+ OUTER: for (String key : keys) {
+ sb.setLength(0);
+ for (int i = 0; i < key.length(); i++) {
+ final char c = key.charAt(i);
+ if (i < prefix.length()) {
+ if (c != prefix.charAt(i)) {
+ continue OUTER;
+ } else {
+ continue;
+ }
+ }
+ if (c == '/') {
+ sb.append('.');
+ } else {
+ sb.append(c);
+ }
+ }
+ if (sb.length() > 0) {
+ setting.fetch(sb.toString());
+ }
+ }
+ }
+
+ /**
+ * Deserializes the given Base64 encoded cursor to a {@link GraphQLCursor} object.
+ *
+ * @param beforeCursor The Base64 encoded cursor
+ * @return a new cursor
+ */
+ protected GraphQLCursor deserialize(String beforeCursor) {
+ try (ObjectInputStream ois = new GraphQLCursorObjectInputStream(Base64.getDecoder().wrap(new ByteArrayInputStream(beforeCursor.getBytes())), serializableBasicTypes)) {
+ int offset = ois.read();
+ int pageSize = ois.read();
+ Serializable[] tuple = (Serializable[]) ois.readObject();
+ return new GraphQLCursor(offset, pageSize, tuple);
+ } catch (Exception e) {
+ throw new RuntimeException("Couldn't read cursor", e);
+ }
+ }
+
+ /**
+ * Returns the entity view class for the given GraphQL type name.
+ *
+ * @param typeName The GraphQL type name
+ * @return the entity view class or null
+ */
+ public Class> getEntityViewClass(String typeName) {
+ return typeNameToClass.get(typeName);
+ }
+
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/GraphQLEntityViewSupportFactory.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/GraphQLEntityViewSupportFactory.java
new file mode 100644
index 0000000000..ad1aacdd6b
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/GraphQLEntityViewSupportFactory.java
@@ -0,0 +1,408 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql;
+
+import com.blazebit.persistence.view.EntityViewManager;
+import com.blazebit.persistence.view.metamodel.ManagedViewType;
+import com.blazebit.persistence.view.metamodel.SingularAttribute;
+import graphql.language.NonNullType;
+import graphql.language.ObjectTypeDefinition;
+import graphql.language.Type;
+import graphql.language.TypeName;
+import graphql.schema.GraphQLCodeRegistry;
+import graphql.schema.GraphQLInterfaceType;
+import graphql.schema.GraphQLSchema;
+import graphql.schema.GraphQLType;
+import graphql.schema.TypeResolver;
+import graphql.schema.idl.TypeDefinitionRegistry;
+
+import java.math.BigDecimal;
+import java.math.BigInteger;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.Map;
+import java.util.Set;
+
+/**
+ * A factory for creating a support class for using entity views in a GraphQL environment.
+ *
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+public class GraphQLEntityViewSupportFactory {
+
+ private static final Map, String> TYPES;
+
+ static {
+ Map, String> types = new HashMap<>();
+ types.put(boolean.class, "Boolean");
+ types.put(Boolean.class, "Boolean");
+ types.put(byte.class, "Byte");
+ types.put(Byte.class, "Byte");
+ types.put(short.class, "Short");
+ types.put(Short.class, "Short");
+ types.put(int.class, "Int");
+ types.put(Integer.class, "Int");
+ types.put(long.class, "Long");
+ types.put(Long.class, "Long");
+ types.put(float.class, "Float");
+ types.put(Float.class, "Float");
+ types.put(double.class, "Float");
+ types.put(Double.class, "Float");
+ types.put(BigInteger.class, "BigInteger");
+ types.put(BigDecimal.class, "BigDecimal");
+ types.put(char.class, "Char");
+ types.put(Character.class, "Char");
+ types.put(String.class, "String");
+ TYPES = types;
+ }
+
+ private boolean defineNormalTypes;
+ private boolean defineRelayTypes;
+ private Boolean implementRelayNode;
+ private boolean defineRelayNodeIfNotExist = false;
+
+ /**
+ * Creates a new entity view support factory with the given configuration.
+ *
+ * @param defineNormalTypes If true, generates normal types for managed view types
+ * @param defineRelayTypes If true, generates relay types for managed views
+ */
+ public GraphQLEntityViewSupportFactory(boolean defineNormalTypes, boolean defineRelayTypes) {
+ this.defineNormalTypes = defineNormalTypes;
+ this.defineRelayTypes = defineRelayTypes;
+ }
+
+ /**
+ * Returns true if normal types should be defined.
+ *
+ * @return true if normal types should be defined
+ */
+ public boolean isDefineNormalTypes() {
+ return defineNormalTypes;
+ }
+
+ /**
+ * Sets whether normal types should be defined.
+ *
+ * @param defineNormalTypes Whether normal types should be defined
+ */
+ public void setDefineNormalTypes(boolean defineNormalTypes) {
+ this.defineNormalTypes = defineNormalTypes;
+ }
+
+ /**
+ * Returns true if Relay types should be defined.
+ *
+ * @return true if Relay types should be defined
+ */
+ public boolean isDefineRelayTypes() {
+ return defineRelayTypes;
+ }
+
+ /**
+ * Sets whether Relay types should be defined.
+ *
+ * @param defineRelayTypes Whether Relay types should be defined
+ */
+ public void setDefineRelayTypes(boolean defineRelayTypes) {
+ this.defineRelayTypes = defineRelayTypes;
+ }
+
+ /**
+ * Returns true if node types should implement the Relay Node interface.
+ *
+ * @return true if node types should implement the Relay Node interface
+ */
+ public boolean isImplementRelayNode() {
+ return implementRelayNode == null ? defineRelayNodeIfNotExist : implementRelayNode;
+ }
+
+ /**
+ * Sets whether Relay Node type should be implemented by node types.
+ *
+ * @param implementRelayNode Whether Relay Node type should be implemented by node types
+ */
+ public void setImplementRelayNode(boolean implementRelayNode) {
+ this.implementRelayNode = implementRelayNode;
+ }
+
+ /**
+ * Returns true if the Relay Node interface should be created if not found in the type registry.
+ *
+ * @return true if the Relay Node interface should be created if not found in the type registry
+ */
+ public boolean isDefineRelayNodeIfNotExist() {
+ return defineRelayNodeIfNotExist;
+ }
+
+ /**
+ * Sets whether the Relay Node interface should be defined if not found in the type registry.
+ *
+ * @param defineRelayNodeIfNotExist Whether the Relay Node interface should be defined if not found in the type registry
+ */
+ public void setDefineRelayNodeIfNotExist(boolean defineRelayNodeIfNotExist) {
+ this.defineRelayNodeIfNotExist = defineRelayNodeIfNotExist;
+ }
+
+ /**
+ * Returns a new {@link GraphQLEntityViewSupport} after registering the entity view types from {@link EntityViewManager}
+ * on the given {@link TypeDefinitionRegistry}.
+ *
+ * @param schemaBuilder The registry to register types
+ * @param entityViewManager The entity view manager
+ * @return a new {@link GraphQLEntityViewSupport}
+ */
+ public GraphQLEntityViewSupport create(GraphQLSchema.Builder schemaBuilder, EntityViewManager entityViewManager) {
+ GraphQLSchema schema = schemaBuilder.build();
+ Map> typeNameToClass = new HashMap<>();
+ GraphQLCodeRegistry codeRegistry = schema.getCodeRegistry();
+ schema.getType("CatSimpleView");
+ for (GraphQLType type : schema.getAdditionalTypes()) {
+ if (type instanceof GraphQLInterfaceType) {
+ TypeResolver typeResolver = codeRegistry.getTypeResolver((GraphQLInterfaceType) type);
+ }
+ }
+
+// for (ManagedViewType> managedView : entityViewManager.getMetamodel().getManagedViews()) {
+// String typeName = getObjectTypeName(managedView);
+// List fieldDefinitions = new ArrayList<>();
+// for (MethodAttribute, ?> attribute : managedView.getAttributes()) {
+// FieldDefinition fieldDefinition = new FieldDefinition(attribute.getName());
+// Type type;
+// if (attribute instanceof SingularAttribute, ?>) {
+// SingularAttribute, ?> singularAttribute = (SingularAttribute, ?>) attribute;
+// if (singularAttribute.isId()) {
+// type = getIdType(typeRegistry, singularAttribute);
+// } else {
+// type = getElementType(typeRegistry, singularAttribute);
+// }
+// } else if (attribute instanceof MapAttribute, ?, ?>) {
+// MapAttribute, ?, ?> mapAttribute = (MapAttribute, ?, ?>) attribute;
+// type = getEntryType(typeRegistry, attribute, getKeyType(typeRegistry, mapAttribute), getElementType(typeRegistry, mapAttribute));
+// } else {
+// type = new ListType(getElementType(typeRegistry, (PluralAttribute, ?, ?>) attribute));
+// }
+// fieldDefinition.setType(type);
+// fieldDefinitions.add(fieldDefinition);
+// }
+// List implementsTypes = new ArrayList<>(0);
+// List directives = new ArrayList<>(0);
+// addObjectTypeDefinition(typeRegistry, typeNameToClass, managedView, new ObjectTypeDefinition(typeName, implementsTypes, directives, fieldDefinitions));
+// }
+
+ Set serializableBasicTypes = new HashSet<>();
+// for (javax.persistence.metamodel.Type> basicType : entityViewManager.getService(EntityMetamodel.class).getBasicTypes()) {
+// for (Class> superType : ReflectionUtils.getSuperTypes(basicType.getJavaType())) {
+// serializableBasicTypes.add(superType.getName());
+// }
+//
+// serializableBasicTypes.add(basicType.getJavaType().getName());
+// }
+//
+// serializableBasicTypes.add(Serializable[].class.getName());
+// serializableBasicTypes.add(GraphQLCursor.class.getName());
+ return new GraphQLEntityViewSupport( typeNameToClass, serializableBasicTypes);
+ }
+
+// protected void addObjectTypeDefinition(TypeDefinitionRegistry typeRegistry, Map> typeNameToClass, ManagedViewType> managedView, ObjectTypeDefinition objectTypeDefinition) {
+// if (isDefineNormalTypes()) {
+// registerManagedViewType(typeRegistry, typeNameToClass, managedView, objectTypeDefinition);
+// }
+// if (isDefineRelayTypes()) {
+// List implementTypes = new ArrayList<>(objectTypeDefinition.getImplements());
+// if (isImplementRelayNode()) {
+// implementTypes.add(new TypeName("Node"));
+// }
+// ObjectTypeDefinition nodeType = new ObjectTypeDefinition(objectTypeDefinition.getName() + "Node", implementTypes, objectTypeDefinition.getDirectives(), objectTypeDefinition.getFieldDefinitions());
+//
+// if (!typeRegistry.getType("Node").isPresent() && isImplementRelayNode() && isDefineRelayNodeIfNotExist()) {
+// List nodeFields = new ArrayList<>(4);
+// nodeFields.add(new FieldDefinition("id", new NonNullType(new TypeName("ID"))));
+// typeRegistry.add(new InterfaceTypeDefinition("Node", nodeFields, new ArrayList<>()));
+// }
+//
+// List edgeFields = new ArrayList<>(2);
+// edgeFields.add(new FieldDefinition("node", new NonNullType(new TypeName(nodeType.getName()))));
+// edgeFields.add(new FieldDefinition("cursor", new NonNullType(new TypeName("String"))));
+// ObjectTypeDefinition edgeType = new ObjectTypeDefinition(objectTypeDefinition.getName() + "Edge", new ArrayList<>(), new ArrayList<>(), edgeFields);
+//
+// List connectionFields = new ArrayList<>(2);
+// connectionFields.add(new FieldDefinition("edges", new ListType(new TypeName(edgeType.getName()))));
+// connectionFields.add(new FieldDefinition("pageInfo", new NonNullType(new TypeName("PageInfo"))));
+// connectionFields.add(new FieldDefinition("totalCount", new NonNullType(new TypeName("Int"))));
+// ObjectTypeDefinition connectionType = new ObjectTypeDefinition(objectTypeDefinition.getName() + "Connection", new ArrayList<>(), new ArrayList<>(), connectionFields);
+//
+// if (!typeRegistry.getType("PageInfo").isPresent() && isDefineRelayNodeIfNotExist()) {
+// List pageInfoFields = new ArrayList<>(4);
+// pageInfoFields.add(new FieldDefinition("hasNextPage", new NonNullType(new TypeName("Boolean"))));
+// pageInfoFields.add(new FieldDefinition("hasPreviousPage", new NonNullType(new TypeName("Boolean"))));
+// pageInfoFields.add(new FieldDefinition("startCursor", new TypeName("String")));
+// pageInfoFields.add(new FieldDefinition("endCursor", new TypeName("String")));
+// typeRegistry.add(new ObjectTypeDefinition("PageInfo", new ArrayList<>(), new ArrayList<>(), pageInfoFields));
+// }
+//
+// registerManagedViewType(typeRegistry, typeNameToClass, managedView, nodeType);
+// registerManagedViewType(typeRegistry, typeNameToClass, managedView, edgeType);
+// registerManagedViewType(typeRegistry, typeNameToClass, managedView, connectionType);
+// }
+// }
+
+ protected void registerManagedViewType(TypeDefinitionRegistry typeRegistry, Map> typeNameToClass, ManagedViewType> managedView, ObjectTypeDefinition objectTypeDefinition) {
+ typeRegistry.add(objectTypeDefinition);
+ Class> old;
+ if ((old = typeNameToClass.put(objectTypeDefinition.getName(), managedView.getJavaType())) != null) {
+ throw new IllegalArgumentException("Type with name '" + objectTypeDefinition.getName() + "' is registered multiple times: [" + old.getName() + ", " + managedView.getJavaType().getName() + "]!");
+ }
+ }
+
+ /**
+ * Return the GraphQL id type for the given singular attribute.
+ *
+ * @param typeRegistry The type registry
+ * @param singularAttribute The singular attribute
+ * @return The type
+ */
+ protected Type getIdType(TypeDefinitionRegistry typeRegistry, SingularAttribute, ?> singularAttribute) {
+ return new NonNullType(new TypeName("ID"));
+ }
+
+// /**
+// * Return the GraphQL entry type for the given map attribute with the given key and value types.
+// *
+// * @param typeRegistry The type registry
+// * @param attribute The map attribute
+// * @param key The key type
+// * @param value The value type
+// * @return The type
+// */
+// protected Type getEntryType(TypeDefinitionRegistry typeRegistry, MethodAttribute, ?> attribute, Type key, Type value) {
+// String entryName = getObjectTypeName(attribute.getDeclaringType()) + StringUtils.firstToLower(attribute.getName()) + "Entry";
+// List fields = new ArrayList<>();
+// fields.add(new FieldDefinition("key", key));
+// fields.add(new FieldDefinition("value", value));
+// typeRegistry.add(new ObjectTypeDefinition(entryName, new ArrayList<>(), new ArrayList<>(), fields));
+// return new ListType(new TypeName(entryName));
+// }
+
+ /**
+ * Returns the GraphQL type name for the given managed view type.
+ *
+ * @param type The managed view type
+ * @return The GraphQL type name
+ */
+ protected String getObjectTypeName(ManagedViewType type) {
+ return type.getJavaType().getSimpleName();
+ }
+
+ /**
+ * Returns the GraphQL type name for the given java type.
+ *
+ * @param type The java type
+ * @return The GraphQL type name
+ */
+ protected String getTypeName(Class> type) {
+ return type.getSimpleName();
+ }
+
+ /**
+ * Return the GraphQL type for the given managed view type.
+ *
+ * @param type The managed view type
+ * @return The type
+ */
+ protected Type getObjectType(ManagedViewType type) {
+ return new TypeName(getObjectTypeName(type));
+ }
+
+// /**
+// * Return the GraphQL type for the given singular attribute.
+// *
+// * @param typeRegistry The type registry
+// * @param singularAttribute The singular attribute
+// * @return The type
+// */
+// protected Type getElementType(TypeDefinitionRegistry typeRegistry, SingularAttribute, ?> singularAttribute) {
+// com.blazebit.persistence.view.metamodel.Type elementType = singularAttribute.getType();
+// if (elementType.getMappingType() == com.blazebit.persistence.view.metamodel.Type.MappingType.BASIC) {
+// return getScalarType(typeRegistry, elementType.getJavaType());
+// } else {
+// return getObjectType((ManagedViewType>) elementType);
+// }
+// }
+//
+// /**
+// * Return the GraphQL type for the given plural attribute.
+// *
+// * @param typeRegistry The type registry
+// * @param pluralAttribute The plural attribute
+// * @return The type
+// */
+// protected Type getElementType(TypeDefinitionRegistry typeRegistry, PluralAttribute, ?, ?> pluralAttribute) {
+// com.blazebit.persistence.view.metamodel.Type elementType = pluralAttribute.getElementType();
+// if (elementType.getMappingType() == com.blazebit.persistence.view.metamodel.Type.MappingType.BASIC) {
+// return getScalarType(typeRegistry, elementType.getJavaType());
+// } else {
+// return getObjectType((ManagedViewType>) elementType);
+// }
+// }
+//
+// /**
+// * Return the GraphQL type for the key of the given map attribute.
+// *
+// * @param typeRegistry The type registry
+// * @param mapAttribute The map attribute
+// * @return The type
+// */
+// protected Type getKeyType(TypeDefinitionRegistry typeRegistry, MapAttribute, ?, ?> mapAttribute) {
+// com.blazebit.persistence.view.metamodel.Type elementType = mapAttribute.getKeyType();
+// if (elementType.getMappingType() == com.blazebit.persistence.view.metamodel.Type.MappingType.BASIC) {
+// return getScalarType(typeRegistry, elementType.getJavaType());
+// } else {
+// return getObjectType((ManagedViewType>) elementType);
+// }
+// }
+//
+// /**
+// * Return the GraphQL type for the given scalar java type.
+// *
+// * @param typeRegistry The type registry
+// * @param javaType The java type
+// * @return The type
+// */
+// protected Type getScalarType(TypeDefinitionRegistry typeRegistry, Class> javaType) {
+// String typeName = TYPES.get(javaType);
+// if (typeName == null) {
+// if (javaType.isEnum()) {
+// typeName = getTypeName(javaType);
+// if (typeRegistry.getType(typeName).isPresent()) {
+// throw new IllegalArgumentException("Enum type with name '" + typeName + "' is registered multiple times: " + javaType.getName());
+// } else {
+// List enumValueDefinitions = new ArrayList<>();
+// for (Enum> enumConstant : (Enum>[]) javaType.getEnumConstants()) {
+// enumValueDefinitions.add(new EnumValueDefinition(enumConstant.name(), new ArrayList<>(0)));
+// }
+//
+// typeRegistry.add(new EnumTypeDefinition(typeName, enumValueDefinitions, new ArrayList<>(0)));
+// }
+// } else {
+// typeName = "String";
+// }
+// }
+// return new TypeName(typeName);
+// }
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/GraphQLProducer.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/GraphQLProducer.java
new file mode 100644
index 0000000000..32fe2a1a20
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/GraphQLProducer.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql;
+
+import graphql.schema.GraphQLSchema;
+import io.smallrye.graphql.schema.model.Operation;
+import io.smallrye.graphql.schema.model.Reference;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.enterprise.event.Observes;
+import javax.enterprise.inject.Produces;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+@ApplicationScoped
+public class GraphQLProducer {
+
+// @Inject
+// EntityViewManager evm;
+
+ Set interfaceReferences = new HashSet<>();
+ GraphQLEntityViewSupport graphQLEntityViewSupport;
+
+ void alterOperation(@Observes Operation operation) {
+// List references = new ArrayList<>();
+// references.add(operation.getReference());
+// while (!references.isEmpty()) {
+// Reference reference = references.remove(references.size() - 1);
+// if (reference.getType() == ReferenceType.INTERFACE) {
+// interfaceReferences.add(reference);
+// }
+// Map parametrizedTypeArguments = reference.getParametrizedTypeArguments();
+// if (parametrizedTypeArguments != null) {
+// references.addAll(parametrizedTypeArguments.values());
+// }
+// }
+ }
+
+ void configure(@Observes GraphQLSchema.Builder schemaBuilder) {
+// for (Reference interfaceReference : interfaceReferences) {
+// schemaBuilder.additionalType(
+// GraphQLObjectType.newObject()
+// .name(interfaceReference.getName() + "Impl")
+// .withInterface(GraphQLTypeReference.typeRef(interfaceReference.getName()))
+// .build()
+// );
+// }
+
+ GraphQLEntityViewSupportFactory graphQLEntityViewSupportFactory = new GraphQLEntityViewSupportFactory(true, true);
+ graphQLEntityViewSupportFactory.setImplementRelayNode(false);
+ graphQLEntityViewSupportFactory.setDefineRelayNodeIfNotExist(true);
+ this.graphQLEntityViewSupport = graphQLEntityViewSupportFactory.create(schemaBuilder, null);
+ }
+
+ @Produces
+ @ApplicationScoped
+ GraphQLEntityViewSupport graphQLEntityViewSupport() {
+ return graphQLEntityViewSupport;
+ }
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/model/Cat.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/model/Cat.java
new file mode 100644
index 0000000000..278dfc5516
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/model/Cat.java
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.model;
+
+import com.fasterxml.jackson.annotation.JsonIgnore;
+import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
+
+import javax.persistence.Entity;
+import javax.persistence.FetchType;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.ManyToMany;
+import javax.persistence.ManyToOne;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+@Entity
+public class Cat {
+
+ @Id
+ @GeneratedValue
+ private Long id;
+ private String name;
+ private Integer age;
+ @JsonIgnoreProperties("kittens")
+ @ManyToOne(fetch = FetchType.LAZY, optional = true)
+ private Person owner;
+ @ManyToOne(fetch = FetchType.LAZY, optional = true)
+ private Cat mother;
+ @ManyToOne(fetch = FetchType.LAZY, optional = true)
+ private Cat father;
+ @JsonIgnore
+ @ManyToMany
+ private Set kittens = new HashSet<>();
+
+ public Cat() {
+ }
+
+ public Cat(String name, Integer age, Person owner) {
+ this.name = name;
+ this.age = age;
+ this.owner = owner;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Integer getAge() {
+ return age;
+ }
+
+ public void setAge(Integer age) {
+ this.age = age;
+ }
+
+ public Person getOwner() {
+ return owner;
+ }
+
+ public void setOwner(Person owner) {
+ this.owner = owner;
+ }
+
+ public Cat getMother() {
+ return mother;
+ }
+
+ public void setMother(Cat mother) {
+ this.mother = mother;
+ }
+
+ public Cat getFather() {
+ return father;
+ }
+
+ public void setFather(Cat father) {
+ this.father = father;
+ }
+
+ public Set getKittens() {
+ return kittens;
+ }
+
+ public void setKittens(Set kittens) {
+ this.kittens = kittens;
+ }
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/model/Person.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/model/Person.java
new file mode 100644
index 0000000000..621a37e24e
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/model/Person.java
@@ -0,0 +1,70 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.model;
+
+import javax.persistence.Entity;
+import javax.persistence.GeneratedValue;
+import javax.persistence.Id;
+import javax.persistence.OneToMany;
+import java.util.HashSet;
+import java.util.Set;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+@Entity
+public class Person {
+
+ @Id
+ @GeneratedValue
+ private Long id;
+ private String name;
+ @OneToMany(mappedBy = "owner")
+ private Set kittens = new HashSet<>();
+
+ public Person() {
+ }
+
+ public Person(String name) {
+ this.name = name;
+ }
+
+ public Long getId() {
+ return id;
+ }
+
+ public void setId(Long id) {
+ this.id = id;
+ }
+
+ public String getName() {
+ return name;
+ }
+
+ public void setName(String name) {
+ this.name = name;
+ }
+
+ public Set getKittens() {
+ return kittens;
+ }
+
+ public void setKittens(Set kittens) {
+ this.kittens = kittens;
+ }
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/repository/CatViewRepository.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/repository/CatViewRepository.java
new file mode 100644
index 0000000000..6115706fb3
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/repository/CatViewRepository.java
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.repository;
+
+import com.blazebit.persistence.CriteriaBuilder;
+import com.blazebit.persistence.CriteriaBuilderFactory;
+import com.blazebit.persistence.view.EntityViewManager;
+import com.blazebit.persistence.view.EntityViewSetting;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import java.util.List;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+@ApplicationScoped
+public class CatViewRepository {
+
+ @Inject
+ EntityManager em;
+ @Inject
+ CriteriaBuilderFactory cbf;
+ @Inject
+ EntityViewManager evm;
+
+ public T findById(EntityViewSetting> setting, Long id) {
+ return evm.find(em, setting, id);
+ }
+
+ public List findAll(EntityViewSetting setting) {
+ return evm.applySetting(setting, cbf.create(em, evm.getMetamodel().managedView(setting.getEntityViewClass()).getEntityClass())).getResultList();
+ }
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/resource/CatResource.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/resource/CatResource.java
new file mode 100644
index 0000000000..8b980c7a7c
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/resource/CatResource.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.resource;
+
+import com.blazebit.persistence.CriteriaBuilder;
+import com.blazebit.persistence.CriteriaBuilderFactory;
+import com.blazebit.persistence.examples.microprofile.graphql.GraphQLEntityViewSupport;
+import com.blazebit.persistence.examples.microprofile.graphql.model.Cat;
+import com.blazebit.persistence.examples.microprofile.graphql.view.CatSimpleView;
+import com.blazebit.persistence.examples.microprofile.graphql.view.CatUpdateView;
+import com.blazebit.persistence.examples.microprofile.graphql.view.CatWithOwnerView;
+import com.blazebit.persistence.integration.graphql.GraphQLRelayConnection;
+import com.blazebit.persistence.integration.jaxrs.EntityViewId;
+import com.blazebit.persistence.view.EntityViewManager;
+import com.blazebit.persistence.view.EntityViewSetting;
+import com.blazebit.persistence.view.Sorters;
+import graphql.schema.DataFetchingEnvironment;
+import io.smallrye.graphql.api.Context;
+import org.eclipse.microprofile.graphql.GraphQLApi;
+import org.eclipse.microprofile.graphql.Query;
+
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.transaction.Transactional;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import java.net.URI;
+import java.util.List;
+
+/**
+ * @author Moritz Becker
+ * @since 1.5.0
+ */
+@GraphQLApi
+@Path("cats")
+public class CatResource {
+
+ @Inject
+ EntityManager em;
+ @Inject
+ EntityViewManager evm;
+ @Inject
+ CriteriaBuilderFactory cbf;
+ @Inject
+ Context context;
+ @Inject
+ GraphQLEntityViewSupport graphQLEntityViewSupport;
+
+ @Transactional
+ @PUT
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public CatSimpleView updateCat(@EntityViewId("id") CatUpdateView catUpdateView) {
+ evm.save(em, catUpdateView);
+ return evm.find(em, CatSimpleView.class, catUpdateView.getId());
+ }
+
+ @POST
+ @Transactional
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response addCat(CatUpdateView view) {
+ evm.save(em, view);
+ return Response.created(URI.create("/cats/" + view.getId())).build();
+ }
+
+ @GET
+ @Query("cats")
+ @Transactional
+ @Produces(MediaType.APPLICATION_JSON)
+ public List getCats() {
+ CriteriaBuilder cb = cbf.create(em, Cat.class);
+ return evm.applySetting(graphQLEntityViewSupport.createSetting(context.unwrap(DataFetchingEnvironment.class)), cb).getResultList();
+ }
+
+ @Query("catViews")
+ public GraphQLRelayConnection findAll() {
+ CriteriaBuilder cb = cbf.create(em, Cat.class);
+ EntityViewSetting> setting = graphQLEntityViewSupport.createSetting(
+ context.unwrap(DataFetchingEnvironment.class)
+ );
+ setting.addAttributeSorter("id", Sorters.ascending());
+ if (setting.getMaxResults() == 0) {
+ return new GraphQLRelayConnection<>();
+ }
+ return new GraphQLRelayConnection<>(evm.applySetting(setting, cb).getResultList());
+ }
+
+ @DELETE
+ @Transactional
+ public Response clearCats() {
+ cbf.delete(em, Cat.class).executeUpdate();
+ return Response.ok().build();
+ }
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/resource/PersonResource.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/resource/PersonResource.java
new file mode 100644
index 0000000000..b6850a6290
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/resource/PersonResource.java
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.resource;
+
+import java.net.URI;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.transaction.Transactional;
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+
+import com.blazebit.persistence.CriteriaBuilderFactory;
+import com.blazebit.persistence.examples.microprofile.graphql.model.Person;
+import com.blazebit.persistence.examples.microprofile.graphql.view.PersonCreateView;
+import com.blazebit.persistence.examples.microprofile.graphql.view.PersonSimpleView;
+import com.blazebit.persistence.examples.microprofile.graphql.view.PersonUpdateView;
+import com.blazebit.persistence.integration.jaxrs.EntityViewId;
+import com.blazebit.persistence.view.EntityViewManager;
+
+/**
+ * @author Moritz Becker
+ * @since 1.5.0
+ */
+@Path("persons")
+public class PersonResource {
+
+ @Inject
+ private EntityManager em;
+ @Inject
+ private EntityViewManager evm;
+ @Inject
+ private CriteriaBuilderFactory cbf;
+
+ @Transactional
+ @PUT
+ @Path("{id}")
+ @Consumes(MediaType.APPLICATION_JSON)
+ @Produces(MediaType.APPLICATION_JSON)
+ public PersonSimpleView updatePerson(@EntityViewId("id") PersonUpdateView personUpdateView) {
+ evm.save(em, personUpdateView);
+ return evm.find(em, PersonSimpleView.class, personUpdateView.getId());
+ }
+
+ @POST
+ @Transactional
+ @Produces(MediaType.APPLICATION_JSON)
+ public Response addPerson(PersonCreateView view) {
+ evm.save(em, view);
+ return Response.created(URI.create("/persons/" + view.getId())).build();
+ }
+
+ @DELETE
+ @Transactional
+ public Response clearPersons() {
+ cbf.delete(em, Person.class).executeUpdate();
+ return Response.ok().build();
+ }
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/CatCreateView.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/CatCreateView.java
new file mode 100644
index 0000000000..3185ffe06d
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/CatCreateView.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.view;
+
+import com.blazebit.persistence.examples.microprofile.graphql.model.Cat;
+import com.blazebit.persistence.view.CreatableEntityView;
+import com.blazebit.persistence.view.EntityView;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+@CreatableEntityView
+@EntityView(Cat.class)
+public interface CatCreateView extends CatUpdateView {
+
+ PersonIdView getOwner();
+
+ void setOwner(PersonIdView owner);
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/CatSimpleView.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/CatSimpleView.java
new file mode 100644
index 0000000000..3780c2a1c2
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/CatSimpleView.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.view;
+
+import com.blazebit.persistence.examples.microprofile.graphql.model.Cat;
+import com.blazebit.persistence.view.EntityView;
+import com.blazebit.persistence.view.IdMapping;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+@EntityView(Cat.class)
+public interface CatSimpleView {
+
+ @IdMapping
+ Long getId();
+
+ String getName();
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/CatUpdateView.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/CatUpdateView.java
new file mode 100644
index 0000000000..15c6956154
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/CatUpdateView.java
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.view;
+
+import com.blazebit.persistence.examples.microprofile.graphql.model.Cat;
+import com.blazebit.persistence.view.EntityView;
+import com.blazebit.persistence.view.UpdatableEntityView;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+@UpdatableEntityView
+@EntityView(Cat.class)
+public interface CatUpdateView extends CatSimpleView {
+
+ void setName(String name);
+
+ Integer getAge();
+
+ void setAge(Integer age);
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/CatWithOwnerView.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/CatWithOwnerView.java
new file mode 100644
index 0000000000..29784ad79d
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/CatWithOwnerView.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.view;
+
+import com.blazebit.persistence.examples.microprofile.graphql.model.Cat;
+import com.blazebit.persistence.view.EntityView;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+@EntityView(Cat.class)
+public interface CatWithOwnerView extends CatSimpleView {
+
+ PersonSimpleView getOwner();
+
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/PersonCreateView.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/PersonCreateView.java
new file mode 100644
index 0000000000..bb6f24d1dc
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/PersonCreateView.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.view;
+
+import com.blazebit.persistence.examples.microprofile.graphql.model.Person;
+import com.blazebit.persistence.view.CreatableEntityView;
+import com.blazebit.persistence.view.EntityView;
+
+/**
+ * @author Moritz Becker
+ * @since 1.5.0
+ */
+@CreatableEntityView
+@EntityView(Person.class)
+public interface PersonCreateView extends PersonSimpleView {
+
+ void setId(Long id);
+
+ void setName(String name);
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/PersonIdView.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/PersonIdView.java
new file mode 100644
index 0000000000..499d16cb66
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/PersonIdView.java
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.view;
+
+import com.blazebit.persistence.examples.microprofile.graphql.model.Person;
+import com.blazebit.persistence.view.EntityView;
+import com.blazebit.persistence.view.IdMapping;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+@EntityView(Person.class)
+public interface PersonIdView {
+
+ @IdMapping
+ Long getId();
+
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/PersonSimpleView.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/PersonSimpleView.java
new file mode 100644
index 0000000000..dd09b75622
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/PersonSimpleView.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.view;
+
+import com.blazebit.persistence.examples.microprofile.graphql.model.Person;
+import com.blazebit.persistence.view.EntityView;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+@EntityView(Person.class)
+public interface PersonSimpleView extends PersonIdView {
+
+ String getName();
+
+}
diff --git a/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/PersonUpdateView.java b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/PersonUpdateView.java
new file mode 100644
index 0000000000..f9db90a90a
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/java/com/blazebit/persistence/examples/microprofile/graphql/view/PersonUpdateView.java
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.view;
+
+import com.blazebit.persistence.examples.microprofile.graphql.model.Person;
+import com.blazebit.persistence.view.EntityView;
+import com.blazebit.persistence.view.UpdatableEntityView;
+
+/**
+ * @author Moritz Becker
+ * @since 1.5.0
+ */
+@UpdatableEntityView
+@EntityView(Person.class)
+public interface PersonUpdateView extends PersonSimpleView {
+
+ void setName(String name);
+}
diff --git a/examples/microprofile-graphql/src/main/resources/META-INF/beans.xml b/examples/microprofile-graphql/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000000..09247f2450
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,17 @@
+
+
+
\ No newline at end of file
diff --git a/examples/microprofile-graphql/src/main/resources/application.properties b/examples/microprofile-graphql/src/main/resources/application.properties
new file mode 100644
index 0000000000..52be77dcfc
--- /dev/null
+++ b/examples/microprofile-graphql/src/main/resources/application.properties
@@ -0,0 +1,6 @@
+quarkus.datasource.db-kind=h2
+quarkus.datasource.username=username-default
+quarkus.datasource.jdbc.url=jdbc:h2:mem:test
+quarkus.hibernate-orm.database.generation=drop-and-create
+quarkus.hibernate-orm.log.sql=false
+quarkus.smallrye-graphql.events.enabled=true
\ No newline at end of file
diff --git a/examples/microprofile-graphql/src/test/java/com/blazebit/persistence/examples/microprofile/graphql/repository/AbstractSampleTest.java b/examples/microprofile-graphql/src/test/java/com/blazebit/persistence/examples/microprofile/graphql/repository/AbstractSampleTest.java
new file mode 100644
index 0000000000..dac703c901
--- /dev/null
+++ b/examples/microprofile-graphql/src/test/java/com/blazebit/persistence/examples/microprofile/graphql/repository/AbstractSampleTest.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.repository;
+
+import com.blazebit.persistence.examples.microprofile.graphql.model.Cat;
+import com.blazebit.persistence.examples.microprofile.graphql.model.Person;
+import org.junit.Before;
+
+import javax.enterprise.context.ApplicationScoped;
+import javax.inject.Inject;
+import javax.persistence.EntityManager;
+import javax.transaction.Transactional;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.ThreadLocalRandom;
+import java.util.function.Consumer;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+public abstract class AbstractSampleTest {
+
+ @Inject
+ DataInitializer dataInitializer;
+
+ @Before
+ public void init() {
+ dataInitializer.run(em -> {
+ ThreadLocalRandom random = ThreadLocalRandom.current();
+ List people = new ArrayList<>();
+ for (int i = 0; i < 4; i++) {
+ Person p = new Person("Person " + i);
+ people.add(p);
+ em.persist(p);
+ }
+ for (int i = 0; i < 100; i++) {
+ Cat c = new Cat("Cat " + i, random.nextInt(20), people.get(random.nextInt(4)));
+ em.persist(c);
+ }
+ });
+ }
+
+ @Transactional
+ @ApplicationScoped
+ public static class DataInitializer {
+
+ @Inject
+ EntityManager em;
+
+ public void run(Consumer c) {
+ c.accept(em);
+ }
+ }
+}
diff --git a/examples/microprofile-graphql/src/test/java/com/blazebit/persistence/examples/microprofile/graphql/repository/H2DatabaseTestResource.java b/examples/microprofile-graphql/src/test/java/com/blazebit/persistence/examples/microprofile/graphql/repository/H2DatabaseTestResource.java
new file mode 100644
index 0000000000..bb6b18d0cb
--- /dev/null
+++ b/examples/microprofile-graphql/src/test/java/com/blazebit/persistence/examples/microprofile/graphql/repository/H2DatabaseTestResource.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.repository;
+
+import java.sql.SQLException;
+import java.util.Collections;
+import java.util.Map;
+
+import io.quarkus.test.common.QuarkusTestResourceLifecycleManager;
+import org.h2.tools.Server;
+
+/**
+ * @author Moritz Becker
+ * @since 1.5.0
+ */
+public class H2DatabaseTestResource implements QuarkusTestResourceLifecycleManager {
+
+ private Server tcpServer;
+
+ @Override
+ public Map start() {
+
+ try {
+ tcpServer = Server.createTcpServer();
+ tcpServer.start();
+ System.out.println("[INFO] H2 database started in TCP server mode; server status: " + tcpServer.getStatus());
+ } catch (SQLException e) {
+ throw new RuntimeException(e);
+ }
+ return Collections.emptyMap();
+ }
+
+ @Override
+ public synchronized void stop() {
+ if (tcpServer != null) {
+ tcpServer.stop();
+ System.out.println("[INFO] H2 database was shut down; server status: " + tcpServer.getStatus());
+ tcpServer = null;
+ }
+ }
+}
diff --git a/examples/microprofile-graphql/src/test/java/com/blazebit/persistence/examples/microprofile/graphql/repository/SampleTest.java b/examples/microprofile-graphql/src/test/java/com/blazebit/persistence/examples/microprofile/graphql/repository/SampleTest.java
new file mode 100644
index 0000000000..ac0ca2015f
--- /dev/null
+++ b/examples/microprofile-graphql/src/test/java/com/blazebit/persistence/examples/microprofile/graphql/repository/SampleTest.java
@@ -0,0 +1,102 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.repository;
+
+import org.junit.Test;
+import org.junit.jupiter.api.AfterEach;
+
+import io.quarkus.test.common.http.TestHTTPResource;
+
+import java.net.URI;
+
+import static io.restassured.RestAssured.given;
+import static org.hamcrest.CoreMatchers.is;
+
+/**
+ * @author Christian Beikov
+ * @since 1.4.0
+ */
+public class SampleTest extends AbstractSampleTest {
+
+ @TestHTTPResource
+ protected URI apiBaseUri;
+
+ @AfterEach
+ public void clearData() {
+// given().when().delete("/documents");
+// given().when().delete("/document-types");
+// given().when().delete("/persons");
+ }
+
+ @Test
+ public void testSanity() {
+ given()
+ .when().get("/graphql/schema.json")
+ .then()
+ .statusCode(200);
+ }
+
+ @Test
+ public void testRequestScope() {
+ given()
+ .contentType("application/graphql")
+ .body(request(5, null))
+ .when().post("/graphql")
+ .then()
+ .statusCode(200)
+ .body("size(data.findAll.edges)", is(5));
+// ResponseEntity response = this.restTemplate.postForEntity("/graphql", new HttpEntity<>(requestGraphQL, headers), JsonNode.class);
+//
+// JsonNode connection = response.getBody().get("data").get("findAll");
+// ArrayNode arrayNode = (ArrayNode) connection.get("edges");
+// List nodes = arrayNode.findValues("node");
+//
+// assertEquals(5, nodes.size());
+// assertEquals("Cat 0", nodes.get(0).get("name").asText());
+//
+// requestGraphQL = request(5, connection.get("pageInfo").get("endCursor").asText());
+// response = this.restTemplate.postForEntity("/graphql", new HttpEntity<>(requestGraphQL, headers), JsonNode.class);
+// connection = response.getBody().get("data").get("findAll");
+// arrayNode = (ArrayNode) connection.get("edges");
+// nodes = arrayNode.findValues("node");
+//
+// assertEquals(5, nodes.size());
+// assertEquals("Cat 5", nodes.get(0).get("name").asText());
+ }
+
+ static String request(int first, String after) {
+ String other = "";
+ if (after != null) {
+ other = ", after: \"" + after + "\"";
+ }
+ String requestGraphQL = "query {\n" +
+ " findAll(first: " + first + other + "){\n" +
+ " edges {\n" +
+ " node {\n" +
+ " id\n" +
+ " name\n" +
+ " }\n" +
+ " }\n" +
+ " pageInfo {\n" +
+ " startCursor\n" +
+ " endCursor\n" +
+ " }\n" +
+ " }\n" +
+ "}";
+ return requestGraphQL;
+ }
+}
diff --git a/examples/microprofile-graphql/src/test/java/com/blazebit/persistence/examples/microprofile/graphql/repository/TestResources.java b/examples/microprofile-graphql/src/test/java/com/blazebit/persistence/examples/microprofile/graphql/repository/TestResources.java
new file mode 100644
index 0000000000..7d7b26f3dc
--- /dev/null
+++ b/examples/microprofile-graphql/src/test/java/com/blazebit/persistence/examples/microprofile/graphql/repository/TestResources.java
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2014 - 2020 Blazebit.
+ *
+ * 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.blazebit.persistence.examples.microprofile.graphql.repository;
+
+import io.quarkus.test.common.QuarkusTestResource;
+
+/**
+ * @author Moritz Becker
+ * @since 1.5.0
+ */
+@QuarkusTestResource(H2DatabaseTestResource.class)
+public class TestResources {
+}
diff --git a/examples/pom.xml b/examples/pom.xml
index f7924ba131..4c7b5ba74a 100644
--- a/examples/pom.xml
+++ b/examples/pom.xml
@@ -36,6 +36,7 @@
spring-hateoas
it-service-management
quarkus
+ microprofile-graphql
\ No newline at end of file
diff --git a/integration/graphql/pom.xml b/integration/graphql/pom.xml
index 95e0f44afc..7322e415cd 100644
--- a/integration/graphql/pom.xml
+++ b/integration/graphql/pom.xml
@@ -52,6 +52,19 @@
+
+ org.jboss.jandex
+ jandex-maven-plugin
+ 1.0.8
+
+
+ make-index
+
+ jandex
+
+
+
+
org.moditect
moditect-maven-plugin
diff --git a/integration/graphql/src/main/java/com/blazebit/persistence/integration/graphql/GraphQLRelayConnection.java b/integration/graphql/src/main/java/com/blazebit/persistence/integration/graphql/GraphQLRelayConnection.java
index 60c7abfd59..55e9668ac4 100644
--- a/integration/graphql/src/main/java/com/blazebit/persistence/integration/graphql/GraphQLRelayConnection.java
+++ b/integration/graphql/src/main/java/com/blazebit/persistence/integration/graphql/GraphQLRelayConnection.java
@@ -22,6 +22,7 @@
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Base64;
+import java.util.Collections;
import java.util.List;
/**
@@ -38,6 +39,15 @@ public class GraphQLRelayConnection implements Serializable {
private final GraphQLRelayPageInfo pageInfo;
private final long totalCount;
+ /**
+ * Creates an empty GraphQL page.
+ */
+ public GraphQLRelayConnection() {
+ this.edges = Collections.emptyList();
+ this.pageInfo = GraphQLRelayPageInfo.EMPTY;
+ this.totalCount = 0;
+ }
+
/**
* Creates a new GraphQL page from the given list.
*
@@ -93,7 +103,7 @@ private static List> createEdges(List list, GraphQLRe
*
* @return the elements
*/
- public List> getElements() {
+ public List> getEdges() {
return edges;
}
diff --git a/integration/graphql/src/main/resources/META-INF/beans.xml b/integration/graphql/src/main/resources/META-INF/beans.xml
new file mode 100644
index 0000000000..09247f2450
--- /dev/null
+++ b/integration/graphql/src/main/resources/META-INF/beans.xml
@@ -0,0 +1,17 @@
+
+
+
\ No newline at end of file