Enigmatis / graphql-java-annotations

GraphQL Annotations for Java
Other
387 stars 97 forks source link

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

Closed blongstreth closed 1 year ago

blongstreth commented 4 years ago

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;
}
Fgerthoffert commented 1 year ago

Hi @blongstreth ,

We're helping with project maintenance and reviewing the list of opened PRs and Issues.

This issue was created quite a while ago, we were wondering if you were still interested in the outcome, please let us know if this is the case.

Without an answer by July 1st, 2023, this issue will be closed as "inactive" (and can always be re-opened later on if needed).

Thanks,