diff --git a/demoapp/pom.xml b/demoapp/pom.xml
index 8dd3151..9d0a43f 100644
--- a/demoapp/pom.xml
+++ b/demoapp/pom.xml
@@ -6,11 +6,11 @@
org.rapid-graphql
rapid-graphql
- 0.0.3
+ 0.0.4
rapid-graphql-demoapp
- 0.0.3
+ 0.0.4
jar
demoapp
@@ -27,10 +27,22 @@
org.springframework.boot
spring-boot-starter-web
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
+
+ org.springframework.boot
+ spring-boot-starter-actuator
+
+
+ org.springframework.boot
+ spring-boot-starter-websocket
+
org.rapid-graphql
rapid-graphql-starter
- 0.0.3
+ 0.0.4
@@ -70,7 +82,6 @@
guava
31.0.1-jre
-
diff --git a/demoapp/src/main/java/org/rapidgraphql/helloworld/MySubscription.java b/demoapp/src/main/java/org/rapidgraphql/helloworld/MySubscription.java
new file mode 100644
index 0000000..6e34f4f
--- /dev/null
+++ b/demoapp/src/main/java/org/rapidgraphql/helloworld/MySubscription.java
@@ -0,0 +1,26 @@
+package org.rapidgraphql.helloworld;
+
+import graphql.kickstart.tools.GraphQLSubscriptionResolver;
+import graphql.schema.DataFetchingEnvironment;
+import lombok.extern.log4j.Log4j2;
+import org.reactivestreams.Publisher;
+import org.springframework.stereotype.Service;
+import reactor.core.publisher.Flux;
+
+import java.time.Duration;
+
+@Service
+@Log4j2
+class MySubscription implements GraphQLSubscriptionResolver {
+
+ public Publisher hello(DataFetchingEnvironment env) {
+ return Flux.range(0, 100)
+ .delayElements(Duration.ofSeconds(1))
+ .map(this::fun);
+ }
+ private Integer fun(Integer i) {
+ Integer result = i*10;
+ log.info("result={}", result);
+ return result;
+ }
+}
\ No newline at end of file
diff --git a/demoapp/src/test/java/org/rapidgraphql/app/RapidGraphQLApplicationTests.java b/demoapp/src/test/java/org/rapidgraphql/app/RapidGraphQLApplicationTests.java
index df62b0a..c921e3a 100644
--- a/demoapp/src/test/java/org/rapidgraphql/app/RapidGraphQLApplicationTests.java
+++ b/demoapp/src/test/java/org/rapidgraphql/app/RapidGraphQLApplicationTests.java
@@ -3,7 +3,7 @@
import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;
-@SpringBootTest
+@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class RapidGraphQLApplicationTests {
@Test
diff --git a/pom.xml b/pom.xml
index ced7d3f..492cc27 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,12 +5,12 @@
org.springframework.boot
spring-boot-starter-parent
- 2.6.3
+ 2.7.15
org.rapid-graphql
rapid-graphql
- 0.0.3
+ 0.0.4
rapid-graphql
Demo graphql booster app
@@ -21,11 +21,11 @@
11
- 12.0.0
- 17.3
+ 14.0.0
+ 21.0
3.21.2
- 17.0
- 2.6.3
+ 17.1
+ 2.7.15
diff --git a/starter/pom.xml b/starter/pom.xml
index f91e8ed..fbbb45a 100644
--- a/starter/pom.xml
+++ b/starter/pom.xml
@@ -6,11 +6,11 @@
org.rapid-graphql
rapid-graphql
- 0.0.3
+ 0.0.4
rapid-graphql-starter
- 0.0.3
+ 0.0.4
jar
starter
@@ -41,7 +41,10 @@
org.springframework
spring-context
-
+
+ org.springframework.boot
+ spring-boot-starter-webflux
+
org.projectlombok
lombok
@@ -71,7 +74,7 @@
com.graphql-java
graphql-java-extended-scalars
- 17.0
+ ${graphql-scalars.version}
diff --git a/starter/src/main/java/org/rapidgraphql/schemabuilder/DefinitionFactory.java b/starter/src/main/java/org/rapidgraphql/schemabuilder/DefinitionFactory.java
index af51638..2925ea3 100644
--- a/starter/src/main/java/org/rapidgraphql/schemabuilder/DefinitionFactory.java
+++ b/starter/src/main/java/org/rapidgraphql/schemabuilder/DefinitionFactory.java
@@ -1,29 +1,13 @@
package org.rapidgraphql.schemabuilder;
import graphql.VisibleForTesting;
-import graphql.kickstart.tools.GraphQLMutationResolver;
-import graphql.kickstart.tools.GraphQLQueryResolver;
-import graphql.kickstart.tools.GraphQLResolver;
-import graphql.kickstart.tools.SchemaError;
-import graphql.language.Definition;
-import graphql.language.DirectiveDefinition;
-import graphql.language.DirectiveLocation;
-import graphql.language.EnumTypeDefinition;
-import graphql.language.EnumValueDefinition;
-import graphql.language.FieldDefinition;
-import graphql.language.InputObjectTypeDefinition;
-import graphql.language.InputValueDefinition;
-import graphql.language.ListType;
-import graphql.language.NonNullType;
-import graphql.language.ObjectTypeDefinition;
-import graphql.language.ObjectTypeExtensionDefinition;
+import graphql.kickstart.tools.*;
import graphql.language.Type;
-import graphql.language.TypeName;
+import graphql.language.*;
import graphql.scalars.ExtendedScalars;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.GraphQLScalarType;
import org.checkerframework.checker.nullness.qual.NonNull;
-import org.jetbrains.annotations.NotNull;
import org.rapidgraphql.annotations.GraphQLIgnore;
import org.rapidgraphql.annotations.GraphQLInputType;
import org.rapidgraphql.directives.SecuredDirectiveWiring;
@@ -31,26 +15,12 @@
import org.springframework.util.ClassUtils;
import org.springframework.util.ReflectionUtils;
-import java.lang.reflect.AnnotatedParameterizedType;
-import java.lang.reflect.AnnotatedType;
-import java.lang.reflect.Method;
-import java.lang.reflect.Parameter;
-import java.lang.reflect.ParameterizedType;
+import java.lang.reflect.*;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.OffsetDateTime;
-import java.util.ArrayDeque;
-import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.HashSet;
-import java.util.List;
-import java.util.Map;
-import java.util.Optional;
-import java.util.Queue;
-import java.util.Set;
+import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.Future;
import java.util.function.Function;
@@ -60,11 +30,7 @@
import static java.lang.String.format;
import static java.util.Map.entry;
import static org.rapidgraphql.schemabuilder.MethodsFilter.*;
-import static org.rapidgraphql.schemabuilder.TypeUtils.actualTypeArgument;
-import static org.rapidgraphql.schemabuilder.TypeUtils.baseType;
-import static org.rapidgraphql.schemabuilder.TypeUtils.castToParameterizedType;
-import static org.rapidgraphql.schemabuilder.TypeUtils.isListType;
-import static org.rapidgraphql.schemabuilder.TypeUtils.isNotNullable;
+import static org.rapidgraphql.schemabuilder.TypeUtils.*;
import static org.slf4j.LoggerFactory.getLogger;
public class DefinitionFactory {
@@ -72,6 +38,7 @@ public class DefinitionFactory {
private static final Logger LOGGER = getLogger(DefinitionFactory.class);
public static final String QUERY_TYPE = "Query";
public static final String MUTATION_TYPE = "Mutation";
+ public static final String SUBSCRIPTION_TYPE = "Subscription";
private static final List scalars = List.of(
ExtendedScalars.GraphQLLong,
ExtendedScalars.Date,
@@ -132,10 +99,14 @@ public Definition> createTypeDefinition(GraphQLResolver> resolver) {
String name;
Class> sourceType = null;
Class> resolverType = ClassUtils.getUserClass(resolver);
+ boolean isSubscription = false;
if (resolver instanceof GraphQLQueryResolver) {
name = QUERY_TYPE;
} else if(resolver instanceof GraphQLMutationResolver) {
name = MUTATION_TYPE;
+ } else if(resolver instanceof GraphQLSubscriptionResolver) {
+ name = SUBSCRIPTION_TYPE;
+ isSubscription = true;
} else {
Optional discoveredClass = extractResolverType(resolver);
if (discoveredClass.isEmpty()) {
@@ -148,8 +119,9 @@ public Definition> createTypeDefinition(GraphQLResolver> resolver) {
LOGGER.info("Processing {} resolver: {}", name, resolverType.getName());
final Class> finalSourceType = sourceType;
+ boolean finalIsSubscription = isSubscription;
Method[] resolverDeclaredMethods = ReflectionUtils.getUniqueDeclaredMethods(resolverType,
- method -> resolverMethodFilter(finalSourceType, method));
+ method -> resolverMethodFilter(finalSourceType, method, finalIsSubscription));
List typeFields = Arrays.stream(resolverDeclaredMethods)
.map(method -> createFieldDefinition(method, skipFirstParameter))
.collect(Collectors.toList());
@@ -348,7 +320,10 @@ private Type> convertToInputGraphQLType(AnnotatedType annotatedType) {
private Type> convertToGraphQLType(AnnotatedType annotatedType, TypeKind typeKind) {
Optional parameterizedType = castToParameterizedType(annotatedType);
Type> graphqlType;
- if (parameterizedType.isPresent() && isListType(parameterizedType.get())) {
+ if (typeKind == TypeKind.OUTPUT_TYPE && parameterizedType.isPresent() && isPublisherType(parameterizedType.get())) {
+ AnnotatedType typeOfParameter = actualTypeArgument(parameterizedType.get(), 0);
+ graphqlType = convertToGraphQLType(typeOfParameter, typeKind);
+ } else if (parameterizedType.isPresent() && isListType(parameterizedType.get())) {
AnnotatedType typeOfParameter = actualTypeArgument(parameterizedType.get(), 0);
graphqlType = new ListType(convertToGraphQLType(typeOfParameter, typeKind));
} else {
diff --git a/starter/src/main/java/org/rapidgraphql/schemabuilder/GraphQLSchemaResolver.java b/starter/src/main/java/org/rapidgraphql/schemabuilder/GraphQLSchemaResolver.java
index 1d5ae71..73604f2 100644
--- a/starter/src/main/java/org/rapidgraphql/schemabuilder/GraphQLSchemaResolver.java
+++ b/starter/src/main/java/org/rapidgraphql/schemabuilder/GraphQLSchemaResolver.java
@@ -65,7 +65,9 @@ private List> processResolvers(List extends GraphQLResolver>>
definitions.addAll(definitionFactory.getScalars().stream()
.map(scalar -> ScalarTypeDefinition.newScalarTypeDefinition().name(scalar.getName()).build())
.collect(Collectors.toList()));
- definitions.addAll(resolvers.stream().map(resolver -> definitionFactory.createTypeDefinition(resolver)).collect(Collectors.toList()));
+ definitions.addAll(resolvers.stream()
+ .map(definitionFactory::createTypeDefinition)
+ .collect(Collectors.toList()));
definitions.addAll(definitionFactory.processTypesQueue());
return definitions;
}
diff --git a/starter/src/main/java/org/rapidgraphql/schemabuilder/MethodsFilter.java b/starter/src/main/java/org/rapidgraphql/schemabuilder/MethodsFilter.java
index a03863b..51c8f75 100644
--- a/starter/src/main/java/org/rapidgraphql/schemabuilder/MethodsFilter.java
+++ b/starter/src/main/java/org/rapidgraphql/schemabuilder/MethodsFilter.java
@@ -16,6 +16,7 @@
import java.util.regex.Pattern;
import static java.lang.Character.isUpperCase;
+import static org.rapidgraphql.schemabuilder.TypeUtils.isPublisherType;
import static org.slf4j.LoggerFactory.getLogger;
public class MethodsFilter {
@@ -52,7 +53,7 @@ private static boolean dataLoaderMethodFilter(Method method) {
return method.isAnnotationPresent(DataLoaderMethod.class);
}
- public static boolean resolverMethodFilter(Class> sourceType, Method method) {
+ public static boolean resolverMethodFilter(Class> sourceType, Method method, boolean isSubscription) {
if (!typeMethodFilter(method)) {
return false;
}
@@ -61,6 +62,14 @@ public static boolean resolverMethodFilter(Class> sourceType, Method method) {
method.getDeclaringClass().getName(), method.getName(), sourceType.getName());
return false;
}
+ if (isSubscription) {
+ if ( !isPublisherType(method.getReturnType()) ) {
+ LOGGER.warn("Skipping method {}::{} in subscription resolver because it doesn't return publisher type",
+ method.getDeclaringClass().getName(), method.getName());
+ return false;
+ }
+ }
+
return true;
}
diff --git a/starter/src/main/java/org/rapidgraphql/schemabuilder/RapidGraphQLContextBuilder.java b/starter/src/main/java/org/rapidgraphql/schemabuilder/RapidGraphQLContextBuilder.java
index 9b3ef88..2cb4add 100644
--- a/starter/src/main/java/org/rapidgraphql/schemabuilder/RapidGraphQLContextBuilder.java
+++ b/starter/src/main/java/org/rapidgraphql/schemabuilder/RapidGraphQLContextBuilder.java
@@ -1,40 +1,44 @@
package org.rapidgraphql.schemabuilder;
-import graphql.kickstart.execution.context.DefaultGraphQLContext;
-import graphql.kickstart.execution.context.GraphQLContext;
-import graphql.kickstart.servlet.context.DefaultGraphQLServletContext;
-import graphql.kickstart.servlet.context.DefaultGraphQLWebSocketContext;
+import graphql.kickstart.execution.context.DefaultGraphQLContextBuilder;
+import graphql.kickstart.execution.context.GraphQLKickstartContext;
import graphql.kickstart.servlet.context.GraphQLServletContextBuilder;
+//import jakarta.servlet.http.HttpServletRequest;
+//import jakarta.servlet.http.HttpServletResponse;
+//import jakarta.websocket.Session;
+//import jakarta.websocket.server.HandshakeRequest;
import org.dataloader.DataLoaderRegistry;
-import org.springframework.stereotype.Component;
-
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.websocket.Session;
import javax.websocket.server.HandshakeRequest;
-public class RapidGraphQLContextBuilder implements GraphQLServletContextBuilder {
+import java.util.HashMap;
+import java.util.Map;
+
+public class RapidGraphQLContextBuilder extends DefaultGraphQLContextBuilder
+ implements GraphQLServletContextBuilder {
private final DataLoaderRegistryFactory dataLoaderRegistryFactory;
public RapidGraphQLContextBuilder(DataLoaderRegistryFactory dataLoaderRegistryFactory) {
this.dataLoaderRegistryFactory = dataLoaderRegistryFactory;
}
- @Override
- public GraphQLContext build(HttpServletRequest req, HttpServletResponse response) {
- return DefaultGraphQLServletContext.createServletContext(buildDataLoaderRegistry(), null).with(req).with(response)
- .build();
- }
@Override
- public GraphQLContext build() {
- return new DefaultGraphQLContext(buildDataLoaderRegistry(), null);
+ public GraphQLKickstartContext build(HttpServletRequest request, HttpServletResponse response) {
+ Map