From 47c97da2d9c0f03d2c0cfc08412bf0405cb10374 Mon Sep 17 00:00:00 2001 From: Yarin Date: Sat, 23 Jun 2018 17:21:09 +0300 Subject: [PATCH 1/3] change properties --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a3c753b8..33920a0f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -7,4 +7,4 @@ org.gradle.jvmargs=-Dfile.encoding=UTF-8 bintray.user=DUMMY_USER bintray.key=DUMMY_KEY -version = 5.3 \ No newline at end of file +version = 5.3.1 \ No newline at end of file From 8cf34407b0d2c5ecb8b1335713fbe25a9a61ff03 Mon Sep 17 00:00:00 2001 From: yarinvak Date: Thu, 5 Jul 2018 11:28:19 +0300 Subject: [PATCH 2/3] add method name cases and ignore graphql name --- .../dataFetchers/MethodDataFetcher.java | 72 +++++++--- .../processor/util/PrefixesUtil.java | 9 ++ .../annotations/MethodDataFetcherTest.java | 125 ++++++++++++++++-- 3 files changed, 178 insertions(+), 28 deletions(-) diff --git a/src/main/java/graphql/annotations/dataFetchers/MethodDataFetcher.java b/src/main/java/graphql/annotations/dataFetchers/MethodDataFetcher.java index c7a657c7..e4a687be 100644 --- a/src/main/java/graphql/annotations/dataFetchers/MethodDataFetcher.java +++ b/src/main/java/graphql/annotations/dataFetchers/MethodDataFetcher.java @@ -23,14 +23,35 @@ import java.lang.reflect.*; import java.util.ArrayList; +import java.util.Arrays; import java.util.List; import java.util.Map; import static graphql.annotations.processor.util.NamingKit.toGraphqlName; import static graphql.annotations.processor.util.PrefixesUtil.addPrefixToPropertyName; +import static graphql.annotations.processor.util.PrefixesUtil.extractPrefixedName; import static graphql.annotations.processor.util.ReflectionKit.constructNewInstance; import static graphql.annotations.processor.util.ReflectionKit.newInstance; + +/** + * This class is determining how to return value of a method from an api entity + * The order of the mapping: + * 1. If no source is provided to map between - invoking the method implementation + * 2. If annotated with @GraphQLInvokeDetached - invoking the method implementation + * 3. else If source is provided, and method name is matching a method name in the source object - execute source implementation + * i.e method name is: `name` ; existing method in the source object with name: `name` + * 4. else If source is provided, and method name is matching a method name with a `get` prefix in the source object - execute source implementation + * i.e method name is: `name` ; existing method in the source object with name: `getName` + * 5. else If source is provided, and method name is matching a method name with a `is` prefix in the source object - execute source implementation + * i.e method name is: `name` ; existing method in the source object with name: isName + * 6. else If source is provided, and method name is matching a field name in the source object - return field value from the source object + * i.e method name is: `name` ; field name in source object is: `name` + * 7. else If source is provided, and method name is prefixed with `get` or `is` - and it matches to a field name (without the prefix) in the source object - return field value from the source object + * i.e method name is: `getName` ; field name in source object is: `name` + * + * @param type of the returned value + */ public class MethodDataFetcher implements DataFetcher { private final Method method; private final ProcessingElementsContainer container; @@ -59,7 +80,7 @@ public T get(DataFetchingEnvironment environment) { } if (obj == null && environment.getSource() != null) { - Object value = getGraphQLFieldValue(environment.getSource(), environment.getField().getName()); + Object value = getGraphQLFieldValue(environment.getSource(), method.getName()); return (T) value; } @@ -140,13 +161,24 @@ private Object getGraphQLFieldValue(Object source, String fieldName) throws Ille Object methodValue = getValueFromMethod(source, fieldName); if (methodValue != null) return methodValue; - Field field = getField(source.getClass(), fieldName); - if (getValueFromField(field)) return field.get(source); + Object fieldValue = getValueFromField(source, fieldName); + if (fieldValue != null) return fieldValue; throw new NoSuchFieldException("No GraphQL field found"); } - private boolean getValueFromField(Field field) throws IllegalAccessException { + private Object getValueFromField(Object source, String fieldName) throws IllegalAccessException { + List namesToSearchFor = Arrays.asList(fieldName, extractPrefixedName(fieldName)); + for (String name : namesToSearchFor) { + Field field = getField(source.getClass(), name); + if (isFieldContainsValue(field)) { + return field.get(source); + } + } + return null; + } + + private boolean isFieldContainsValue(Field field) throws IllegalAccessException { if (field != null) { field.setAccessible(true); return true; @@ -154,6 +186,18 @@ private boolean getValueFromField(Field field) throws IllegalAccessException { return false; } + private Field getField(Class clazz, String name) { + Field field = null; + while (clazz != null && field == null) { + try { + field = clazz.getDeclaredField(name); + } catch (Exception ignored) { + } + clazz = clazz.getSuperclass(); + } + return field; + } + private Object getValueFromMethod(Object source, String fieldName) throws IllegalAccessException, InvocationTargetException { String[] orderedPrefixes = new String[]{"", "get", "is"}; for (String orderedPrefix : orderedPrefixes) { @@ -166,7 +210,13 @@ private Object getValueFromMethod(Object source, String fieldName) throws Illega } private Method getMethod(Class clazz, String name, String prefix) { - String prefixedName = addPrefixToPropertyName(prefix, name); + String prefixedName; + if (prefix.isEmpty()) { + prefixedName = name; + } else { + prefixedName = addPrefixToPropertyName(prefix, name); + } + Method method = null; while (clazz != null && method == null) { try { @@ -179,16 +229,4 @@ private Method getMethod(Class clazz, String name, String prefix) { return method; } - private Field getField(Class clazz, String name) { - Field field = null; - while (clazz != null && field == null) { - try { - field = clazz.getDeclaredField(name); - } catch (Exception ignored) { - } - clazz = clazz.getSuperclass(); - } - return field; - } - } diff --git a/src/main/java/graphql/annotations/processor/util/PrefixesUtil.java b/src/main/java/graphql/annotations/processor/util/PrefixesUtil.java index d7d69603..96a02db6 100644 --- a/src/main/java/graphql/annotations/processor/util/PrefixesUtil.java +++ b/src/main/java/graphql/annotations/processor/util/PrefixesUtil.java @@ -18,4 +18,13 @@ public class PrefixesUtil { public static String addPrefixToPropertyName(String prefix, String propertyName) { return prefix + propertyName.substring(0, 1).toUpperCase() + propertyName.substring(1); } + + public static String extractPrefixedName(String name) { + if (name.startsWith("is")) { + return name.replaceFirst("^is", "").toLowerCase(); + } else if (name.startsWith("get")) { + return name.replaceFirst("^get", "").toLowerCase(); + } + return name; + } } diff --git a/src/test/java/graphql/annotations/MethodDataFetcherTest.java b/src/test/java/graphql/annotations/MethodDataFetcherTest.java index bbff4492..78b18381 100644 --- a/src/test/java/graphql/annotations/MethodDataFetcherTest.java +++ b/src/test/java/graphql/annotations/MethodDataFetcherTest.java @@ -25,7 +25,6 @@ import org.testng.annotations.BeforeMethod; import org.testng.annotations.Test; -import javax.xml.crypto.Data; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; @@ -95,10 +94,12 @@ public void query_onlyApiClass_valueIsDeterminedByMethod() throws Exception { } /** - * Case 3: Api and a DB class, value is determined by the db field + * Case 3: Api and a DB class with polymorphism, value is determined by the db field + * name of api method <-> name of db field */ public static class Api3 { @GraphQLField + @GraphQLName("nameX") public String name() { return "dani"; } @@ -131,17 +132,19 @@ public void query_apiAndDbClass_valueIsDeterminedByDBField() throws Exception { GraphQLObjectType object = GraphQLAnnotations.object(Query3.class); GraphQLSchema schema = newSchema().query(object).build(); - ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { queryField { name } }").root(new Query3())); + ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { queryField { nameX } }").root(new Query3())); assertTrue(result.getErrors().isEmpty()); - assertEquals(((Map>) result.getData()).get("queryField").get("name").toString(), "osher"); + assertEquals(((Map>) result.getData()).get("queryField").get("nameX").toString(), "osher"); } /** * Case 4: Api and DB classes, value is determined by db method + * api method name <-> (`get`) + db method name */ public static class Api4 { @GraphQLField + @GraphQLName("nameX") public String name() { return null; } @@ -173,15 +176,116 @@ public static class Query4 { } @Test - public void query_apiAndDbClass_valueIsDeterminedByDBMethod() throws Exception { + public void query_apiAndDbClass_valueIsDeterminedByGetPrefixDBMethod() throws Exception { GraphQLObjectType object = GraphQLAnnotations.object(Query4.class); GraphQLSchema schema = newSchema().query(object).build(); - ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { queryField { name } }").root(new Query4())); + ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { queryField { nameX } }").root(new Query4())); + assertTrue(result.getErrors().isEmpty()); + assertEquals(((Map>) result.getData()).get("queryField").get("nameX").toString(), "guy/yarin"); + } + + /** + * Case: Api and DB classes, value is determined by db method + * api method name <-> (`is`) + db method name + */ + + public static class Api6 { + @GraphQLField + @GraphQLName("nameX") + public String name() { + return null; + } + } + + public static class SuperDB6 { + private String name = "guy"; + + public String isName() { + return name + "/yarin"; + } + } + + public static class DB6 extends SuperDB6 { + } + + public static class Api6Resolver implements DataFetcher { + + @Override + public DB6 get(DataFetchingEnvironment environment) { + return new DB6(); + } + } + + public static class Query6 { + @GraphQLField + @GraphQLDataFetcher(Api6Resolver.class) + public Api6 queryField; + } + + @Test + public void query_apiAndDbClass_valueIsDeterminedByIsPrefixDBMethod() throws Exception { + GraphQLObjectType object = GraphQLAnnotations.object(Query6.class); + GraphQLSchema schema = newSchema().query(object).build(); + + ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { queryField { nameX } }").root(new Query6())); assertTrue(result.getErrors().isEmpty()); - assertEquals(((Map>) result.getData()).get("queryField").get("name").toString(), "guy/yarin"); + assertEquals(((Map>) result.getData()).get("queryField").get("nameX").toString(), "guy/yarin"); } + /** + * Case: Api and DB classes, value is determined by db method + * api method name <-> db method name + */ + + public static class Api7 { + @GraphQLField + @GraphQLName("nameX") + public String name() { + return null; + } + } + + public static class SuperDB7 { + private String name = "guy"; + + public String name() { + return name + "/yarin"; + } + + public String isName() { + return "blabla"; + } + } + + public static class DB7 extends SuperDB7 { + } + + public static class Api7Resolver implements DataFetcher { + + @Override + public DB7 get(DataFetchingEnvironment environment) { + return new DB7(); + } + } + + public static class Query7 { + @GraphQLField + @GraphQLDataFetcher(Api7Resolver.class) + public Api7 queryField; + } + + @Test + public void query_apiAndDbClass_valueIsDeterminedByDBMethod() throws Exception { + GraphQLObjectType object = GraphQLAnnotations.object(Query7.class); + GraphQLSchema schema = newSchema().query(object).build(); + + ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { queryField { nameX } }").root(new Query7())); + assertTrue(result.getErrors().isEmpty()); + assertEquals(((Map>) result.getData()).get("queryField").get("nameX").toString(), "guy/yarin"); + } + + /** * Case 5: Invoke Detached on method, both api and db classes, value is determined by the api method */ @@ -215,8 +319,6 @@ public static class Query5 { public Api5 queryField; } -///////////////////////////////////////// - @Test public void query_apiAndDbClassAndApiIsInvokeDetached_valueIsDeterminedByApiMethod() throws Exception { GraphQLObjectType object = GraphQLAnnotations.object(Query5.class); @@ -227,6 +329,9 @@ public void query_apiAndDbClassAndApiIsInvokeDetached_valueIsDeterminedByApiMeth assertEquals(((Map>) result.getData()).get("queryField").get("name").toString(), "yarin/guy/osher"); } + ///////////////////////////////////////// + ///////////////////////////////////////// + ///////////////////////////////////////// public class TestException extends Exception { } @@ -276,8 +381,6 @@ public int c() { public CanonizedTypeApi getCanonizedType() { return null; } - - } public static class CanonizedFetcher implements DataFetcher { From b6bc72d012b1dd028bd646bbad35f306772b57d9 Mon Sep 17 00:00:00 2001 From: Yarin Date: Thu, 12 Jul 2018 18:08:23 +0300 Subject: [PATCH 3/3] add static handling --- .../dataFetchers/MethodDataFetcher.java | 5 ++++- .../annotations/MethodDataFetcherTest.java | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 1 deletion(-) diff --git a/src/main/java/graphql/annotations/dataFetchers/MethodDataFetcher.java b/src/main/java/graphql/annotations/dataFetchers/MethodDataFetcher.java index e4a687be..a5fc6ab2 100644 --- a/src/main/java/graphql/annotations/dataFetchers/MethodDataFetcher.java +++ b/src/main/java/graphql/annotations/dataFetchers/MethodDataFetcher.java @@ -68,7 +68,10 @@ public MethodDataFetcher(Method method, TypeFunction typeFunction, ProcessingEle public T get(DataFetchingEnvironment environment) { try { T obj; - if (method.isAnnotationPresent(GraphQLBatched.class) || method.isAnnotationPresent(GraphQLInvokeDetached.class)) { + if (Modifier.isStatic(method.getModifiers())){ + return (T) method.invoke(null, invocationArgs(environment, container)); + } + else if (method.isAnnotationPresent(GraphQLBatched.class) || method.isAnnotationPresent(GraphQLInvokeDetached.class)) { obj = newInstance((Class) method.getDeclaringClass()); } else if (!method.getDeclaringClass().isInstance(environment.getSource())) { obj = newInstance((Class) method.getDeclaringClass(), environment.getSource()); diff --git a/src/test/java/graphql/annotations/MethodDataFetcherTest.java b/src/test/java/graphql/annotations/MethodDataFetcherTest.java index 78b18381..2cd949ef 100644 --- a/src/test/java/graphql/annotations/MethodDataFetcherTest.java +++ b/src/test/java/graphql/annotations/MethodDataFetcherTest.java @@ -40,6 +40,24 @@ public void init() { GraphQLAnnotations.getInstance().getTypeRegistry().clear(); } + public static class StaticApi { + @GraphQLField + public static String name() { + return "osher"; + } + } + + @Test + public void query_staticMethod_valueIsDeterminedByMethod(){ + GraphQLObjectType object = GraphQLAnnotations.object(StaticApi.class); + GraphQLSchema schema = newSchema().query(object).build(); + + ExecutionResult result = GraphQL.newGraphQL(schema).build().execute(builder -> builder.query("query { name }").root(new StaticApi())); + assertTrue(result.getErrors().isEmpty()); + assertEquals(((Map) result.getData()).get("name").toString(), "osher"); + } + + /** * CASE 1 : Only Api class, value determined by field */