Skip to content

Commit 1b027f3

Browse files
committed
Merge branch 'master' into custom-paging
2 parents 2d87adc + e120188 commit 1b027f3

File tree

9 files changed

+250
-10
lines changed

9 files changed

+250
-10
lines changed

README.md

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,26 @@ GraphQLObjectType object = GraphQLAnnotations.object(SomeObject.class);
3434

3535
## Defining Interfaces
3636

37-
This is very similar to defining objects:
37+
This is very similar to defining objects, with the addition of type resolver :
3838

3939
```java
40+
@GraphQLTypeResolver(MyTypeResolver.class)
4041
public interface SomeInterface {
4142
@GraphQLField
4243
String field();
4344
}
4445

46+
public class MyTypeResolver implements TypeResolver {
47+
GraphQLObjectType getType(TypeResolutionEnvironment env) { ... }
48+
}
49+
4550
// ...
4651
GraphQLInterfaceType object = GraphQLAnnotations.iface(SomeInterface.class);
4752
```
4853

54+
An instance of the type resolver will be created from the specified class. If a `getInstance` method is present on the
55+
class, it will be used instead of the default constructor.
56+
4957
## Fields
5058

5159
In addition to specifying a field over a Java class field, a field can be defined over a method:
@@ -108,10 +116,21 @@ public String field(@GraphQLDefaultValue(DefaultValue.class) String value) {
108116
}
109117
```
110118

119+
The `DefaultValue` class can define a `getInstance` method that will be called instead of the default constructor.
120+
111121
`@GraphQLDeprecate` and Java's `@Deprecated` can be used to specify a deprecated
112122
field.
113123

114-
You can specify a custom data fetcher for a field with `@GraphQLDataFetcher`
124+
### Custom data fetcher
125+
126+
You can specify a custom data fetcher for a field with `@GraphQLDataFetcher`. The annotation will reference a class name,
127+
which will be used as data fetcher.
128+
129+
An instance of the data fetcher will be created. The `args` attribute on the annotation can be used to specify a list of
130+
String arguments to pass to the construcor, allowing to reuse the same class on different fields, with different parameter.
131+
The `firstArgIsTargetName` attribute can also be set on `@GraphQLDataFetcher` to pass the field name as a single parameter of the constructor.
132+
133+
If no argument is needed and a `getInstance` method is present, this method will be called instead of the constructor.
115134

116135
## Type extensions
117136

build.gradle

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -60,7 +60,9 @@ repositories {
6060

6161
gradle.projectsEvaluated {
6262
tasks.withType(JavaCompile) {
63-
options.compilerArgs << "-parameters"
63+
doLast {
64+
options.compilerArgs += "-parameters"
65+
}
6466
}
6567
}
6668

src/main/java/graphql/annotations/GraphQLFieldDefinitionWrapper.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,16 @@
1414
*/
1515
package graphql.annotations;
1616

17+
import graphql.schema.DataFetcher;
18+
import graphql.schema.DataFetcherFactories;
1719
import graphql.schema.GraphQLFieldDefinition;
1820

1921
public class GraphQLFieldDefinitionWrapper extends GraphQLFieldDefinition {
2022

2123
public GraphQLFieldDefinitionWrapper(GraphQLFieldDefinition fieldDefinition) {
2224
super(fieldDefinition.getName(), fieldDefinition.getDescription(), fieldDefinition.getType(),
23-
fieldDefinition.getDataFetcher(), fieldDefinition.getArguments(), fieldDefinition.getDeprecationReason());
25+
DataFetcherFactories.useDataFetcher((DataFetcher<?>)fieldDefinition.getDataFetcher()), fieldDefinition.getArguments(), fieldDefinition.getDeprecationReason(),
26+
fieldDefinition.getDefinition());
2427
}
2528

2629
@Override
Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/**
2+
* Copyright 2016 Yurii Rashkovskii
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
*/
15+
package graphql.annotations.processor.typeFunctions;
16+
17+
import graphql.annotations.processor.ProcessingElementsContainer;
18+
import graphql.schema.GraphQLList;
19+
import graphql.schema.GraphQLType;
20+
21+
import java.lang.reflect.AnnotatedArrayType;
22+
import java.lang.reflect.AnnotatedType;
23+
import java.lang.reflect.ParameterizedType;
24+
25+
public class ArrayFunction implements TypeFunction {
26+
27+
private DefaultTypeFunction defaultTypeFunction;
28+
29+
public ArrayFunction(DefaultTypeFunction defaultTypeFunction) {
30+
this.defaultTypeFunction = defaultTypeFunction;
31+
}
32+
33+
@Override
34+
public boolean canBuildType(Class<?> aClass, AnnotatedType annotatedType) {
35+
return aClass.isArray();
36+
}
37+
38+
@Override
39+
public GraphQLType buildType(boolean input, Class<?> aClass, AnnotatedType annotatedType, ProcessingElementsContainer container) {
40+
if (!(annotatedType instanceof AnnotatedArrayType)) {
41+
throw new IllegalArgumentException("Array type parameter should be specified");
42+
}
43+
AnnotatedArrayType parameterizedType = (AnnotatedArrayType) annotatedType;
44+
AnnotatedType arg = parameterizedType.getAnnotatedGenericComponentType();
45+
Class<?> klass;
46+
if (arg.getType() instanceof ParameterizedType) {
47+
klass = (Class<?>) ((ParameterizedType) (arg.getType())).getRawType();
48+
} else {
49+
klass = (Class<?>) arg.getType();
50+
}
51+
return new GraphQLList(defaultTypeFunction.buildType(input, klass, arg, container));
52+
}
53+
}

src/main/java/graphql/annotations/processor/typeFunctions/DefaultTypeFunction.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ public void activate() {
6262
typeFunctions.add(new BigDecimalFunction());
6363
typeFunctions.add(new CharFunction());
6464
typeFunctions.add(new IterableFunction(DefaultTypeFunction.this));
65+
typeFunctions.add(new ArrayFunction(DefaultTypeFunction.this));
6566
typeFunctions.add(new StreamFunction(DefaultTypeFunction.this));
6667
typeFunctions.add(new OptionalFunction(DefaultTypeFunction.this));
6768
typeFunctions.add(new ObjectFunction(graphQLInputProcessor, graphQLOutputProcessor));

src/main/java/graphql/annotations/processor/typeFunctions/IterableFunction.java

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,12 @@
2525
/**
2626
* Support for the Iterable things like Lists / Sets / Collections and so on..
2727
*/
28-
class IterableFunction implements TypeFunction {
28+
class IterableFunction implements TypeFunction {
2929

3030
private DefaultTypeFunction defaultTypeFunction;
3131

32-
public IterableFunction(DefaultTypeFunction defaultTypeFunction){
33-
this.defaultTypeFunction=defaultTypeFunction;
32+
public IterableFunction(DefaultTypeFunction defaultTypeFunction) {
33+
this.defaultTypeFunction = defaultTypeFunction;
3434
}
3535

3636
@Override
@@ -51,6 +51,6 @@ public GraphQLType buildType(boolean input, Class<?> aClass, AnnotatedType annot
5151
} else {
5252
klass = (Class<?>) arg.getType();
5353
}
54-
return new GraphQLList(defaultTypeFunction.buildType(input, klass, arg,container));
54+
return new GraphQLList(defaultTypeFunction.buildType(input, klass, arg, container));
5555
}
56-
}
56+
}

src/main/java/graphql/annotations/processor/util/ReflectionKit.java

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818

1919
import java.lang.reflect.Constructor;
2020
import java.lang.reflect.InvocationTargetException;
21+
import java.lang.reflect.Method;
22+
import java.lang.reflect.Modifier;
2123

2224
/**
2325
* A package level helper in calling reflective methods and turning them into
@@ -26,8 +28,16 @@
2628
public class ReflectionKit {
2729
public static <T> T newInstance(Class<T> clazz) throws GraphQLAnnotationsException {
2830
try {
31+
try {
32+
Method getInstance = clazz.getMethod("getInstance", new Class<?>[0]);
33+
if (Modifier.isStatic(getInstance.getModifiers()) && clazz.isAssignableFrom(getInstance.getReturnType())) {
34+
return (T) getInstance.invoke(null);
35+
}
36+
} catch (NoSuchMethodException e) {
37+
// ignore, just call the constructor
38+
}
2939
return clazz.newInstance();
30-
} catch (InstantiationException | IllegalAccessException e) {
40+
} catch (InstantiationException | InvocationTargetException | IllegalAccessException e) {
3141
throw new GraphQLAnnotationsException("Unable to instantiate class : " + clazz, e);
3242
}
3343
}

src/test/java/graphql/annotations/GraphQLInterfaceTest.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,10 @@ public void noResolver() {
6969

7070
public static class Resolver implements TypeResolver {
7171

72+
public static Resolver getInstance() {
73+
return new Resolver();
74+
}
75+
7276
@Override
7377
public GraphQLObjectType getType(TypeResolutionEnvironment env) {
7478
try {
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
/**
2+
* Copyright 2016 Yurii Rashkovskii
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
*/
15+
package graphql.annotations;
16+
17+
import graphql.ExecutionResult;
18+
import graphql.GraphQL;
19+
import graphql.annotations.annotationTypes.GraphQLDataFetcher;
20+
import graphql.annotations.annotationTypes.GraphQLField;
21+
import graphql.annotations.processor.GraphQLAnnotations;
22+
import graphql.schema.DataFetcher;
23+
import graphql.schema.DataFetchingEnvironment;
24+
import graphql.schema.GraphQLObjectType;
25+
import graphql.schema.GraphQLSchema;
26+
import org.testng.annotations.BeforeMethod;
27+
import org.testng.annotations.Test;
28+
29+
import java.util.ArrayList;
30+
import java.util.Collections;
31+
import java.util.LinkedHashMap;
32+
import java.util.List;
33+
34+
import static graphql.schema.GraphQLSchema.newSchema;
35+
import static org.testng.Assert.assertEquals;
36+
import static org.testng.Assert.assertTrue;
37+
38+
public class GraphQLIterableAndArrayTest {
39+
@BeforeMethod
40+
public void init() {
41+
GraphQLAnnotations.getInstance().getTypeRegistry().clear();
42+
}
43+
44+
static class TestMappedObject {
45+
@GraphQLField
46+
public String name;
47+
48+
@GraphQLField
49+
public String foo;
50+
}
51+
52+
static class TestObjectDB {
53+
private String foo;
54+
55+
private String name;
56+
57+
TestObjectDB(String name, String foo) {
58+
this.name = name;
59+
this.foo = foo;
60+
}
61+
}
62+
63+
public static class IterableAndArrayTestQuery {
64+
@GraphQLField
65+
@GraphQLDataFetcher(ArrayFetcher.class)
66+
public TestMappedObject[] array;
67+
68+
@GraphQLField
69+
@GraphQLDataFetcher(ListFetcher.class)
70+
public List<TestMappedObject> list;
71+
72+
@GraphQLField
73+
@GraphQLDataFetcher(ArrayWithinArrayFetcher.class)
74+
public TestMappedObject[][] arrayWithinArray;
75+
}
76+
77+
public static class ArrayFetcher implements DataFetcher<TestObjectDB[]> {
78+
79+
@Override
80+
public TestObjectDB[] get(DataFetchingEnvironment environment) {
81+
return new TestObjectDB[]{new TestObjectDB("hello", "world")};
82+
}
83+
}
84+
85+
public static class ListFetcher implements DataFetcher<List<TestObjectDB>> {
86+
87+
@Override
88+
public List<TestObjectDB> get(DataFetchingEnvironment environment) {
89+
return Collections.singletonList(new TestObjectDB("test name", "test foo"));
90+
}
91+
}
92+
93+
public static class ArrayWithinArrayFetcher implements DataFetcher<TestObjectDB[][]> {
94+
95+
@Override
96+
public TestObjectDB[][] get(DataFetchingEnvironment environment) {
97+
return new TestObjectDB[][]{
98+
{new TestObjectDB("hello", "world")},
99+
{new TestObjectDB("a", "b"), new TestObjectDB("c", "d")}
100+
};
101+
}
102+
}
103+
104+
@Test
105+
public void queryWithArray() {
106+
GraphQLObjectType object = GraphQLAnnotations.object(IterableAndArrayTestQuery.class);
107+
GraphQLSchema schema = newSchema().query(object).build();
108+
109+
ExecutionResult result = GraphQL.newGraphQL(schema).build().execute("{array {name foo}}");
110+
assertTrue(result.getErrors().isEmpty());
111+
assertEquals(((LinkedHashMap) getQueryResultAtIndex(result, "array", 0)).get("name"), "hello");
112+
assertEquals(((LinkedHashMap) getQueryResultAtIndex(result, "array", 0)).get("foo"), "world");
113+
}
114+
115+
@Test
116+
public void queryWithList() {
117+
GraphQLObjectType object = GraphQLAnnotations.object(IterableAndArrayTestQuery.class);
118+
GraphQLSchema schema = newSchema().query(object).build();
119+
120+
ExecutionResult result = GraphQL.newGraphQL(schema).build().execute("{list {name foo}}");
121+
assertTrue(result.getErrors().isEmpty());
122+
assertEquals(((LinkedHashMap) getQueryResultAtIndex(result, "list", 0)).get("name"), "test name");
123+
assertEquals(((LinkedHashMap) getQueryResultAtIndex(result, "list", 0)).get("foo"), "test foo");
124+
}
125+
126+
@Test
127+
public void queryWithArrayWithinAnArray() {
128+
GraphQLObjectType object = GraphQLAnnotations.object(IterableAndArrayTestQuery.class);
129+
GraphQLSchema schema = newSchema().query(object).build();
130+
131+
ExecutionResult result = GraphQL.newGraphQL(schema).build().execute("{arrayWithinArray {name foo}}");
132+
assertTrue(result.getErrors().isEmpty());
133+
assertEquals(((LinkedHashMap) getQueryResultAtCell(result, "arrayWithinArray", 0, 0)).get("name"), "hello");
134+
assertEquals(((LinkedHashMap) getQueryResultAtCell(result, "arrayWithinArray", 0, 0)).get("foo"), "world");
135+
assertEquals(((LinkedHashMap) getQueryResultAtCell(result, "arrayWithinArray", 1, 0)).get("name"), "a");
136+
assertEquals(((LinkedHashMap) getQueryResultAtCell(result, "arrayWithinArray", 1, 0)).get("foo"), "b");
137+
assertEquals(((LinkedHashMap) getQueryResultAtCell(result, "arrayWithinArray", 1, 1)).get("name"), "c");
138+
assertEquals(((LinkedHashMap) getQueryResultAtCell(result, "arrayWithinArray", 1, 1)).get("foo"), "d");
139+
}
140+
141+
private Object getQueryResultAtIndex(ExecutionResult result, String queryName, int index) {
142+
return ((ArrayList) (((LinkedHashMap) result.getData()).get(queryName))).get(index);
143+
}
144+
145+
private Object getQueryResultAtCell(ExecutionResult result, String queryName, int rowIndex, int columnIndex) {
146+
return (((ArrayList) (getQueryResultAtIndex(result, queryName, rowIndex))).get(columnIndex));
147+
}
148+
}

0 commit comments

Comments
 (0)