Skip to content

Commit

Permalink
Merge pull request #119 from guy120494/fix-connection
Browse files Browse the repository at this point in the history
Fix connection
  • Loading branch information
yarinvak authored Nov 9, 2017
2 parents 4663688 + 55fb330 commit 496ed16
Show file tree
Hide file tree
Showing 247 changed files with 772 additions and 85 deletions.
15 changes: 14 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -229,7 +229,20 @@ Relay [specification for mutations](https://facebook.github.io/relay/graphql/mut

### Connection

You can use `@GraphQLConnection` annotation to make a field iterable in adherence to Relay [Connection specification](https://facebook.github.io/relay/graphql/connections.htm).
You can use `@GraphQLConnection` annotation to make a field iterable in adherence to Relay [Connection specification](https://facebook.github.io/relay/graphql/connections.htm).\
If a field is annotated with the annotation, the associated dataFetcher must return an instance of `PaginatedData`.\
The `PaginatedData` class holds the result of the connection:
1. The data of the page
2. Whether or not there is a next page and a previous page
3. A method that returns for each entity the encoded cursor of the entity (it returns string)

For you convenience, there is `AbstractPaginatedData` that can be extended.

If you want to use you own implementation of connection, that's fine, just give a value to connection().\
Please note that if you do so, you also have to specify your own connection validator that implements `ConnectionValidator`\
(and should throw `@GraphQLConnectionException` if something is wrong)

NOTE: because `PropertyDataFetcher` and `FieldDataFetcher` can't handle connection, this annotation cant be used on a field that doesn't have a dataFetcher

### Customizing Relay schema

Expand Down
7 changes: 3 additions & 4 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ idea {
vcs = 'Git'
}
}

release {
tagTemplate = 'v${version}'
failOnPublishNeeded = false
Expand Down Expand Up @@ -152,12 +151,12 @@ bintray {
vcsUrl = 'https://github.com/graphql-java/graphql-java-annotations'
version {
name = project.version
released = new Date()
released = new Date()
}
}
}

task bundle(type: Bundle) {
from sourceSets.main.output
bndfile = file('bundle.bnd')
from sourceSets.main.output
bndfile = file('bundle.bnd')
}
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/**
* Copyright 2016 Yurii Rashkovskii
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
*/
package graphql.annotations.connection;

import java.util.Iterator;

public abstract class AbstractPaginatedData<T> implements PaginatedData<T> {

boolean hasPreviousPage;
boolean hasNextPage;
Iterable<T> data;

public AbstractPaginatedData(boolean hasPreviousPage, boolean hasNextPage, Iterable<T> data) {
this.hasNextPage = hasNextPage;
this.hasPreviousPage = hasPreviousPage;
this.data = data;
}

@Override
public boolean hasNextPage() {
return hasNextPage;
}

@Override
public boolean hasPreviousPage() {
return hasPreviousPage;
}

@Override
public Iterator<T> iterator() {
return data.iterator();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/**
* Copyright 2016 Yurii Rashkovskii
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
*/
package graphql.annotations.connection;

import graphql.relay.Connection;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.Optional;

import static graphql.annotations.processor.util.ReflectionKit.constructNewInstance;


public class ConnectionDataFetcher<T> implements DataFetcher<Connection<T>> {
private final DataFetcher<?> actualDataFetcher;
private final Constructor<ConnectionFetcher<T>> constructor;

@SuppressWarnings("unchecked")
public ConnectionDataFetcher(Class<? extends ConnectionFetcher<T>> connection, DataFetcher<?> actualDataFetcher) {
this.actualDataFetcher = actualDataFetcher;
Optional<Constructor<ConnectionFetcher<T>>> constructor =
Arrays.stream(connection.getConstructors()).
filter(c -> c.getParameterCount() == 1).
map(c -> (Constructor<ConnectionFetcher<T>>) c).
findFirst();
if (constructor.isPresent()) {
this.constructor = constructor.get();
} else {
throw new IllegalArgumentException(connection.getSimpleName() + " doesn't have a single argument constructor");
}
}

@Override
public Connection<T> get(DataFetchingEnvironment environment) {
ConnectionFetcher<T> conn = constructNewInstance(constructor, actualDataFetcher);
return conn.get(environment);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/**
* Copyright 2016 Yurii Rashkovskii
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
*/
package graphql.annotations.connection;

import graphql.relay.Connection;
import graphql.schema.DataFetcher;

public interface ConnectionFetcher<T> extends DataFetcher<Connection<T>> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright 2016 Yurii Rashkovskii
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
*/
package graphql.annotations.connection;

import java.lang.reflect.AccessibleObject;

public interface ConnectionValidator {

void validate(AccessibleObject field);
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,10 +12,8 @@
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
*/
package graphql.annotations.annotationTypes;
package graphql.annotations.connection;

import graphql.annotations.dataFetchers.connection.Connection;
import graphql.annotations.dataFetchers.connection.DispatchingConnection;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
Expand All @@ -24,7 +22,7 @@

/**
* Specifies that the annotated field or method (given it is also
* annotated with {@link GraphQLField}) is a collection that will
* annotated with {@link graphql.annotations.annotationTypes.GraphQLField}) is a collection that will
* be adhering <a href="https://facebook.github.io/relay/graphql/connections.htm">Relay Connection specification</a>
*
* At the moment, the only allowed type for such field is <code>List&lt;?&gt;</code>
Expand All @@ -33,16 +31,25 @@
@Retention(RetentionPolicy.RUNTIME)
public @interface GraphQLConnection {
/**
* By default, a simple List connection is specified, but can be overridden using
* this property to allow for more efficient fetching procedures (limiting database queries, etc.)
* By default, a paginated data connection is specified.
* this property allows for more efficient fetching procedures (limiting database queries, etc.)
* NOTE: if you override this, you should also override the validator field, and specify
* your own connection validator
* @return a connection class
*/
Class<? extends Connection> connection() default DispatchingConnection.class;
Class<? extends ConnectionFetcher> connection() default PaginatedDataConnectionFetcher.class;

/**
* By default, wrapped type's name is used for naming TypeConnection, but can be overridden
* using this property
* @return the wrapped type's name
*/
String name() default "";

/**
* By default, the the validator validates a paginated data connection.
* Can be overridden (and should be) if you are using a custom connection
* @return a connection validator
*/
Class <? extends ConnectionValidator> validator() default PaginatedDataConnectionTypeValidator.class;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* Copyright 2016 Yurii Rashkovskii
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
*/
package graphql.annotations.connection;

class GraphQLConnectionException extends RuntimeException {

GraphQLConnectionException(String error) {
super(error);
}
}
48 changes: 48 additions & 0 deletions src/main/java/graphql/annotations/connection/PaginatedData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/**
* Copyright 2016 Yurii Rashkovskii
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
*/
package graphql.annotations.connection;

/**
* This class is the result of a connection. Every Graphql connection field must return this interface
* <p>
* NOTE: this interface extends Iterable. The data is retrieved from the "iterator" function.
* Please implement the iterator with data structure that has order
*
* @param <T> the data of which we paginated over
*/
public interface PaginatedData<T> extends Iterable<T> {

/**
* Whether or not this is the last page
*
* @return true if there is a next page, false otherwise
*/
boolean hasNextPage();

/**
* Whether or not this is the first page
*
* @return true if there is a previous page, false otherwise
*/
boolean hasPreviousPage();

/**
* get the encoded cursor of the entity
*
* @param entity the entity
* @return String representation of the cursor of the entity
*/
String getCursor(T entity);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
/**
* Copyright 2016 Yurii Rashkovskii
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
*/
package graphql.annotations.connection;

import graphql.relay.*;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;

/**
* Use this class in {@link GraphQLConnection} to do a real pagination,
* i.e you fetch each time the relevant data, you make the cursors and
* you decide if there are previous or next pages
* <p>
* Note: If you are using the connection, the return type of the associated dataFetcher must implement {@link PaginatedData}
*
* @param <T> the entity type that is paginated
*/
public class PaginatedDataConnectionFetcher<T> implements ConnectionFetcher<T> {

private DataFetcher<PaginatedData<T>> paginationDataFetcher;

public PaginatedDataConnectionFetcher(DataFetcher<PaginatedData<T>> paginationDataFetcher) {
this.paginationDataFetcher = paginationDataFetcher;
}

@Override
public Connection<T> get(DataFetchingEnvironment environment) {
PaginatedData<T> paginatedData = paginationDataFetcher.get(environment);
if (paginatedData == null) {
return new DefaultConnection<>(Collections.emptyList(), new DefaultPageInfo(null,null,false,false));
}
List<Edge<T>> edges = buildEdges(paginatedData);
PageInfo pageInfo = getPageInfo(edges, paginatedData);
return new DefaultConnection<>(edges, pageInfo);
}

private PageInfo getPageInfo(List<Edge<T>> edges, PaginatedData<T> paginatedData) {
ConnectionCursor firstCursor = edges.get(0).getCursor();
ConnectionCursor lastCursor = edges.get(edges.size() - 1).getCursor();
return new DefaultPageInfo(
firstCursor,
lastCursor,
paginatedData.hasPreviousPage(),
paginatedData.hasNextPage()
);
}

private List<Edge<T>> buildEdges(PaginatedData<T> paginatedData) {
Iterator<T> data = paginatedData.iterator();
List<Edge<T>> edges = new ArrayList<>();
for (; data.hasNext(); ) {
T entity = data.next();
edges.add(new DefaultEdge<>(entity, new DefaultConnectionCursor(paginatedData.getCursor(entity))));
}
return edges;
}
}
Loading

0 comments on commit 496ed16

Please sign in to comment.