com.graphql_java_generator.client.QueryExecutorSpringReactiveImpl Maven / Gradle / Ivy
/**
*
*/
package com.graphql_java_generator.client;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.Map;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.security.oauth2.client.web.reactive.function.client.ServerOAuth2AuthorizedClientExchangeFilterFunction;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.socket.client.WebSocketClient;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.graphql_java_generator.annotation.RequestType;
import com.graphql_java_generator.client.request.AbstractGraphQLRequest;
import com.graphql_java_generator.client.response.JsonResponseWrapper;
import com.graphql_java_generator.exception.GraphQLRequestExecutionException;
import com.graphql_java_generator.spring.client.GraphQLAutoConfiguration;
import reactor.core.Disposable;
import reactor.core.publisher.Mono;
import reactor.core.scheduler.Schedulers;
/**
* This is the default implementation for the {@link QueryExecutor} This implementation has been added in version
* 1.12.
* It is loaded by the {@link GraphQLAutoConfiguration} Spring auto-configuration class.
*
* @since 1.12
* @author etienne-sf
*/
public class QueryExecutorSpringReactiveImpl implements QueryExecutor {
/** Logger for this class */
private static Logger logger = LoggerFactory.getLogger(QueryExecutorSpringReactiveImpl.class);
/**
* A graphqlEndpoint Spring bean, of type String, must be provided, with the URL of the GraphQL endpoint, for
* instance https://my.serveur.com/graphql
*/
String graphqlEndpoint;
/**
* If the subscription is on a different endpoint than the main GraphQL endpoint, thant you can define a
* graphqlSubscriptionEndpoint Spring bean, of type String, with this specific URL, for instance
* https://my.serveur.com/graphql/subscription. For instance, Java servers suffer from a limitation which
* prevent to server both GET/POST HTTP verbs and WebSockets on the same URL.
* If no bean graphqlSubscriptionEndpoint Spring bean is defined, then the graphqlEndpoint URL is also
* used for subscriptions (which is the standard case).
*/
String graphqlSubscriptionEndpoint;
/**
* The optional {@link ServerOAuth2AuthorizedClientExchangeFilterFunction} that manages the OAuth Authorization
* header, on client side
*/
ServerOAuth2AuthorizedClientExchangeFilterFunction serverOAuth2AuthorizedClientExchangeFilterFunction;
/**
* The optional {@link OAuthTokenExtractor} that extracts the OAuth Authorization header, once the
* {@link ServerOAuth2AuthorizedClientExchangeFilterFunction} has gotten it
*/
OAuthTokenExtractor oAuthTokenExtractor;
/**
* The Spring reactive {@link WebClient} that will execute the HTTP requests for GraphQL queries and mutations.
*/
WebClient webClient;
/**
* The Spring reactive {@link WebSocketClient} web socket client, that will execute HTTP requests to build the web
* sockets, for GraphQL subscriptions.
* This is mandatory if the application latter calls subscription. It may be null otherwise.
*/
WebSocketClient webSocketClient;
/**
* The {@link ObjectMapper} that will read the json response, and map it to the correct java class, generated from
* the GraphQL type defined in the source GraphQL schema
*/
ObjectMapper objectMapper = new ObjectMapper();
/**
* This constructor may be called by Spring, once it has build a {@link WebClient} bean, or directly, in non Spring
* applications.
*
* @param graphqlEndpoint
* A graphqlEndpoint Spring bean, of type String, must be provided, with the URL of the GraphQL
* endpoint, for instance https://my.serveur.com/graphql
* @param graphqlSubscriptionEndpoint
* If the subscription is on a different endpoint than the main GraphQL endpoint, thant you can define a
* graphqlSubscriptionEndpoint Spring bean, of type String, with this specific URL, for instance
* https://my.serveur.com/graphql/subscription. For instance, Java servers suffer from a
* limitation which prevent to server both GET/POST HTTP verbs and WebSockets on the same URL.
* If no bean graphqlSubscriptionEndpoint Spring bean is defined, then the graphqlEndpoint
* URL is also used for subscriptions (which is the standard case).
* @param webClient
* The Spring reactive {@link WebClient} that will execute the HTTP requests for GraphQL queries and
* mutations.
* @param webSocketClient
* The Spring reactive {@link WebSocketClient} web socket client, that will execute HTTP requests to
* build the web sockets, for GraphQL subscriptions.
* This is mandatory if the application latter calls subscription. It may be null otherwise.
* @param serverOAuth2AuthorizedClientExchangeFilterFunction
* The {@link ServerOAuth2AuthorizedClientExchangeFilterFunction} is responsible for getting OAuth token
* from the OAuth authorization server. It is optional, and may be provided by the App's spring config.
* If it is not provided, then there is no OAuth authentication on client side. If provided, then the
* client uses it to provide the OAuth2 authorization token, when accessing the GraphQL resource server
* for queries/mutations/subscriptions.
* @param oAuthTokenExtractor
* This class is responsible for extracting the OAuth token, once the
* {@link ServerOAuth2AuthorizedClientExchangeFilterFunction} has done its job, and added the OAuth2
* token into the request, in the Authorization header. See the {@link OAuthTokenExtractor} doc for more
* information.
*/
@Autowired
public QueryExecutorSpringReactiveImpl(String graphqlEndpoint, //
@Autowired(required = false) String graphqlSubscriptionEndpoint, //
WebClient webClient, //
@Autowired(required = false) WebSocketClient webSocketClient,
@Autowired(required = false) ServerOAuth2AuthorizedClientExchangeFilterFunction serverOAuth2AuthorizedClientExchangeFilterFunction,
@Autowired(required = false) OAuthTokenExtractor oAuthTokenExtractor) {
this.graphqlEndpoint = graphqlEndpoint;
this.graphqlSubscriptionEndpoint = graphqlSubscriptionEndpoint;
this.webClient = webClient;
this.webSocketClient = webSocketClient;
this.serverOAuth2AuthorizedClientExchangeFilterFunction = serverOAuth2AuthorizedClientExchangeFilterFunction;
this.oAuthTokenExtractor = oAuthTokenExtractor;
}
@Override
public R execute(AbstractGraphQLRequest graphQLRequest, Map parameters,
Class dataResponseType) throws GraphQLRequestExecutionException {
if (graphQLRequest.getRequestType().equals(RequestType.subscription))
throw new GraphQLRequestExecutionException("This method may not be called for subscriptions");
String jsonRequest = graphQLRequest.buildRequest(parameters);
try {
logger.trace(GRAPHQL_MARKER, "Executing GraphQL request: {}", jsonRequest);
JsonResponseWrapper responseJson = webClient//
.post()//
.contentType(MediaType.APPLICATION_JSON)//
.body(Mono.just(jsonRequest), String.class)//
.accept(MediaType.APPLICATION_JSON)//
.retrieve()//
.bodyToMono(JsonResponseWrapper.class)//
.block();
return QueryExecutorImpl.parseDataFromGraphQLServerResponse(objectMapper, responseJson, dataResponseType);
} catch (IOException e) {
throw new GraphQLRequestExecutionException(
"Error when executing query <" + jsonRequest + ">: " + e.getMessage(), e);
}
}
@Override
public SubscriptionClient execute(AbstractGraphQLRequest graphQLRequest, Map parameters,
SubscriptionCallback subscriptionCallback, String subscriptionName, Class subscriptionType,
Class messageType) throws GraphQLRequestExecutionException {
// This method accepts only subscription at a time (no query and no mutation)
if (!graphQLRequest.getRequestType().equals(RequestType.subscription))
throw new GraphQLRequestExecutionException("This method may be called only for subscriptions");
// Subscription may be subscribed only once at a time, as this method allows only one subscriptionCallback
if (graphQLRequest.getSubscription().getFields().size() != 1) {
throw new GraphQLRequestExecutionException(
"This method may be called only for one subscription at a time, but there was "
+ graphQLRequest.getSubscription().getFields().size()
+ " subscriptions in this GraphQLRequest");
}
// The subscription name must be the good one
if (!graphQLRequest.getSubscription().getFields().get(0).getName().equals(subscriptionName)) {
throw new GraphQLRequestExecutionException("The subscription provided in the GraphQLRequest is "
+ graphQLRequest.getSubscription().getFields().get(0).getName() + " but it should be "
+ subscriptionName);
}
// Is there an OAuth authentication to handle?
HttpHeaders headers = new HttpHeaders();
if (serverOAuth2AuthorizedClientExchangeFilterFunction != null && oAuthTokenExtractor != null) {
String authorizationHeaderValue = oAuthTokenExtractor.getAuthorizationHeaderValue();
logger.debug("Got this OAuth token (authorization header value): {}", authorizationHeaderValue);
headers.add(OAuthTokenExtractor.AUTHORIZATION_HEADER_NAME, authorizationHeaderValue);
} else {
logger.debug(
"No serverOAuth2AuthorizedClientExchangeFilterFunction or no oAuthTokenExtractor where provided. No OAuth token is provided.");
}
String request = graphQLRequest.buildRequest(parameters);
logger.debug(GRAPHQL_MARKER, "Executing GraphQL subscription '{}' with request {}", subscriptionName, request);
// Let's create and start the Web Socket
GraphQLReactiveWebSocketHandler webSocketHandler = new GraphQLReactiveWebSocketHandler<>(request,
subscriptionName, subscriptionCallback, subscriptionType, messageType);
logger.trace(GRAPHQL_MARKER, "Before execution of GraphQL subscription '{}' with request {}", subscriptionName,
request);
Disposable disposable = webSocketClient.execute(getWebSocketURI(), headers, webSocketHandler)
.subscribeOn(Schedulers.single())// Let's have a dedicated thread
.subscribe();
logger.trace(GRAPHQL_MARKER, "After execution of GraphQL subscription '{}' with request {}", subscriptionName,
request);
return new SubscriptionClientReactiveImpl(disposable, webSocketHandler.getSession());
}
/**
* Retrieves the URI for the Web Socket, based on the GraphQL endpoint that has been given to the Constructor
*
* @return
* @throws GraphQLRequestExecutionException
*/
public URI getWebSocketURI() throws GraphQLRequestExecutionException {
String endpoint = (graphqlSubscriptionEndpoint != null) ? graphqlSubscriptionEndpoint : graphqlEndpoint;
if (endpoint.startsWith("http:") || endpoint.startsWith("https:")) {
// We'll use the ws or the wss protocol. Let's just replace http by ws for that
try {
return new URI("ws" + endpoint.substring(4));
} catch (URISyntaxException e) {
throw new GraphQLRequestExecutionException(
"Error when trying to determine the Web Socket endpoint for GraphQL endpoint " + endpoint, e);
}
}
throw new GraphQLRequestExecutionException(
"non managed protocol for endpoint " + endpoint + ". This method manages only http and https");
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy