Skip to content

Commit c1395c8

Browse files
authored
Merge pull request #3251 from graphql-java/static_property_like_methods_should_not_be_used
static record like methods are no longer supported
2 parents 51206b0 + f059a00 commit c1395c8

File tree

2 files changed

+162
-8
lines changed

2 files changed

+162
-8
lines changed

src/main/java/graphql/schema/PropertyFetchingImpl.java

Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,18 @@ public Object getPropertyValue(String propertyName, Object object, GraphQLType g
138138
//
139139
// try by public getters name - object.getPropertyName()
140140
try {
141-
MethodFinder methodFinder = (rootClass, methodName) -> findPubliclyAccessibleMethod(cacheKey, rootClass, methodName, dfeInUse);
141+
MethodFinder methodFinder = (rootClass, methodName) -> findPubliclyAccessibleMethod(cacheKey, rootClass, methodName, dfeInUse, false);
142+
return getPropertyViaGetterMethod(object, propertyName, graphQLType, methodFinder, singleArgumentValue);
143+
} catch (NoSuchMethodException ignored) {
144+
}
145+
//
146+
// try by public getters name - object.getPropertyName() where its static
147+
try {
148+
// we allow static getXXX() methods because we always have. It's strange in retrospect but
149+
// in order to not break things we allow statics to be used. In theory this double code check is not needed
150+
// because you CANT have a `static getFoo()` and a `getFoo()` in the same class hierarchy but to make the code read clearer
151+
// I have repeated the lookup. Since we cache methods, this happens only once and does not slow us down
152+
MethodFinder methodFinder = (rootClass, methodName) -> findPubliclyAccessibleMethod(cacheKey, rootClass, methodName, dfeInUse, true);
142153
return getPropertyViaGetterMethod(object, propertyName, graphQLType, methodFinder, singleArgumentValue);
143154
} catch (NoSuchMethodException ignored) {
144155
}
@@ -215,7 +226,7 @@ private Object getPropertyViaGetterUsingPrefix(Object object, String propertyNam
215226
* which have abstract public interfaces implemented by package-protected
216227
* (generated) subclasses.
217228
*/
218-
private Method findPubliclyAccessibleMethod(CacheKey cacheKey, Class<?> rootClass, String methodName, boolean dfeInUse) throws NoSuchMethodException {
229+
private Method findPubliclyAccessibleMethod(CacheKey cacheKey, Class<?> rootClass, String methodName, boolean dfeInUse, boolean allowStaticMethods) throws NoSuchMethodException {
219230
Class<?> currentClass = rootClass;
220231
while (currentClass != null) {
221232
if (Modifier.isPublic(currentClass.getModifiers())) {
@@ -224,7 +235,7 @@ private Method findPubliclyAccessibleMethod(CacheKey cacheKey, Class<?> rootClas
224235
// try a getter that takes singleArgumentType first (if we have one)
225236
try {
226237
Method method = currentClass.getMethod(methodName, singleArgumentType);
227-
if (Modifier.isPublic(method.getModifiers())) {
238+
if (isSuitablePublicMethod(method, allowStaticMethods)) {
228239
METHOD_CACHE.putIfAbsent(cacheKey, new CachedMethod(method));
229240
return method;
230241
}
@@ -233,7 +244,7 @@ private Method findPubliclyAccessibleMethod(CacheKey cacheKey, Class<?> rootClas
233244
}
234245
}
235246
Method method = currentClass.getMethod(methodName);
236-
if (Modifier.isPublic(method.getModifiers())) {
247+
if (isSuitablePublicMethod(method, allowStaticMethods)) {
237248
METHOD_CACHE.putIfAbsent(cacheKey, new CachedMethod(method));
238249
return method;
239250
}
@@ -244,6 +255,18 @@ private Method findPubliclyAccessibleMethod(CacheKey cacheKey, Class<?> rootClas
244255
return rootClass.getMethod(methodName);
245256
}
246257

258+
private boolean isSuitablePublicMethod(Method method, boolean allowStaticMethods) {
259+
int methodModifiers = method.getModifiers();
260+
if (Modifier.isPublic(methodModifiers)) {
261+
//noinspection RedundantIfStatement
262+
if (Modifier.isStatic(methodModifiers) && !allowStaticMethods) {
263+
return false;
264+
}
265+
return true;
266+
}
267+
return false;
268+
}
269+
247270
/*
248271
https://docs.oracle.com/en/java/javase/15/language/records.html
249272
@@ -253,9 +276,11 @@ private Method findPubliclyAccessibleMethod(CacheKey cacheKey, Class<?> rootClas
253276
254277
However, we won't just restrict ourselves strictly to true records. We will find methods that are record like
255278
and fetch them - e.g. `object.propertyName()`
279+
280+
We won't allow static methods for record like methods however
256281
*/
257282
private Method findRecordMethod(CacheKey cacheKey, Class<?> rootClass, String methodName) throws NoSuchMethodException {
258-
return findPubliclyAccessibleMethod(cacheKey, rootClass, methodName, false);
283+
return findPubliclyAccessibleMethod(cacheKey, rootClass, methodName, false, false);
259284
}
260285

261286
private Method findViaSetAccessible(CacheKey cacheKey, Class<?> aClass, String methodName, boolean dfeInUse) throws NoSuchMethodException {

src/test/groovy/graphql/schema/PropertyDataFetcherTest.groovy

Lines changed: 132 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class PropertyDataFetcherTest extends Specification {
3333
.build()
3434
}
3535

36-
class SomeObject {
36+
static class SomeObject {
3737
String value
3838
}
3939

@@ -483,7 +483,7 @@ class PropertyDataFetcherTest extends Specification {
483483

484484
}
485485

486-
class ProductDTO {
486+
static class ProductDTO {
487487
String name
488488
String model
489489
}
@@ -537,7 +537,7 @@ class PropertyDataFetcherTest extends Specification {
537537
private static class Bar implements Foo {
538538
@Override
539539
String getSomething() {
540-
return "bar";
540+
return "bar"
541541
}
542542
}
543543

@@ -562,4 +562,133 @@ class PropertyDataFetcherTest extends Specification {
562562
then:
563563
result == "bar"
564564
}
565+
566+
def "issue 3247 - record like statics should not be used"() {
567+
given:
568+
def payload = new UpdateOrganizerSubscriptionPayload(true, new OrganizerSubscriptionError())
569+
PropertyDataFetcher propertyDataFetcher = new PropertyDataFetcher("success")
570+
def dfe = Mock(DataFetchingEnvironment)
571+
dfe.getSource() >> payload
572+
when:
573+
def result = propertyDataFetcher.get(dfe)
574+
575+
then:
576+
result == true
577+
578+
// repeat - should be cached
579+
when:
580+
result = propertyDataFetcher.get(dfe)
581+
582+
then:
583+
result == true
584+
}
585+
586+
def "issue 3247 - record like statics should not be found"() {
587+
given:
588+
def errorShape = new OrganizerSubscriptionError()
589+
PropertyDataFetcher propertyDataFetcher = new PropertyDataFetcher("message")
590+
def dfe = Mock(DataFetchingEnvironment)
591+
dfe.getSource() >> errorShape
592+
when:
593+
def result = propertyDataFetcher.get(dfe)
594+
595+
then:
596+
result == null // not found as its a static recordLike() method
597+
598+
// repeat - should be cached
599+
when:
600+
result = propertyDataFetcher.get(dfe)
601+
602+
then:
603+
result == null
604+
}
605+
606+
def "issue 3247 - getter statics should be found"() {
607+
given:
608+
def objectInQuestion = new BarClassWithStaticProperties()
609+
PropertyDataFetcher propertyDataFetcher = new PropertyDataFetcher("foo")
610+
def dfe = Mock(DataFetchingEnvironment)
611+
dfe.getSource() >> objectInQuestion
612+
when:
613+
def result = propertyDataFetcher.get(dfe)
614+
615+
then:
616+
result == "foo"
617+
618+
// repeat - should be cached
619+
when:
620+
result = propertyDataFetcher.get(dfe)
621+
622+
then:
623+
result == "foo"
624+
625+
when:
626+
propertyDataFetcher = new PropertyDataFetcher("bar")
627+
result = propertyDataFetcher.get(dfe)
628+
629+
then:
630+
result == "bar"
631+
632+
// repeat - should be cached
633+
when:
634+
result = propertyDataFetcher.get(dfe)
635+
636+
then:
637+
result == "bar"
638+
}
639+
640+
/**
641+
* Classes from issue to ensure we reproduce as reported by customers
642+
*
643+
* In the UpdateOrganizerSubscriptionPayload class we will find the getSuccess() because static recordLike() methods are no longer allowed
644+
*/
645+
static class OrganizerSubscriptionError {
646+
static String message() { return "error " }
647+
}
648+
649+
static class UpdateOrganizerSubscriptionPayload {
650+
private final Boolean success
651+
private final OrganizerSubscriptionError error
652+
653+
UpdateOrganizerSubscriptionPayload(Boolean success, OrganizerSubscriptionError error) {
654+
this.success = success
655+
this.error = error
656+
}
657+
658+
static UpdateOrganizerSubscriptionPayload success() {
659+
// 👈 note the static factory method for creating a success payload
660+
return new UpdateOrganizerSubscriptionPayload(Boolean.TRUE, null)
661+
}
662+
663+
static UpdateOrganizerSubscriptionPayload error(OrganizerSubscriptionError error) {
664+
// 👈 note the static factory method for creating a success payload
665+
return new UpdateOrganizerSubscriptionPayload(null, error)
666+
}
667+
668+
Boolean getSuccess() {
669+
return success
670+
}
671+
672+
OrganizerSubscriptionError getError() {
673+
return error
674+
}
675+
676+
677+
@Override
678+
String toString() {
679+
return new StringJoiner(
680+
", ", UpdateOrganizerSubscriptionPayload.class.getSimpleName() + "[", "]")
681+
.add("success=" + success)
682+
.add("error=" + error)
683+
.toString()
684+
}
685+
}
686+
687+
static class FooClassWithStaticProperties {
688+
static String getFoo() { return "foo" }
689+
}
690+
691+
static class BarClassWithStaticProperties extends FooClassWithStaticProperties {
692+
static String getBar() { return "bar" }
693+
}
565694
}

0 commit comments

Comments
 (0)