Skip to content
This repository was archived by the owner on Mar 4, 2025. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
491 changes: 249 additions & 242 deletions pom.xml

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@

import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;

import java.lang.annotation.Annotation;

public interface DirectiveCaller<T extends Annotation> extends DirectiveOperation<T> {
public Object process(T annotation, DataFetchingEnvironment env, DataFetcher<?> fetcher) throws Exception;
}
}
25 changes: 19 additions & 6 deletions src/main/java/com/fleetpin/graphql/builder/DirectiveProcessor.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,17 @@

import com.fleetpin.graphql.builder.annotations.Directive;
import graphql.introspection.Introspection;
import graphql.schema.*;
import graphql.schema.GraphQLAppliedDirective;
import graphql.schema.GraphQLAppliedDirectiveArgument;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLDirective;
import jakarta.validation.Constraint;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.*;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;

Expand All @@ -22,15 +28,22 @@ public DirectiveProcessor(GraphQLDirective directive, Map<String, Function<Objec

public static DirectiveProcessor build(EntityProcessor entityProcessor, Class<? extends Annotation> directive) {
var builder = GraphQLDirective.newDirective().name(directive.getSimpleName());
var validLocations = directive.getAnnotation(Directive.class).value();
Introspection.DirectiveLocation[] validLocations = null;

if (!directive.isAnnotationPresent(Directive.class) && directive == Constraint.class) {
validLocations = new Introspection.DirectiveLocation[] { Introspection.DirectiveLocation.ARGUMENT_DEFINITION };
} else {
validLocations = directive.getAnnotation(Directive.class).value();

// Check for repeatable tag in annotation and add it
builder.repeatable(directive.getAnnotation(Directive.class).repeatable());
}

// loop through and add valid locations
for (Introspection.DirectiveLocation location : validLocations) {
builder.validLocation(location);
}

// Check for repeatable tag in annotation and add it
builder.repeatable(directive.getAnnotation(Directive.class).repeatable());

// Go through each argument and add name/type to directive
var methods = directive.getDeclaredMethods();
Map<String, Function<Object, GraphQLAppliedDirectiveArgument>> builders = new HashMap<>();
Expand Down
50 changes: 21 additions & 29 deletions src/main/java/com/fleetpin/graphql/builder/DirectivesSchema.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,22 @@

import com.fleetpin.graphql.builder.annotations.DataFetcherWrapper;
import com.fleetpin.graphql.builder.annotations.Directive;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.GraphQLAppliedDirective;
import graphql.schema.GraphQLDirective;
import graphql.schema.*;
import jakarta.validation.Constraint;
import jakarta.validation.constraints.Size;
import org.reactivestreams.Publisher;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
import java.util.function.Consumer;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.reactivestreams.Publisher;

import static graphql.Scalars.GraphQLInt;

class DirectivesSchema {

Expand Down Expand Up @@ -63,7 +62,7 @@ public static DirectivesSchema build(List<RestrictTypeFactory<?>> globalDirectiv
}
continue;
}
if (!directiveType.isAnnotationPresent(Directive.class)) {
if (!directiveType.isAnnotationPresent(Directive.class) && directiveType != Constraint.class) {
continue;
}
if (!directiveType.isAnnotation()) {
Expand Down Expand Up @@ -179,25 +178,10 @@ private <T> CompletableFuture<Object> applyRestrict(RestrictType restrict, Objec
}
}

private static <T> CompletableFuture<List<T>> all(List<CompletableFuture<T>> toReturn) {
return CompletableFuture
.allOf(toReturn.toArray(CompletableFuture[]::new))
.thenApply(__ ->
toReturn
.stream()
.map(m -> {
try {
return m.get();
} catch (InterruptedException | ExecutionException e) {
throw new RuntimeException(e);
}
})
.collect(Collectors.toList())
);
}

public void addSchemaDirective(AnnotatedElement element, Class<?> location, Consumer<GraphQLAppliedDirective> builder) {
for (Annotation annotation : element.getAnnotations()) {
convertJakartaAnnotationsToConstraintDirectives(builder, annotation);

var processor = this.directiveProcessors.get(annotation.annotationType());
if (processor != null) {
try {
Expand All @@ -209,10 +193,18 @@ public void addSchemaDirective(AnnotatedElement element, Class<?> location, Cons
}
}

private static void convertJakartaAnnotationsToConstraintDirectives(Consumer<GraphQLAppliedDirective> builder, Annotation annotation) {
// convert all jakarta validation annotations to a corresponding constraint directive
if (annotation instanceof Size size) {
builder.accept(GraphQLAppliedDirective.newDirective().name("Constraint").argument(GraphQLAppliedDirectiveArgument
.newArgument().name("min").type(GraphQLInt).valueProgrammatic(size.min())).build());
}
}

public void processDirectives(EntityProcessor ep) { // Replacement of processSDL
Map<Class<? extends Annotation>, DirectiveProcessor> directiveProcessors = new HashMap<>();
Map<Class<? extends Annotation>, DirectiveProcessor> directiveProcessorsMap = new HashMap<>();

this.directives.forEach(dir -> directiveProcessors.put(dir, DirectiveProcessor.build(ep, dir)));
this.directiveProcessors = directiveProcessors;
this.directives.forEach(dir -> directiveProcessorsMap.put(dir, DirectiveProcessor.build(ep, dir)));
this.directiveProcessors = directiveProcessorsMap;
}
}
53 changes: 6 additions & 47 deletions src/main/java/com/fleetpin/graphql/builder/MethodProcessor.java
Original file line number Diff line number Diff line change
@@ -1,30 +1,18 @@
package com.fleetpin.graphql.builder;

import static com.fleetpin.graphql.builder.EntityUtil.isContext;

import com.fleetpin.graphql.builder.annotations.Directive;
import com.fleetpin.graphql.builder.annotations.GraphQLDeprecated;
import com.fleetpin.graphql.builder.annotations.GraphQLDescription;
import com.fleetpin.graphql.builder.annotations.Mutation;
import com.fleetpin.graphql.builder.annotations.Query;
import com.fleetpin.graphql.builder.annotations.Subscription;
import com.fleetpin.graphql.builder.annotations.*;
import graphql.GraphQLContext;
import graphql.schema.DataFetcher;
import graphql.schema.DataFetchingEnvironment;
import graphql.schema.FieldCoordinates;
import graphql.schema.GraphQLAppliedDirective;
import graphql.schema.GraphQLAppliedDirectiveArgument;
import graphql.schema.GraphQLArgument;
import graphql.schema.GraphQLCodeRegistry;
import graphql.schema.GraphQLFieldDefinition;
import graphql.schema.*;
import graphql.schema.GraphQLFieldDefinition.Builder;
import graphql.schema.GraphQLObjectType;

import java.lang.annotation.Annotation;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.function.Function;

import static com.fleetpin.graphql.builder.EntityUtil.isContext;

class MethodProcessor {

private final DataFetcherRunner dataFetcherRunner;
Expand Down Expand Up @@ -109,19 +97,7 @@ Builder process(AuthorizerSchema authorizer, FieldCoordinates coordinates, TypeM
argument.description(description.value());
}

for (Annotation annotation : parameter.getAnnotations()) {
// Check to see if the annotation is a directive
if (!annotation.annotationType().isAnnotationPresent(Directive.class)) {
continue;
}
var annotationType = annotation.annotationType();
// Get the values out of the directive annotation
var methods = annotationType.getDeclaredMethods();

// Get the applied directive and add it to the argument
var appliedDirective = getAppliedDirective(annotation, annotationType, methods);
argument.withAppliedDirective(appliedDirective);
}
entityProcessor.addSchemaDirective(parameter, method.getDeclaringClass(), argument::withAppliedDirective);

argument.name(EntityUtil.getName(parameter.getName(), parameter));
// TODO: argument.defaultValue(defaultValue)
Expand All @@ -133,23 +109,6 @@ Builder process(AuthorizerSchema authorizer, FieldCoordinates coordinates, TypeM
return field;
}

private GraphQLAppliedDirective getAppliedDirective(Annotation annotation, Class<? extends Annotation> annotationType, Method[] methods)
throws IllegalAccessException, InvocationTargetException {
var appliedDirective = new GraphQLAppliedDirective.Builder().name(annotationType.getSimpleName());
for (var definedMethod : methods) {
var name = definedMethod.getName();
var value = definedMethod.invoke(annotation);
if (value == null) {
continue;
}

TypeMeta innerMeta = new TypeMeta(null, definedMethod.getReturnType(), definedMethod.getGenericReturnType());
var argumentType = entityProcessor.getEntity(innerMeta).getInputType(innerMeta, definedMethod.getAnnotations());
appliedDirective.argument(GraphQLAppliedDirectiveArgument.newArgument().name(name).type(argumentType).valueProgrammatic(value).build());
}
return appliedDirective.build();
}

private <T extends Annotation> DataFetcher<?> buildFetcher(DirectivesSchema diretives, AuthorizerSchema authorizer, Method method, TypeMeta meta) {
DataFetcher<?> fetcher = buildDataFetcher(meta, method);
fetcher = diretives.wrap(method, meta, fetcher);
Expand Down
98 changes: 59 additions & 39 deletions src/main/java/com/fleetpin/graphql/builder/SchemaBuilder.java
Original file line number Diff line number Diff line change
Expand Up @@ -14,13 +14,16 @@
import com.fleetpin.graphql.builder.annotations.*;
import graphql.schema.GraphQLScalarType;
import graphql.schema.GraphQLSchema;
import jakarta.validation.Constraint;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.reflections.Reflections;
import org.reflections.scanners.Scanners;

public class SchemaBuilder {

Expand Down Expand Up @@ -75,7 +78,7 @@ private graphql.schema.GraphQLSchema.Builder build(Set<Class<? extends SchemaCon
builder.subscription(subscriptions);
}

directives.getSchemaDirective().forEach(directive -> builder.additionalDirective(directive));
directives.getSchemaDirective().forEach(builder::additionalDirective);

for (var schema : schemaConfiguration) {
this.directives.addSchemaDirective(schema, schema, builder::withSchemaAppliedDirective);
Expand Down Expand Up @@ -131,41 +134,7 @@ public GraphQLSchema.Builder build() {

Set<Class<? extends SchemaConfiguration>> schemaConfiguration = reflections.getSubTypesOf(SchemaConfiguration.class);

Set<Class<?>> directivesTypes = reflections.getTypesAnnotatedWith(Directive.class);
directivesTypes.addAll(reflections.getTypesAnnotatedWith(DataFetcherWrapper.class));

Set<Class<?>> restrict = reflections.getTypesAnnotatedWith(Restrict.class);
Set<Class<?>> restricts = reflections.getTypesAnnotatedWith(Restricts.class);
List<RestrictTypeFactory<?>> globalRestricts = new ArrayList<>();

for (var r : restrict) {
Restrict annotation = EntityUtil.getAnnotation(r, Restrict.class);
var factoryClass = annotation.value();
var factory = factoryClass.getConstructor().newInstance();
if (!factory.extractType().isAssignableFrom(r)) {
throw new RuntimeException(
"Restrict annotation does match class applied to targets" + factory.extractType() + " but was on class " + r
);
}
globalRestricts.add(factory);
}

for (var r : restricts) {
Restricts annotations = EntityUtil.getAnnotation(r, Restricts.class);
for (Restrict annotation : annotations.value()) {
var factoryClass = annotation.value();
var factory = factoryClass.getConstructor().newInstance();

if (!factory.extractType().isAssignableFrom(r)) {
throw new RuntimeException(
"Restrict annotation does match class applied to targets" + factory.extractType() + " but was on class " + r
);
}
globalRestricts.add(factory);
}
}

DirectivesSchema directivesSchema = DirectivesSchema.build(globalRestricts, directivesTypes); // Entry point for directives
DirectivesSchema directivesSchema = getDirectivesSchema(reflections);

Set<Class<?>> types = reflections.getTypesAnnotatedWith(Entity.class);

Expand All @@ -178,7 +147,7 @@ public GraphQLSchema.Builder build() {
endPoints.addAll(queries);

types.removeIf(t -> t.getDeclaredAnnotation(Entity.class) == null);
types.removeIf(t -> t.isAnonymousClass());
types.removeIf(Class::isAnonymousClass);

return new SchemaBuilder(dataFetcherRunner, scalars, directivesSchema, authorizer)
.processTypes(types)
Expand All @@ -188,5 +157,56 @@ public GraphQLSchema.Builder build() {
throw new RuntimeException(e);
}
}

private static DirectivesSchema getDirectivesSchema(Reflections reflections) throws ReflectiveOperationException {
Set<Class<?>> directivesTypes = reflections.getTypesAnnotatedWith(Directive.class);
directivesTypes.addAll(reflections.getTypesAnnotatedWith(DataFetcherWrapper.class));

addAllJakartaAnnotations(directivesTypes);

List<RestrictTypeFactory<?>> globalRestricts = getGlobalRestricts(reflections);

return DirectivesSchema.build(globalRestricts, directivesTypes); // Entry point for directives
}

private static void addAllJakartaAnnotations(Set<Class<?>> directivesTypes) {
// make sure the Constraint directive is added to the schema
directivesTypes.add(Constraint.class);
}

private static List<RestrictTypeFactory<?>> getGlobalRestricts(Reflections reflections)
throws InstantiationException, IllegalAccessException, InvocationTargetException, NoSuchMethodException {
Set<Class<?>> restrict = reflections.getTypesAnnotatedWith(Restrict.class);
Set<Class<?>> restricts = reflections.getTypesAnnotatedWith(Restricts.class);
List<RestrictTypeFactory<?>> globalRestricts = new ArrayList<>();

for (var r : restrict) {
Restrict annotation = EntityUtil.getAnnotation(r, Restrict.class);
var factoryClass = annotation.value();
var factory = factoryClass.getConstructor().newInstance();
if (!factory.extractType().isAssignableFrom(r)) {
throw new RuntimeException(
"Restrict annotation does match class applied to targets" + factory.extractType() + " but was on class " + r
);
}
globalRestricts.add(factory);
}

for (var r : restricts) {
Restricts annotations = EntityUtil.getAnnotation(r, Restricts.class);
for (Restrict annotation : annotations.value()) {
var factoryClass = annotation.value();
var factory = factoryClass.getConstructor().newInstance();

if (!factory.extractType().isAssignableFrom(r)) {
throw new RuntimeException(
"Restrict annotation does match class applied to targets" + factory.extractType() + " but was on class " + r
);
}
globalRestricts.add(factory);
}
}
return globalRestricts;
}
}
}
Loading
Loading