Skip to content

Commit 0029b6e

Browse files
authored
Merge branch 'master' into timward/unused-types
2 parents 4b83c0b + d00c7bf commit 0029b6e

24 files changed

+310
-96
lines changed

.github/workflows/pull-request.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ jobs:
77
runs-on: ubuntu-latest
88
strategy:
99
matrix:
10-
java: [ '8', '11', '15' ]
10+
java: [ '11', '15', '17' ]
1111
steps:
1212
- name: Checkout
1313
uses: actions/checkout@v3

.github/workflows/release.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ jobs:
99
- name: Setup java
1010
uses: actions/setup-java@v3
1111
with:
12-
java-version: '8'
12+
java-version: '11'
1313
distribution: 'adopt'
1414
- name: Build with Maven
1515
run: mvn --batch-mode --update-snapshots verify
@@ -23,7 +23,7 @@ jobs:
2323
- name: Setup Maven Central
2424
uses: actions/setup-java@v3
2525
with:
26-
java-version: '8'
26+
java-version: '11'
2727
distribution: 'adopt'
2828
server-id: ossrh
2929
server-username: MAVEN_USERNAME
@@ -38,7 +38,7 @@ jobs:
3838
GPG_PASSPHRASE: ${{ secrets.GPG_SIGNING_PASSWORD }}
3939
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
4040
- name: Create Release PR
41-
uses: peter-evans/create-pull-request@v4
41+
uses: peter-evans/create-pull-request@v5
4242
with:
4343
branch: version-release
4444
title: Version Release

.github/workflows/snapshot.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ jobs:
1212
- name: Setup java
1313
uses: actions/setup-java@v3
1414
with:
15-
java-version: '8'
15+
java-version: '11'
1616
distribution: 'adopt'
1717
- name: Build with Maven
1818
run: mvn --batch-mode --update-snapshots verify
@@ -26,7 +26,7 @@ jobs:
2626
- name: Setup Maven Central
2727
uses: actions/setup-java@v3
2828
with:
29-
java-version: '8'
29+
java-version: '11'
3030
distribution: 'adopt'
3131
server-id: ossrh
3232
server-username: MAVEN_USERNAME

pom.xml

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,11 @@
1313

1414
<properties>
1515
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
16-
<java.version>1.8</java.version>
17-
<kotlin.version>1.8.10</kotlin.version>
16+
<java.version>11</java.version>
17+
<kotlin.version>1.8.21</kotlin.version>
1818
<kotlin-coroutines.version>1.6.4</kotlin-coroutines.version>
1919
<jackson.version>2.14.2</jackson.version>
20-
<graphql-java.version>20.0</graphql-java.version>
20+
<graphql-java.version>21.0</graphql-java.version>
2121
<reactive-streams.version>1.0.4</reactive-streams.version>
2222

2323
<maven.compiler.source>${java.version}</maven.compiler.source>
@@ -100,7 +100,7 @@
100100
<dependency>
101101
<groupId>ch.qos.logback</groupId>
102102
<artifactId>logback-classic</artifactId>
103-
<version>1.3.5</version>
103+
<version>1.3.6</version>
104104
</dependency>
105105
<dependency>
106106
<groupId>javax.servlet</groupId>
@@ -279,20 +279,20 @@
279279
<artifactId>maven-compiler-plugin</artifactId>
280280
<version>3.11.0</version>
281281
<configuration>
282-
<source>1.8</source>
283-
<target>1.8</target>
282+
<source>11</source>
283+
<target>11</target>
284284
</configuration>
285285
</plugin>
286286

287287
<plugin>
288288
<groupId>org.apache.maven.plugins</groupId>
289289
<artifactId>maven-surefire-plugin</artifactId>
290-
<version>2.22.2</version>
290+
<version>3.0.0</version>
291291
<dependencies>
292292
<dependency>
293293
<groupId>org.apache.maven.surefire</groupId>
294294
<artifactId>surefire-junit4</artifactId>
295-
<version>2.22.2</version>
295+
<version>3.0.0</version>
296296
</dependency>
297297
</dependencies>
298298
<configuration>

src/main/kotlin/graphql/kickstart/tools/GenericType.kt

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -83,8 +83,7 @@ internal open class GenericType(protected val mostSpecificType: JavaType, protec
8383
* Unwrap certain Java types to find the "real" class.
8484
*/
8585
fun unwrapGenericType(javaType: JavaType): JavaType {
86-
val type = replaceTypeVariable(javaType)
87-
return when (type) {
86+
return when (val type = replaceTypeVariable(javaType)) {
8887
is ParameterizedType -> {
8988
val rawType = type.rawType
9089
val genericType = options.genericWrappers.find { it.type == rawType }
@@ -107,7 +106,7 @@ internal open class GenericType(protected val mostSpecificType: JavaType, protec
107106
}
108107
}
109108
is WildcardType -> type.upperBounds.firstOrNull()
110-
?: throw error("Unable to unwrap type, wildcard has no upper bound: $type")
109+
?: error("Unable to unwrap type, wildcard has no upper bound: $type")
111110
is Class<*> -> if (type.isPrimitive) Primitives.wrap(type) else type
112111
else -> error("Unable to unwrap type: $type")
113112
}

src/main/kotlin/graphql/kickstart/tools/SchemaClassScanner.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -177,7 +177,7 @@ internal class SchemaClassScanner(
177177
.coercing(provided.coercing)
178178
.definition(definition)
179179
.build()
180-
}.associateBy { it.name!! }
180+
}.associateBy { it.name }
181181

182182
val unusedDefinitions = (definitionsByName.values - observedDefinitions).toSet()
183183
unusedDefinitions

src/main/kotlin/graphql/kickstart/tools/SchemaParserBuilder.kt

Lines changed: 28 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import graphql.language.Definition
44
import graphql.language.Document
55
import graphql.parser.MultiSourceReader
66
import graphql.parser.Parser
7+
import graphql.parser.ParserEnvironment
78
import graphql.parser.ParserOptions
89
import graphql.schema.GraphQLScalarType
910
import graphql.schema.idl.RuntimeWiring
@@ -25,6 +26,10 @@ class SchemaParserBuilder {
2526
private val scalars = mutableListOf<GraphQLScalarType>()
2627
private val runtimeWiringBuilder = RuntimeWiring.newRuntimeWiring()
2728
private var options = SchemaParserOptions.defaultOptions()
29+
private val parser = Parser()
30+
private val parserOptions = ParserOptions
31+
.getDefaultParserOptions()
32+
.transform { o -> o.maxTokens(MAX_VALUE) }
2833

2934
/**
3035
* Add GraphQL schema files from the classpath.
@@ -166,21 +171,14 @@ class SchemaParserBuilder {
166171
}
167172

168173
private fun parseDocuments(): List<Document> {
169-
val parser = Parser()
170-
val documents = mutableListOf<Document>()
171174
try {
172-
val options = ParserOptions
173-
.getDefaultParserOptions()
174-
.transform { o -> o.maxTokens(MAX_VALUE) }
175+
val documents = files.map { parseDocument(readFile(it), it) }.toMutableList()
175176

176-
files.forEach {
177-
val sourceReader = MultiSourceReader.newMultiSourceReader().string(readFile(it), it).trackData(true).build()
178-
documents.add(parser.parseDocument(sourceReader, options))
177+
if (schemaString.isNotBlank()) {
178+
documents.add(parseDocument(schemaString.toString()))
179179
}
180180

181-
if (schemaString.isNotEmpty()) {
182-
documents.add(parser.parseDocument(schemaString.toString(), options))
183-
}
181+
return documents
184182
} catch (pce: ParseCancellationException) {
185183
val cause = pce.cause
186184
if (cause != null && cause is RecognitionException) {
@@ -189,23 +187,34 @@ class SchemaParserBuilder {
189187
throw pce
190188
}
191189
}
192-
return documents
193190
}
194191

195-
private fun readFile(filename: String): String {
196-
return java.io.BufferedReader(java.io.InputStreamReader(
197-
object : Any() {}.javaClass.classLoader.getResourceAsStream(filename)
198-
?: throw java.io.FileNotFoundException("classpath:$filename")
199-
)).readText()
192+
private fun parseDocument(input: String, sourceName: String? = null): Document {
193+
val sourceReader = MultiSourceReader
194+
.newMultiSourceReader()
195+
.string(input, sourceName)
196+
.trackData(true).build()
197+
val environment = ParserEnvironment
198+
.newParserEnvironment()
199+
.document(sourceReader)
200+
.parserOptions(parserOptions).build()
201+
return parser.parseDocument(environment)
200202
}
201203

204+
private fun readFile(filename: String) =
205+
this::class.java.classLoader.getResource(filename)?.readText()
206+
?: throw java.io.FileNotFoundException("classpath:$filename")
207+
202208
/**
203209
* Build the parser with the supplied schema and dictionary.
204210
*/
205211
fun build() = SchemaParser(scan(), options, runtimeWiringBuilder.build())
206212
}
207213

208-
class InvalidSchemaError(pce: ParseCancellationException, private val recognitionException: RecognitionException) : RuntimeException(pce) {
209-
override val message: String?
214+
class InvalidSchemaError(
215+
pce: ParseCancellationException,
216+
private val recognitionException: RecognitionException
217+
) : RuntimeException(pce) {
218+
override val message: String
210219
get() = "Invalid schema provided (${recognitionException.javaClass.name}) at: ${recognitionException.offendingToken}"
211220
}

src/main/kotlin/graphql/kickstart/tools/SchemaParserOptions.kt

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package graphql.kickstart.tools
33
import com.fasterxml.jackson.databind.ObjectMapper
44
import graphql.kickstart.tools.proxy.*
55
import graphql.kickstart.tools.relay.RelayConnectionFactory
6+
import graphql.kickstart.tools.resolver.MissingResolverDataFetcherProvider
67
import graphql.kickstart.tools.util.JavaType
78
import graphql.kickstart.tools.util.ParameterizedTypeImpl
89
import graphql.schema.DataFetcher
@@ -23,7 +24,9 @@ data class SchemaParserOptions internal constructor(
2324
val contextClass: Class<*>?,
2425
val genericWrappers: List<GenericWrapper>,
2526
val allowUnimplementedResolvers: Boolean,
27+
@Deprecated("Use missingResolverDataFetcherProvider instead.")
2628
val missingResolverDataFetcher: DataFetcher<Any?>?,
29+
val missingResolverDataFetcherProvider: MissingResolverDataFetcherProvider?,
2730
val objectMapperProvider: PerFieldObjectMapperProvider,
2831
val proxyHandlers: List<ProxyHandler>,
2932
val inputArgumentOptionalDetectOmission: Boolean,
@@ -53,6 +56,7 @@ data class SchemaParserOptions internal constructor(
5356
private var useDefaultGenericWrappers = true
5457
private var allowUnimplementedResolvers = false
5558
private var missingResolverDataFetcher: DataFetcher<Any?>? = null
59+
private var missingResolverDataFetcherProvider: MissingResolverDataFetcherProvider? = null
5660
private var objectMapperProvider: PerFieldObjectMapperProvider = PerFieldConfiguringObjectMapperProvider()
5761
private val proxyHandlers: MutableList<ProxyHandler> = mutableListOf(Spring4AopProxyHandler(), GuiceAopProxyHandler(), JavassistProxyHandler(), WeldProxyHandler())
5862
private var inputArgumentOptionalDetectOmission = false
@@ -88,10 +92,15 @@ data class SchemaParserOptions internal constructor(
8892
this.allowUnimplementedResolvers = allowUnimplementedResolvers
8993
}
9094

95+
@Deprecated("Use missingResolverDataFetcherProvider instead.")
9196
fun missingResolverDataFetcher(missingResolverDataFetcher: DataFetcher<Any?>?) = this.apply {
9297
this.missingResolverDataFetcher = missingResolverDataFetcher
9398
}
9499

100+
fun missingResolverDataFetcherProvider(missingResolverDataFetcherProvider: MissingResolverDataFetcherProvider?) = this.apply {
101+
this.missingResolverDataFetcherProvider = missingResolverDataFetcherProvider
102+
}
103+
95104
fun inputArgumentOptionalDetectOmission(inputArgumentOptionalDetectOmission: Boolean) = this.apply {
96105
this.inputArgumentOptionalDetectOmission = inputArgumentOptionalDetectOmission
97106
}
@@ -154,7 +163,7 @@ data class SchemaParserOptions internal constructor(
154163
GenericWrapper(CompletableFuture::class, 0),
155164
GenericWrapper(CompletionStage::class, 0),
156165
GenericWrapper(Publisher::class, 0),
157-
GenericWrapper.withTransformer(ReceiveChannel::class, 0, { receiveChannel, _ ->
166+
GenericWrapper.withTransformer(ReceiveChannel::class, 0, { receiveChannel ->
158167
publish(coroutineContextProvider.provide()) {
159168
try {
160169
for (item in receiveChannel) {
@@ -175,6 +184,7 @@ data class SchemaParserOptions internal constructor(
175184
wrappers,
176185
allowUnimplementedResolvers,
177186
missingResolverDataFetcher,
187+
missingResolverDataFetcherProvider,
178188
objectMapperProvider,
179189
proxyHandlers,
180190
inputArgumentOptionalDetectOmission,

src/main/kotlin/graphql/kickstart/tools/TypeClassMatcher.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ internal class TypeClassMatcher(private val definitionsByName: Map<String, TypeD
6868
is ListType -> {
6969
if (realType is ParameterizedType && isListType(realType, potentialMatch)) {
7070
match(potentialMatch, graphQLType.type, realType.actualTypeArguments.first())
71-
} else if ((realType as Class<*>).isArray) {
71+
} else if (realType is Class<*> && realType.isArray) {
7272
match(potentialMatch, graphQLType.type, realType.componentType)
7373
} else {
7474
throw error(potentialMatch, "Java class is not a List or generic type information was lost: $realType")

src/main/kotlin/graphql/kickstart/tools/directive/SchemaDirectiveWiringEnvironmentImpl.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,8 @@ class SchemaDirectiveWiringEnvironmentImpl<T : GraphQLDirectiveContainer?>(
5656
override fun getFieldDataFetcher(): DataFetcher<*> {
5757
checkNotNull(fieldDefinition) { "An output field must be in context to call this method" }
5858
checkNotNull(fieldsContainer) { "An output field container must be in context to call this method" }
59-
return codeRegistry.getDataFetcher(fieldsContainer, fieldDefinition)
59+
val coordinates = FieldCoordinates.coordinates(fieldsContainer, fieldDefinition)
60+
return codeRegistry.getDataFetcher(coordinates, fieldDefinition)
6061
}
6162

6263
override fun setFieldDataFetcher(newDataFetcher: DataFetcher<*>?): GraphQLFieldDefinition {

src/main/kotlin/graphql/kickstart/tools/resolver/FieldResolverScanner.kt

Lines changed: 34 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -2,15 +2,18 @@ package graphql.kickstart.tools.resolver
22

33
import graphql.GraphQLContext
44
import graphql.Scalars
5+
import graphql.kickstart.tools.GraphQLSubscriptionResolver
56
import graphql.kickstart.tools.ResolverInfo
67
import graphql.kickstart.tools.RootResolverInfo
78
import graphql.kickstart.tools.SchemaParserOptions
89
import graphql.kickstart.tools.util.*
910
import graphql.language.FieldDefinition
1011
import graphql.language.TypeName
1112
import graphql.schema.DataFetchingEnvironment
13+
import kotlinx.coroutines.channels.ReceiveChannel
1214
import org.apache.commons.lang3.ClassUtils
1315
import org.apache.commons.lang3.reflect.FieldUtils
16+
import org.reactivestreams.Publisher
1417
import org.slf4j.LoggerFactory
1518
import java.lang.reflect.AccessibleObject
1619
import java.lang.reflect.Method
@@ -72,7 +75,9 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
7275
}
7376

7477
private fun missingFieldResolver(field: FieldDefinition, searches: List<Search>, scanProperties: Boolean): FieldResolver {
75-
return if (options.allowUnimplementedResolvers || options.missingResolverDataFetcher != null) {
78+
return if (options.allowUnimplementedResolvers
79+
|| options.missingResolverDataFetcher != null
80+
|| options.missingResolverDataFetcherProvider != null) {
7681
if (options.allowUnimplementedResolvers) {
7782
log.warn("Missing resolver for field: $field")
7883
}
@@ -84,7 +89,7 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
8489
}
8590

8691
private fun findResolverMethod(field: FieldDefinition, search: Search): Method? {
87-
val methods = getAllMethods(search.type)
92+
val methods = getAllMethods(search)
8893
val argumentCount = field.inputValueDefinitions.size + if (search.requiredFirstParameterType != null) 1 else 0
8994
val name = field.name
9095

@@ -107,10 +112,11 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
107112
}
108113
}
109114

110-
private fun getAllMethods(type: JavaType): List<Method> {
111-
val declaredMethods = type.unwrap().declaredNonProxyMethods
112-
val superClassesMethods = ClassUtils.getAllSuperclasses(type.unwrap()).flatMap { it.methods.toList() }
113-
val interfacesMethods = ClassUtils.getAllInterfaces(type.unwrap()).flatMap { it.methods.toList() }
115+
private fun getAllMethods(search: Search): List<Method> {
116+
val type = search.type.unwrap()
117+
val declaredMethods = type.declaredNonProxyMethods
118+
val superClassesMethods = ClassUtils.getAllSuperclasses(type).flatMap { it.methods.toList() }
119+
val interfacesMethods = ClassUtils.getAllInterfaces(type).flatMap { it.methods.toList() }
114120

115121
return (declaredMethods + superClassesMethods + interfacesMethods)
116122
.asSequence()
@@ -119,9 +125,26 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
119125
// discard any methods that are coming off the root of the class hierarchy
120126
// to avoid issues with duplicate method declarations
121127
.filter { it.declaringClass != Object::class.java }
128+
// subscription resolvers must return a publisher
129+
.filter { search.source !is GraphQLSubscriptionResolver || resolverMethodReturnsPublisher(it) }
122130
.toList()
123131
}
124132

133+
private fun resolverMethodReturnsPublisher(method: Method) =
134+
method.returnType.isAssignableFrom(Publisher::class.java) || receiveChannelToPublisherWrapper(method)
135+
136+
private fun receiveChannelToPublisherWrapper(method: Method) =
137+
method.returnType.isAssignableFrom(ReceiveChannel::class.java)
138+
&& options.genericWrappers.any { wrapper ->
139+
val isReceiveChannelWrapper = wrapper.type == method.returnType
140+
val hasPublisherTransformer = wrapper
141+
.transformer.javaClass
142+
.declaredMethods
143+
.filter { it.name == "invoke" }
144+
.any { it.returnType.isAssignableFrom(Publisher::class.java) }
145+
isReceiveChannelWrapper && hasPublisherTransformer
146+
}
147+
125148
private fun isBoolean(type: GraphQLLangType) = type.unwrap().let { it is TypeName && it.name == Scalars.GraphQLBoolean.name }
126149

127150
private fun verifyMethodArguments(method: Method, requiredCount: Int, search: Search): Boolean {
@@ -164,14 +187,18 @@ internal class FieldResolverScanner(val options: SchemaParserOptions) {
164187
private fun getMissingFieldMessage(field: FieldDefinition, searches: List<Search>, scannedProperties: Boolean): String {
165188
val signatures = mutableListOf("")
166189
val isBoolean = isBoolean(field.type)
190+
var isSubscription = false
167191

168192
searches.forEach { search ->
169193
signatures.addAll(getMissingMethodSignatures(field, search, isBoolean, scannedProperties))
194+
isSubscription = isSubscription || search.source is GraphQLSubscriptionResolver
170195
}
171196

172197
val sourceName = if (field.sourceLocation != null && field.sourceLocation.sourceName != null) field.sourceLocation.sourceName else "<unknown>"
173198
val sourceLocation = if (field.sourceLocation != null) "$sourceName:${field.sourceLocation.line}" else "<unknown>"
174-
return "No method${if (scannedProperties) " or field" else ""} found as defined in schema $sourceLocation with any of the following signatures (with or without one of $allowedLastArgumentTypes as the last argument), in priority order:\n${signatures.joinToString("\n ")}"
199+
return "No method${if (scannedProperties) " or field" else ""} found as defined in schema $sourceLocation with any of the following signatures " +
200+
"(with or without one of $allowedLastArgumentTypes as the last argument), in priority order:\n${signatures.joinToString("\n ")}" +
201+
if (isSubscription) "\n\nNote that a Subscription data fetcher must return a Publisher of events" else ""
175202
}
176203

177204
private fun getMissingMethodSignatures(field: FieldDefinition, search: Search, isBoolean: Boolean, scannedProperties: Boolean): List<String> {

0 commit comments

Comments
 (0)