graphql-java-annotations
graphql-java-annotations copied to clipboard
Enhance Java POJOs such that fields can be automatically included similar to Java XML bindings
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;
}