graphql-java-annotations icon indicating copy to clipboard operation
graphql-java-annotations copied to clipboard

Enhance Java POJOs such that fields can be automatically included similar to Java XML bindings

Open blongstreth opened this issue 4 years ago • 0 comments

Hello:

I am wondering if the ability to include fields automatically from any regular Java object without having to annotate each field with @GraphQLField could be something you would consider? The behavior could be similar to Java XML bindings. As a simple first cut, any regular Java object annotated with @GraphQLObject would automatically have their fields included in the GraphQL object type rather than having to annotate each field with @GraphQLField. For example:

Defining Objects

Any regular Java class can be converted to a GraphQL object type when the object is defined with a @GraphQLObject annotation:

@GraphQLObject
public class SomeObject {
  // Field can be public, protected, private, etc.
  public String field;
}

Long-term it would be nice to provide similar functionality to javax.xml.bind.annotation.XmlAccessType and javax.xml.bind.annotation.XmlRootElement. Anyway, I have provided a proof of concept listed below by hacking GraphQLAnnotations class such that it can take a custom GraphQLObjectInfoRetriever. At the very least, if someone could update the GraphQLAnnotations to allow the ability to customize whether a Java object field is to be included or not would be greatly appreciated for I just don't know how future proof the hack will be.

Finally, I wanted to mention that having the ability to import POJOs from libraries which don't allow for annotations would be another feature to consider. See: Support Mixins #242. This is obviously is a more complicated feature but the mechanism could be achieved if we had the ability to provide a custom GraphQLObjectInfoRetriever.

Regards,

Bradley

Custom annotation used to mark a Java object as a GraphQL object for which all declared fields will be used:

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface GraphQLObject
{
    boolean value() default true;
}

Hack to include object fields:

private static class HackedGraphQLObjectInfoRetriever extends GraphQLObjectInfoRetriever
{
   public Boolean isGraphQLField(AnnotatedElement element)
   {
      Boolean result = null;

      if (Field.class.isAssignableFrom(element.getClass()))
      {
         final Field field = Field.class.cast(element);
         if (field.getDeclaringClass().getAnnotation(GraphQLObject.class) != null)
         {
            result = true;
         }
      }

      if (result == null)
      {
         result = super.isGraphQLField(element);
      }

      return result;
   }
}

Ugly, ugly hack which mocks (demonstrates) the GraphQLAnnotations default constructor:

public static GraphQLAnnotations newGraphQLAnnotations()
{
    // Mock what GraphQLAnnotations default constructor does
    final GraphQLObjectHandler objectHandler = new GraphQLObjectHandler();
    final GraphQLTypeRetriever typeRetriever = new GraphQLTypeRetriever();
    final GraphQLObjectInfoRetriever objectInfoRetriever = new HackedGraphQLObjectInfoRetriever();
    final GraphQLInterfaceRetriever interfaceRetriever = new GraphQLInterfaceRetriever();
    final GraphQLFieldRetriever fieldRetriever = new GraphQLFieldRetriever();
    final GraphQLInputProcessor inputProcessor = new GraphQLInputProcessor();
    final GraphQLOutputProcessor outputProcessor = new GraphQLOutputProcessor();
    final BreadthFirstSearch methodSearchAlgorithm = new BreadthFirstSearch(objectInfoRetriever);
    final ParentalSearch fieldSearchAlgorithm = new ParentalSearch(objectInfoRetriever);
    final DataFetcherConstructor dataFetcherConstructor = new DataFetcherConstructor();
    final GraphQLExtensionsHandler extensionsHandler = new GraphQLExtensionsHandler();
    final DefaultTypeFunction defaultTypeFunction = new DefaultTypeFunction(inputProcessor, outputProcessor);

    objectHandler.setTypeRetriever(typeRetriever);
    typeRetriever.setGraphQLObjectInfoRetriever(objectInfoRetriever);
    typeRetriever.setGraphQLInterfaceRetriever(interfaceRetriever);
    typeRetriever.setMethodSearchAlgorithm(methodSearchAlgorithm);
    typeRetriever.setFieldSearchAlgorithm(fieldSearchAlgorithm);
    typeRetriever.setExtensionsHandler(extensionsHandler);
    typeRetriever.setGraphQLFieldRetriever(fieldRetriever);
    interfaceRetriever.setGraphQLTypeRetriever(typeRetriever);
    fieldRetriever.setDataFetcherConstructor(dataFetcherConstructor);
    inputProcessor.setGraphQLTypeRetriever(typeRetriever);
    outputProcessor.setGraphQLTypeRetriever(typeRetriever);
    extensionsHandler.setGraphQLObjectInfoRetriever(objectInfoRetriever);
    extensionsHandler.setFieldSearchAlgorithm(fieldSearchAlgorithm);
    extensionsHandler.setMethodSearchAlgorithm(methodSearchAlgorithm);
    extensionsHandler.setFieldRetriever(fieldRetriever);

    final GraphQLAnnotations graphqlAnnotations = new GraphQLAnnotations(defaultTypeFunction, objectHandler, extensionsHandler);
    final Field directiveCreatorField;
    try
    {
        directiveCreatorField = graphqlAnnotations.getClass().getDeclaredField("directiveCreator");
    }
    catch (final NoSuchFieldException e)
    {
        throw new RuntimeException(e);
    }

    if (!directiveCreatorField.isAccessible())
    {
        directiveCreatorField.setAccessible(true);
        try
        {
            final ProcessingElementsContainer container = graphqlAnnotations.getContainer();

            final DirectiveArgumentCreator directiveArgumentCreator = new DirectiveArgumentCreator(new CommonPropertiesCreator(),
                  container.getDefaultTypeFunction(), container);

            final DirectiveCreator directiveCreator = new DirectiveCreator(directiveArgumentCreator, new CommonPropertiesCreator());

            directiveCreatorField.set(graphqlAnnotations, directiveCreator);
        }
        catch (final IllegalAccessException e)
        {
            throw new RuntimeException(e);
        }
        finally
        {
            directiveCreatorField.setAccessible(false);
        }
    }
    return graphqlAnnotations;
}

blongstreth avatar Aug 11 '20 18:08 blongstreth