Skip to content

Commit

Permalink
Merge pull request #22 from nfl/better-connection-creation
Browse files Browse the repository at this point in the history
adding a name registry to allow for reusable connections as long as the underlying GraphQL types are equal
  • Loading branch information
vaant authored Sep 26, 2017
2 parents 3bc1b86 + 0b022b2 commit 44c9683
Show file tree
Hide file tree
Showing 10 changed files with 379 additions and 92 deletions.
2 changes: 1 addition & 1 deletion gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ maven.central.sync=false
sonatype.username=DUMMY_SONATYPE_USER
sonatype.password=DUMMY_SONATYPE_PASSWORD

PROJECT_VERSION=1.1.6
PROJECT_VERSION=1.2.1
PROJECT_GITHUB_REPO_URL=https://github.com/nfl/glitr
PROJECT_LICENSE_URL=https://github.com/nfl/glitr/blob/master/LICENSE
194 changes: 194 additions & 0 deletions src/main/java/com/nfl/glitr/registry/GlitrTypeMap.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,194 @@
package com.nfl.glitr.registry;

import com.nfl.glitr.exception.GlitrException;
import graphql.schema.GraphQLType;

import java.util.Collection;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;

/**
* A map implementation to sync the two kinds of registries currently in {@link com.nfl.glitr.registry.TypeRegistry}.
* Syncs happen dynamically, keeping the nameRegistry appraised of classRegistry additions to ensure unique GraphQLTypes
*/
public class GlitrTypeMap implements ConcurrentMap {

private final Map<Class, GraphQLType> classRegistry = new ConcurrentHashMap<>();
private final Map<String, GraphQLType> nameRegistry = new ConcurrentHashMap<>();


@Override
public Object getOrDefault(Object key, Object defaultValue) {
if (isClass(key)) {
return classRegistry.getOrDefault(key, (GraphQLType) defaultValue);
} else if (isString(key)) {
return nameRegistry.getOrDefault(key, (GraphQLType) defaultValue);
}
throw new GlitrException("Unsupported type passed as key to GlitrTypeMap");
}

@Override
public Object putIfAbsent(Object key, Object value) {
if (isClass(key)) {
nameRegistry.putIfAbsent(((Class) key).getSimpleName(), (GraphQLType) value);
return classRegistry.putIfAbsent((Class) key, (GraphQLType) value);
} else if (isString(key)) {
return nameRegistry.putIfAbsent((String) key, (GraphQLType) value);
}
throw new GlitrException("Unsupported type passed as key to GlitrTypeMap");
}

@Override
public boolean remove(Object key, Object value) {
if (isClass(key)) {
nameRegistry.remove(((Class) key).getSimpleName(), value);
return classRegistry.remove(key, value);
} else if (isString(key)) {
return nameRegistry.remove(key, value);
}
throw new GlitrException("Unsupported type passed as key to GlitrTypeMap");
}

@Override
public boolean replace(Object key, Object oldValue, Object newValue) {
if (isClass(key)) {
nameRegistry.replace(((Class) key).getSimpleName(), (GraphQLType) oldValue, (GraphQLType) newValue);
return classRegistry.replace((Class) key, (GraphQLType) oldValue, (GraphQLType) newValue);
} else if (isString(key)) {
return nameRegistry.replace((String) key, (GraphQLType) oldValue, (GraphQLType) newValue);
}
throw new GlitrException("Unsupported type passed as key to GlitrTypeMap");
}

@Override
public Object replace(Object key, Object value) {
if (isClass(key)) {
nameRegistry.replace(((Class) key).getSimpleName(), (GraphQLType) value);
return classRegistry.replace((Class) key, (GraphQLType) value);
} else if (isString(key)) {
return nameRegistry.replace((String) key, (GraphQLType) value);
}
throw new GlitrException("Unsupported type passed as key to GlitrTypeMap");
}

@Override
public boolean isEmpty() {
return nameRegistry.isEmpty();
}

@Override
public boolean containsKey(Object key) {
if (isClass(key)) {
return classRegistry.containsKey(key);
} else if (isString(key)) {
return nameRegistry.containsKey(key);
}
throw new GlitrException("Unsupported type passed as key to GlitrTypeMap");
}

@Override
public boolean containsValue(Object value) {
return nameRegistry.containsValue(value);
}

@Override
public Object get(Object key) {
if (isClass(key)) {
return classRegistry.get(key);
} else if (isString(key)) {
return nameRegistry.get(key);
}
throw new GlitrException("Unsupported type passed as key to GlitrTypeMap");
}

@Override
public Object put(Object key, Object value) {
if (isClass(key)) {
nameRegistry.put(((Class) key).getSimpleName(), (GraphQLType) value);
return classRegistry.put((Class) key, (GraphQLType) value);
} else if (isString(key)) {
return nameRegistry.put((String) key, (GraphQLType) value);
}
throw new GlitrException("Unsupported type passed as key to GlitrTypeMap");
}

@Override
public Object remove(Object key) {
if (isClass(key)) {
nameRegistry.remove(((Class) key).getSimpleName());
return classRegistry.remove(key);
} else if (isString(key)) {
return nameRegistry.remove(key);
}
throw new GlitrException("Unsupported type passed as key to GlitrTypeMap");
}

@Override
public void clear() {
classRegistry.clear();
nameRegistry.clear();
}

@Override
public int size() {
return nameRegistry.size();
}

@Override
public void putAll(Map m) {
Set set = m.keySet();
Object next = set.iterator().next();
if (isClass(next)) {
for (Object o : m.entrySet()) {
Entry pair = (Entry) o;
nameRegistry.put(((Class) pair.getKey()).getSimpleName(), (GraphQLType) pair.getValue());
classRegistry.put((Class) pair.getKey(), (GraphQLType) pair.getValue());
}
} else if (isString(next)) {
nameRegistry.putAll(m);
}
throw new GlitrException("Unsupported type passed as key to GlitrTypeMap");
}

@Override
public Collection values() {
return nameRegistry.values();
}

@Override
public Set keySet() {
throw new GlitrException("Unsupported method, GlitrTypeMap does not support this method to ensure expected return types");
}

@Override
public Set<Entry> entrySet() {
throw new GlitrException("Unsupported method, GlitrTypeMap does not support this method to ensure expected return types");
}

public Set ClassKeySet() {
return classRegistry.keySet();
}

public Set NameKeySet() {
return nameRegistry.keySet();
}

public Set<Entry<Class, GraphQLType>> ClassEntrySet() {
return classRegistry.entrySet();
}

public Set<Entry<String, GraphQLType>> NameEntrySet() {
return nameRegistry.entrySet();
}

private boolean isClass(Object obj) {
return obj instanceof Class;
}

private boolean isString(Object obj) {
return obj instanceof String;
}
}
9 changes: 9 additions & 0 deletions src/main/java/com/nfl/glitr/registry/TypeRegistry.java
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@ public class TypeRegistry implements TypeResolver {
private static final Logger logger = LoggerFactory.getLogger(TypeRegistry.class);

private final Map<Class, GraphQLType> registry = new ConcurrentHashMap<>();
private final Map<String, GraphQLType> nameRegistry = new ConcurrentHashMap<>();
private final Map<Class, List<Object>> overrides;

private final Map<Class<? extends Annotation>, Func4<Field, Method, Class, Annotation, List<GraphQLArgument>>> annotationToArgumentsProviderMap;
Expand Down Expand Up @@ -108,6 +109,7 @@ public class TypeRegistry implements TypeResolver {
this.nodeInterface = relay.nodeInterface(this);
// register Node so we don't inadvertently recreate it later
this.registry.put(Node.class, this.nodeInterface);
this.nameRegistry.put(Node.class.getSimpleName(), this.nodeInterface);
}
this.explicitRelayNodeScanEnabled = explicitRelayNodeScanEnabled;
}
Expand Down Expand Up @@ -175,11 +177,13 @@ public GraphQLType lookupOutput(Class clazz) {

// put a type reference in while building the type to work around circular references
registry.put(clazz, new GraphQLTypeReference(clazz.getSimpleName()));
nameRegistry.put(clazz.getSimpleName(), new GraphQLTypeReference(clazz.getSimpleName()));

GraphQLOutputType type = graphQLTypeFactory.createGraphQLOutputType(clazz);

if (type != null) {
registry.put(clazz, type);
nameRegistry.put(clazz.getSimpleName(), type);
} else {
throw new IllegalArgumentException("Unable to create GraphQLOutputType for: " + clazz.getCanonicalName());
}
Expand All @@ -203,6 +207,7 @@ public GraphQLType lookupInput(Class clazz) {

if (type != null) {
registry.put(clazz, type);
nameRegistry.put(clazz.getSimpleName(), type);
} else {
throw new IllegalArgumentException("Unable to create GraphQLInputType for: " + clazz.getCanonicalName());
}
Expand Down Expand Up @@ -644,4 +649,8 @@ private GraphQLType getGraphQLTypeForInputParameterizedType(Type type) {
throw new IllegalArgumentException("Unable to convert type " + type.getTypeName() + " to GraphQLInputType");
}
}

public Map<String, GraphQLType> getNameRegistry() {
return nameRegistry;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.google.common.collect.Lists;
import com.googlecode.gentyref.GenericTypeReflector;
import com.nfl.glitr.exception.GlitrException;
import com.nfl.glitr.relay.RelayHelper;
import com.nfl.glitr.util.ReflectionUtil;
import com.nfl.glitr.annotation.GlitrForwardPagingArguments;
Expand All @@ -10,6 +11,7 @@
import graphql.schema.GraphQLNonNull;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLOutputType;
import graphql.schema.GraphQLType;
import rx.functions.Func4;

import javax.annotation.Nullable;
Expand All @@ -19,6 +21,7 @@
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.Collections;
import java.util.Map;

/**
* Output type converter function for paging arguments annotations.
Expand Down Expand Up @@ -64,8 +67,24 @@ public GraphQLOutputType call(@Nullable Field field, Method method, Class declar
edgeGraphQLOutputType,
relayHelper.getNodeInterface(),
Collections.emptyList());
// last build the relay connection!
return relayHelper.connectionType(endEdgeClass.getSimpleName(), edgeType, Lists.newArrayList());
// build the relay connection
GraphQLObjectType connectionType = relayHelper.connectionType(endEdgeClass.getSimpleName(), edgeType, Lists.newArrayList());

// check if a connection with this name already exists
Map<String, GraphQLType> nameRegistry = typeRegistry.getNameRegistry();
GraphQLObjectType qlObjectType = (GraphQLObjectType) nameRegistry.get(connectionType.getName());
if (qlObjectType != null) {
// TODO: better equality function
if (!qlObjectType.toString().equals(connectionType.toString())) {
throw new GlitrException("Attempting to create two types with the same name. All types within a GraphQL schema must have unique names. " +
"You have defined the type [" + connectionType.getName() + "] as both [" + qlObjectType + "] and [" + connectionType + "]");
}
return qlObjectType;
}

// add the connection to the registry and return the connection
nameRegistry.put(connectionType.getName(), connectionType);
return connectionType;
}

public PagingOutputTypeConverter setTypeRegistry(TypeRegistry typeRegistry) {
Expand Down
23 changes: 21 additions & 2 deletions src/test/groovy/com/nfl/glitr/data/query/QueryType.java
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,27 @@ public Video getVideo() {
}

@GlitrArgument(name = "id", type = String.class, nullable = false)
public com.nfl.glitr.relay.Node getNode() { return null; }
public com.nfl.glitr.relay.Node getNode() {
return null;
}

@GlitrArgument(name = "ids", type = String[].class, nullable = false)
public List<com.nfl.glitr.relay.Node> getNodes() { return null; }
public List<com.nfl.glitr.relay.Node> getNodes() {
return null;
}

@GlitrForwardPagingArguments
public List<Video> getOtherVideos() {
return null;
}

@GlitrArgument(name = "ids", type = String[].class, nullable = false)
public List<com.nfl.glitr.relay.Node> getZZZNodes() {
return null;
}

// no arguments
public List<Video> getZZZVideos() {
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,32 +1,34 @@
package com.nfl.glitr.data.query.additionalTypes;

public class Cyborg implements Person {
public String getId() {
return id;
}

public String getCodeName() {
return codeName;
}

@Override
public String getAge() {
return this.age;
}

public void setId(String id) {
this.id = id;
}

public void setCodeName(String codeName) {
this.codeName = codeName;
}

public void setAge(String age) {
this.age = age;
}

private String id;
private String codeName;
private String age;
}

private String id;
private String codeName;
private String age;


public String getId() {
return id;
}

public String getCodeName() {
return codeName;
}

@Override
public String getAge() {
return this.age;
}

public void setId(String id) {
this.id = id;
}

public void setCodeName(String codeName) {
this.codeName = codeName;
}

public void setAge(String age) {
this.age = age;
}
}
Loading

0 comments on commit 44c9683

Please sign in to comment.