Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance. Project price only 1 $
You can buy this project and download/modify it how often you want.
/**
*
*/
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.GraphQLRequestObject;
import com.graphql_java_generator.client.SubscriptionCallback;
import com.graphql_java_generator.client.SubscriptionClient;
import com.graphql_java_generator.client.request.InputParameter.InputParameterType;
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 requestName;
/**
* 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;
/** Indicates what is being read, when reading the GraphQL variable list */
private enum Step {
NAME, TYPE
};
/**
* 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 fieldName
* The name of the query, mutation or subscription, for instance "createHuman", in the GraphQL request
* "mutation {createHuman (...) { ...}}".
* @param inputParams
* The list of input parameters for this query/mutation/subscription
* @throws GraphQLRequestPreparationException
*/
public AbstractGraphQLRequest(String graphQLRequest, RequestType requestType, String fieldName,
InputParameter... inputParams) throws GraphQLRequestPreparationException {
if (requestType == null) {
throw new NullPointerException("requestType is mandatory, but a null value has been provided");
}
if (fieldName == null) {
throw new NullPointerException("fieldName is mandatory, but a null value has been provided");
}
this.requestType = requestType;
this.requestName = null;
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, fieldName);
query.fields.add(field);
break;
case mutation:
mutation = getMutationContext();// Get the mutation field from the concrete class
field = new QueryField(mutation.clazz, fieldName);
mutation.fields.add(field);
break;
case subscription:
subscription = getSubscriptionContext();// Get the subscription field from the concrete class
field = new QueryField(subscription.clazz, fieldName);
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 {
String localQueryName = 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.
boolean requestTypeHasBeenRead = false; // Used for a basic check in unknown tokens, to see if this token can be
// the request name
List inputParameters = new ArrayList<>(); // The list of GraphQL variables for this 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);
requestTypeHasBeenRead = true;// We'll know accept an unknown token as the request name
break;
case "(":
try {
readRequestParameters(qt, inputParameters);
} catch (Exception e) {
throw new GraphQLRequestPreparationException(
e.getMessage() + " (while reading the request parameters)", e);
}
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.inputParameters = inputParameters;
query.readTokenizerForResponseDefinition(qt);
break;
case mutation:
mutation = getMutationContext();// Get the mutation field from the concrete class
mutation.inputParameters = inputParameters;
mutation.readTokenizerForResponseDefinition(qt);
break;
case subscription:
subscription = getSubscriptionContext();// Get the subscription field from the concrete class
subscription.inputParameters = inputParameters;
subscription.readTokenizerForResponseDefinition(qt);
break;
default:
throw new GraphQLRequestPreparationException("Non managed request type '" + requestType
+ " while reading the GraphQL request: " + graphQLRequest);
}
break;
default:
if (requestTypeHasBeenRead) {
localQueryName = token;
} else {
throw new GraphQLRequestPreparationException(
"Unknown token '" + token + " while reading the GraphQL request: " + graphQLRequest);
}
}
}
if (query == null && mutation == null && subscription == null) {
throw new GraphQLRequestPreparationException("No response definition found");
}
// Let's finish the job
// As the query name can't be changed, we have to set in a temporary variable, to allow changing its value when
// we found one
this.requestName = localQueryName;
finishRequestPreparation();
}
/**
* Reads the parameters of the request. These parameters are actually GraphQL variables, according to the GraphQL
* spec.
*
* @param qt
* The {@link QueryTokenizer} current token is the '(' that starts the parameter list. When the method
* returns, the {@link QueryTokenizer} current token is the ')'
* @param inputParameters
* The empty list if {@link InputParameter}s.
* @throws GraphQLRequestPreparationException
*/
private void readRequestParameters(QueryTokenizer qt, List inputParameters)
throws GraphQLRequestPreparationException {
String token;
// We're reading the request parameters. It should be something like "($param1: Type1, $param2: Type2!)"
Step step = Step.NAME;
String name = null;
while (true) {
token = qt.nextToken();
// Are we done?
if (token.equals(")")) {
if (step.equals(Step.TYPE)) {
throw new GraphQLRequestPreparationException(
"Found a ')', while expecting a value for the '" + name + "' query parameter");
} else {
// Ok we're done
break;
}
} else if (token.equals(",")) {
// We should be waiting for the name of the GraphQL variable
if (!step.equals(Step.NAME)) {
throw new GraphQLRequestPreparationException("unexpected ','");
}
// Let's go to the next token, that should be the GraphQL type
token = qt.nextToken();
} else if (token.equals(":")) {
// We should be waiting for the type of the GraphQL variable
if (!step.equals(Step.TYPE)) {
throw new GraphQLRequestPreparationException("unexpected ':'");
}
// Let's go to the next token, that should be the GraphQL type
token = qt.nextToken();
}
switch (step) {
case NAME:
if (!token.startsWith("$")) {
throw new GraphQLRequestPreparationException(
"The GraphQL variable names should start by a '$', but this one doesn't: '" + token + "'");
}
// We store the name, without the leading '$'
name = token.substring(1);
// The next token should be the value
step = Step.TYPE;
break;
case TYPE:
// The current token is the GraphQL variable type, for instance "[[Human!]]!". Let's parse it.
int currentDepth = 0;
String graphQLTypeName = null;
boolean mandatory = false;
int listDepth = 0;
boolean itemMandatory = false;
while (true) {
switch (token) {
case "[":
listDepth += 1;
currentDepth += 1;
break;
case "]":
currentDepth -= 1;
break;
case "!":
// If we're here, it means the depth is at least one.
itemMandatory = true;
break;
case ",":
case ")":// Too bad, the query is wrongly written
throw new GraphQLRequestPreparationException(
"Syntax error in the query, while reading the type of the '" + name
+ "' parameter of the request");
default:
// We have the GraphQL type name
graphQLTypeName = token;
}
// Are we done?
if (currentDepth == 0) {
break;
}
token = qt.nextToken();
}
;
// We get here if the item is not a list, or after reading the last ']'.
// Let's check if there is a trailing '!'
if (qt.checkNextToken("!")) {
mandatory = true;
// Then we pass this token
token = qt.nextToken();
} else {
mandatory = false;
}
inputParameters.add(InputParameter.newGraphQLVariableParameter(name, graphQLTypeName, mandatory,
listDepth, itemMandatory));
// The next token should be either the end of parameters (with a ')') or a name
step = Step.NAME;
break;
}// switch
} // while
}
/**
* 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,
Class subscriptionType, Class messageType) throws GraphQLRequestExecutionException {
if (instanceConfiguration != null) {
return instanceConfiguration.getQueryExecutor().execute(this, params, subscriptionCallback,
subscriptionType, messageType);
} else if (staticConfiguration != null) {
return staticConfiguration.getQueryExecutor().execute(this, params, subscriptionCallback, 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
QueryField request;
if (query != null) {
request = query;
} else if (mutation != null) {
request = mutation;
} else if (subscription != null) {
request = subscription;
} else {
throw new GraphQLRequestExecutionException("[Internal error] no request has been initialized");
}
// The name of the query/mutation/subscription follows special rules (including the request name and GraphQL
// variables). So we need to add these things here, and not from the QueryField class.
sb.append(request.name);
if (requestName != null) {
sb.append(" ").append(requestName);
}
// Let's add all GraphQL variables here
StringBuilder sbGraphQLVariables = new StringBuilder();
StringBuilder sbGraphQLValues = new StringBuilder();
String separator = "";
for (InputParameter param : request.inputParameters) {
if (param.getType() == InputParameterType.GRAPHQL_VARIABLE) {
//////////////////////////////////////////////////////////////////////
// Let's complete the variable list,
sbGraphQLVariables.append(separator)//
.append("$")//
.append(param.getBindParameterName())//
.append(":");
// The String.repeat(int) method needs Java 11 minimum
for (int i = 0; i < param.getListDepth(); i += 1) {
sbGraphQLVariables.append("[");
} // for
sbGraphQLVariables.append(param.getGraphQLTypeName())//
.append(param.isItemMandatory() ? "!" : "");
// The String.repeat(int) method needs Java 11 minimum
for (int i = 0; i < param.getListDepth(); i += 1) {
sbGraphQLVariables.append("]");
} // for
sbGraphQLVariables.append(param.isMandatory() ? "!" : "");
//////////////////////////////////////////////////////////////////////
// And the variable value list (for the json variables field)
sbGraphQLValues.append(separator)//
.append("\"")//
.append(param.getBindParameterName())//
.append("\":")//
.append(param.getValueForGraphqlQuery(true, params));
separator = ",";
}
}
// Are there some GraphQL variables?
String graphQLVariables = sbGraphQLVariables.toString();
if (graphQLVariables.length() > 0) {
sb.append("(").append(graphQLVariables).append(")");
}
// Let's add the whole request
request.appendToGraphQLRequests(sb, params, false);
// Let's finish the json string
sb.append("\",\"variables\":")//
.append((graphQLVariables.length() > 0) ? "{" + sbGraphQLValues + "}" : "null")//
.append(",\"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 getRequestName() {
return requestName;
}
/**
* 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;
}
}