All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.graphql_java_generator.client.request.AbstractGraphQLRequest Maven / Gradle / Ivy

There is a newer version: 1.18
Show newest version
/**
 * 
 */
package com.graphql_java_generator.client.request;

import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Map;

import org.eclipse.jetty.websocket.client.WebSocketClient;

import com.graphql_java_generator.annotation.GraphQLScalar;
import com.graphql_java_generator.annotation.RequestType;
import com.graphql_java_generator.client.GraphQLConfiguration;
import com.graphql_java_generator.client.SubscriptionCallback;
import com.graphql_java_generator.client.SubscriptionClient;
import com.graphql_java_generator.exception.GraphQLRequestExecutionException;
import com.graphql_java_generator.exception.GraphQLRequestPreparationException;

/**
 * This class contains the description for a GraphQL request that will be sent to the server. It's an abstract class,
 * and can not be use directly: a concrete class is generated by the plugin, when in client mode. This concrete class
 * provides all the necessary context to this abstract class for it to work properly.
* This class stores: *
    *
  • The query part, if any
  • *
  • The mutation part, if any
  • *
  • The subscription part, if any
  • *
  • The fragments, if any
  • *
* * @author etienne-sf */ public abstract class AbstractGraphQLRequest { /** * This contains the default configuration, that will apply if no local configuration has been defined for this * instance */ static GraphQLConfiguration staticConfiguration = null; /** * This contains the configuration for this instance. This configuration overrides the {@link #staticConfiguration}, * if defined. */ GraphQLConfiguration instanceConfiguration = null; /** The query, if any */ QueryField query = null; /** The mutation, if any */ QueryField mutation = null; /** The mutation, if any */ QueryField subscription = null; /** All the fragments defined for this query */ List fragments = new ArrayList<>(); /** The string that has been used to create this GraphQL request */ final String graphQLRequest; /** * Null if the request is a full request. Mandatory if the request is a partial request. When this GraphQLRequest is * built for a partial query, that is for a particular query/mutation/subscription, then fieldName states whether * this queryName is actually a query, a mutation or a subscription. */ RequestType requestType; /** * Null if the request is a full request. Mandatory if the request is a partial request.
* When this GraphQLRequest is built for a partial query, that is for a particular query/mutation/subscription, then * queryName is the name of the query, mutation or subscription. This allow to check that the GraphQLRequest is the * good to be executed for this partial query. */ final String queryName; /** * The package name, where the GraphQL generated classes are. It's used to load the class definition, and get the * GraphQL metadata coming from the GraphQL schema. */ protected final String packageName; /** * Create the instance, from the GraphQL request, for a partial request.
* * Important note: this constructor SHOULD NOT be called by external application. Its signature * may change in the future. To prepare Partial Requests, application code SHOULD call the * getXxxxGraphQLRequests methods, that are generated in the query/mutation/subscription java classes. * * @param graphQLRequest * The partial GraphQL request, in text format. Writing partial request allows use to execute a * query/mutation/subscription, and only define what's expected as a response for this * query/mutation/subscription. You can send the parameters for this query/mutation/subscription as * parameter of the java method, without dealing with bind variable in the GraphQL query. Please read the * client doc * page for more information, including hints and limitations. * @param requestType * The information whether this queryName is actually a query, a mutation or a subscription * @param queryName * The name of the query, mutation or subscription * @param inputParams * The list of input parameters for this query/mutation/subscription * @throws GraphQLRequestPreparationException */ public AbstractGraphQLRequest(String graphQLRequest, RequestType requestType, String queryName, InputParameter... inputParams) throws GraphQLRequestPreparationException { if (requestType == null) { throw new NullPointerException("requestType is mandatory, but a null value has been provided"); } if (queryName == null) { throw new NullPointerException("queryName is mandatory, but a null value has been provided"); } this.requestType = requestType; this.queryName = queryName; this.graphQLRequest = graphQLRequest; this.packageName = getGraphQLClassesPackageName(); QueryField field; switch (requestType) { case query: query = getQueryContext();// Get the query field from the concrete class field = new QueryField(query.clazz, queryName); query.fields.add(field); break; case mutation: mutation = getMutationContext();// Get the mutation field from the concrete class field = new QueryField(mutation.clazz, queryName); mutation.fields.add(field); break; case subscription: subscription = getSubscriptionContext();// Get the subscription field from the concrete class field = new QueryField(subscription.clazz, queryName); subscription.fields.add(field); break; default: throw new GraphQLRequestPreparationException("Non managed request type '" + requestType + " while reading the GraphQL request: " + graphQLRequest); } // Let's add the input parameters to this new field field.inputParameters = Arrays.asList(inputParams); // Ok, we have to parse a string which looks like that: "query {human(id: &humanId) { id name friends{name}}}" // We tokenize the string, by using the space as a delimiter, and all other special GraphQL characters QueryTokenizer qt = new QueryTokenizer(this.graphQLRequest); // The graphQLRequest may be null (for instance for a scalar, or if we want the plugin to automatically add all // scalar fields for this query/mutation/subscription) if (!qt.hasMoreTokens()) { // Ok, we're done } else { // The first token must be a { // And we must read it first, before parsing the request content String token = qt.nextToken(); if (!"{".equals(token)) { throw new GraphQLRequestPreparationException( "The Partial GraphQL Request should start by a '{', but it doesn't: " + graphQLRequest); } field.readTokenizerForResponseDefinition(qt); } // Let's finish the job finishRequestPreparation(); } /** * Creates the GraphQL request, for a full request. It will: *
    *
  • Read the query and/or the mutation
  • *
  • Read all fragment definitions
  • *
  • For all non scalar field, subfields (and so on recursively), if they are empty (that is the query doesn't * define the requested fields of a non scalar field, then all its scalar fields are added)
  • *
  • Add the introspection __typename field to all scalar field list, if it doesnt't already exist. This is * necessary to allow proper deserialization of interfaces and unions.
  • *
* * @param graphQLRequest * The GraphQL request, in text format, as defined in the GraphQL specifications, and as it can be used * in GraphiQL. Please read the * client doc * page for more information, including hints and limitations. * * @throws GraphQLRequestPreparationException */ public AbstractGraphQLRequest(String graphQLRequest) throws GraphQLRequestPreparationException { this.queryName = null; this.graphQLRequest = graphQLRequest; this.packageName = getGraphQLClassesPackageName(); this.requestType = RequestType.query; // query is the default value, as if there is no query, mutation or // subscription keyword, then it must be a query. // Ok, we have to parse a string which looks like that: "query {human(id: &humanId) { id name friends{name}}}" // We tokenize the string, by using the space as a delimiter, and all other special GraphQL characters QueryTokenizer qt = new QueryTokenizer(this.graphQLRequest); // We scan the input string. It may contain fragment definition and query/mutation/subscription while (qt.hasMoreTokens()) { String token = qt.nextToken(); switch (token) { case "fragment": fragments.add(new Fragment(qt, packageName, false, null)); break; case "query": case "mutation": case "subscription": requestType = RequestType.valueOf(token); break; case "{": // We read the query/mutation/subscription like any field. switch (requestType) { case query: query = getQueryContext();// Get the query field from the concrete class query.readTokenizerForResponseDefinition(qt); break; case mutation: mutation = getMutationContext();// Get the mutation field from the concrete class mutation.readTokenizerForResponseDefinition(qt); break; case subscription: subscription = getSubscriptionContext();// Get the subscription field from the concrete class subscription.readTokenizerForResponseDefinition(qt); break; default: throw new GraphQLRequestPreparationException("Non managed request type '" + requestType + " while reading the GraphQL request: " + graphQLRequest); } break; default: throw new GraphQLRequestPreparationException( "Unknown token '" + token + " while reading the GraphQL request: " + graphQLRequest); } } if (query == null && mutation == null) { throw new GraphQLRequestPreparationException("No response definition found"); } // Let's finish the job finishRequestPreparation(); } /** * This method executes the current GraphQL as a query or mutation GraphQL request, and return its * response mapped in the relevant POJO. This method executes a partial GraphQL query, or a full GraphQL * request.
* Note: Don't forget to free the server's resources by calling the {@link WebSocketClient#stop()} method of * the returned object. * * @param * @param t * The type of the POJO which should be returned. It must be the query or the mutation class, generated * by the plugin * @param params * @return * @throws GraphQLRequestExecutionException */ public T exec(Class t, Map params) throws GraphQLRequestExecutionException { if (instanceConfiguration != null) { return instanceConfiguration.getQueryExecutor().execute(this, params, t); } else if (staticConfiguration != null) { return staticConfiguration.getQueryExecutor().execute(this, params, t); } else { throw new GraphQLRequestExecutionException( "The GraphQLRequestConfiguration has not been set in the GraphQLRequest. " + "Please set either the GraphQL instance configuration " + "or the GraphQL static configuration before executing a GraphQL request"); } } /** * Execution of the given subscription GraphQL request, and return its response mapped in the relevant POJO. * This method executes a partial GraphQL query, or a full GraphQL request.
* Note: Don't forget to free the server's resources by calling the {@link WebSocketClient#stop()} method of * the returned object. * * @param * The class that is generated from the subscription definition in the GraphQL schema. It contains one * attribute, for each available subscription. The data tag of the GraphQL server response will be mapped * into an instance of this class. * @param * The type that must is returned by the subscription in the GraphQL schema, which is actually the type * that will be sent in each notification received from this subscription. * @param t * The type of the POJO which should be returned. It must be the query or the mutation class, generated * by the plugin * @param params * the input parameters for this query. If the query has no parameters, it may be null or an empty list. * @param subscriptionCallback * The object that will be called each time a message is received, or an error on the subscription * occurs. This object is provided by the application. * @param subscriptionName * The name of the subscription that should be subscribed by this method call. It will be used to check * that the correct GraphQLRequest has been provided by the caller. * @param subscriptionType * The R class * @param messageType * The T class * @return The Subscription client. It allows to stop the subscription, by executing its * {@link SubscriptionClient#unsubscribe()} method. This will stop the incoming notification flow, and will * free resources on both the client and the server. * @throws GraphQLRequestExecutionException * When an error occurs during the request execution, typically a network error, an error from the * GraphQL server or if the server response can't be parsed * @throws IOException */ public SubscriptionClient exec(Map params, SubscriptionCallback subscriptionCallback, String subscriptionName, Class subscriptionType, Class messageType) throws GraphQLRequestExecutionException { if (instanceConfiguration != null) { return instanceConfiguration.getQueryExecutor().execute(this, params, subscriptionCallback, subscriptionName, subscriptionType, messageType); } else if (staticConfiguration != null) { return staticConfiguration.getQueryExecutor().execute(this, params, subscriptionCallback, subscriptionName, subscriptionType, messageType); } else { throw new GraphQLRequestExecutionException( "The GraphQLRequestConfiguration has not been set in the GraphQLRequest. " + "Please set either the GraphQL instance configuration " + "or the GraphQL static configuration before executing a GraphQL request"); } } /** * Adds the __typename fields to all non scalar types * * @param graphQLRequest * @throws GraphQLRequestPreparationException */ private void addTypenameFields() throws GraphQLRequestPreparationException { // We need the __typename fields, to properly parse the JSON response for interfaces and unions. // So we add it for every returned object. if (query != null) { query.addTypenameFields(); } if (mutation != null) { mutation.addTypenameFields(); } if (subscription != null) { subscription.addTypenameFields(); } for (Fragment f : fragments) { f.addTypenameFields(); } } /** * Finish the preparation of the GraphQL request, once everything has been read: *
    *
  • add the scalar fields, for all empty non scalar fields.
  • *
  • Add the __typename field in fragments and field lists, to be sure to get it in return. This is necessary to * properly deserialize the GRaphQL interfaces and unions *
* * @throws GraphQLRequestPreparationException */ private void finishRequestPreparation() throws GraphQLRequestPreparationException { // For each non scalar field, we add its non scalar fields, if none was defined AddScalarFieldToEmptyNonScalarField(query); AddScalarFieldToEmptyNonScalarField(mutation); AddScalarFieldToEmptyNonScalarField(subscription); // Let's add the __typename fields to all non scalar types addTypenameFields(); } private void AddScalarFieldToEmptyNonScalarField(QueryField field) throws GraphQLRequestPreparationException { // If this field contains no subfield, and is not a scalar, we add all its scalar fields, as requested fields. if (field == null || field.isScalar()) { // No action } else if (field.fields.size() == 0 && field.fragments.size() == 0 && field.inlineFragments.size() == 0) { // This non scalar field has no subfields in the GraphQL request. It also have no fragment // We'll request all it scalar fields. if (field.clazz.isInterface()) { // For interfaces, we look for getters for (Method m : field.clazz.getDeclaredMethods()) { if (m.getName().startsWith("get")) { GraphQLScalar graphQLScalar = m.getAnnotation(GraphQLScalar.class); if (graphQLScalar != null) { // We've found a subfield that is a scalar. Let's add it. field.fields.add(new QueryField(field.clazz, graphQLScalar.fieldName())); } } } } else { // For objects, we look for class's attributes for (Field f : field.clazz.getDeclaredFields()) { GraphQLScalar graphQLScalar = f.getAnnotation(GraphQLScalar.class); if (graphQLScalar != null) { // We've found a subfield that is a scalar. Let's add it. field.fields.add(new QueryField(field.clazz, graphQLScalar.fieldName())); } } } } else { // This non scalar fields contains requested subfield. We recurse into each of its fields. for (QueryField f : field.fields) AddScalarFieldToEmptyNonScalarField(f); } // for } /** * * @param params * @return * @throws GraphQLRequestExecutionException */ public String buildRequest(Map params) throws GraphQLRequestExecutionException { StringBuilder sb = new StringBuilder("{\"query\":\""); // Let's start by the fragments for (Fragment fragment : fragments) { fragment.appendToGraphQLRequests(sb, params); } // Then the other parts of the request if (query != null) { query.appendToGraphQLRequests(sb, params, true); } if (mutation != null) { mutation.appendToGraphQLRequests(sb, params, true); } if (subscription != null) { subscription.appendToGraphQLRequests(sb, params, true); } sb.append("\",\"variables\":null,\"operationName\":null}"); return sb.toString(); } /** * This method returns the package name, where the GraphQL generated classes are. It's used to load the class * definition, and get the GraphQL metadata coming from the GraphQL schema. * * @return */ protected abstract String getGraphQLClassesPackageName(); /** * Retrieved the {@link QueryField} for the query (that is the query type coming from the GraphQL schema) from the * concrete class. * * @return * @throws GraphQLRequestPreparationException */ protected abstract QueryField getQueryContext() throws GraphQLRequestPreparationException; /** * Retrieved the {@link QueryField} for the mutation (that is the mutation type coming from the GraphQL schema) from * the concrete class. * * @return */ protected abstract QueryField getMutationContext() throws GraphQLRequestPreparationException; /** * Retrieved the {@link QueryField} for the subscription (that is the subscription type coming from the GraphQL * schema) from the concrete class. * * @return */ protected abstract QueryField getSubscriptionContext() throws GraphQLRequestPreparationException; public QueryField getQuery() { return query; } public QueryField getMutation() { return mutation; } public QueryField getSubscription() { return subscription; } public List getFragments() { return fragments; } public RequestType getRequestType() { return requestType; } public String getQueryName() { return queryName; } /** * This gets the default configuration, that will apply if no local configuration has been defined for this * instance. * * @return the staticConfiguration */ public static GraphQLConfiguration getStaticConfiguration() { return staticConfiguration; } /** * This sets the default configuration, that will apply if no local configuration has been defined for this * instance. * * @param staticConfiguration * the staticConfiguration to set */ public static void setStaticConfiguration(GraphQLConfiguration staticConfiguration) { AbstractGraphQLRequest.staticConfiguration = staticConfiguration; } /** * This gets the configuration for this instance. This configuration overrides the * {@link #getStaticConfiguration()}, if defined. * * @return the instanceConfiguration */ public GraphQLConfiguration getInstanceConfiguration() { return instanceConfiguration; } /** * This sets the configuration for this instance. This configuration overrides the * {@link #getStaticConfiguration()}, if defined. * * @param instanceConfiguration * the instanceConfiguration to set */ public void setInstanceConfiguration(GraphQLConfiguration instanceConfiguration) { this.instanceConfiguration = instanceConfiguration; } }




© 2015 - 2025 Weber Informatics LLC | Privacy Policy