Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add graphql api creation by SDL url and introspection #12737

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -291,7 +291,12 @@ public enum ExceptionCodes implements ErrorHandler {
"GraphQL Schema cannot be empty or null"),
UNSUPPORTED_GRAPHQL_FILE_EXTENSION(900802, "Unsupported GraphQL Schema File Extension", 400,
"Unsupported extension. Only supported extensions are .graphql, .txt and .sdl"),

INVALID_GRAPHQL_FILE(900803, "GraphQL filename cannot be null or invalid", 400,
"GraphQL filename cannot be null or invalid"),
GENERATE_GRAPHQL_SCHEMA_FROM_INTROSPECTION_ERROR(900804, "Error while generating GraphQL schema from introspection",
500, "Error while generating GraphQL schema from introspection"),
RETRIEVE_GRAPHQL_SCHEMA_FROM_URL_ERROR(900805, "Error while retrieving GraphQL schema from URL", 500,
"Error while retrieving GraphQL schema from URL"),

// Oauth related codes
AUTH_GENERAL_ERROR(900900, "Authorization Error", 403, " Error in authorization"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -253,6 +253,7 @@
import java.net.URLDecoder;
import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.rmi.RemoteException;
import java.security.*;
import java.security.cert.Certificate;
Expand Down Expand Up @@ -1404,6 +1405,18 @@ public static String getOpenAPIDefinitionFilePath(String apiName, String apiVers
apiName + RegistryConstants.PATH_SEPARATOR + apiVersion + RegistryConstants.PATH_SEPARATOR;
}

public static String getIntrospectionQuery() throws APIManagementException {
String introspectionQueryFilePath = "graphql/introspection_query.txt";
try (InputStream fileStream = APIUtil.class.getClassLoader().getResourceAsStream(introspectionQueryFilePath)) {
if (fileStream == null) {
throw new APIManagementException("File not found: " + introspectionQueryFilePath);
}
return IOUtils.toString(fileStream, StandardCharsets.UTF_8);
} catch (IOException e) {
throw new APIManagementException("Error reading introspection query file", e);
}
}

public static String getRevisionPath(String apiUUID, int revisionId) {
return APIConstants.API_REVISION_LOCATION + RegistryConstants.PATH_SEPARATOR +
apiUUID +
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
query IntrospectionQuery {
__schema {

queryType { name }
mutationType { name }
subscriptionType { name }
types {
...FullType
}
directives {
name
description

locations
args {
...InputValue
}
}
}
}

fragment FullType on __Type {
kind
name
description

fields(includeDeprecated: true) {
name
description
args {
...InputValue
}
type {
...TypeRef
}
isDeprecated
deprecationReason
}
inputFields {
...InputValue
}
interfaces {
...TypeRef
}
enumValues(includeDeprecated: true) {
name
description
isDeprecated
deprecationReason
}
possibleTypes {
...TypeRef
}
}

fragment InputValue on __InputValue {
name
description
type { ...TypeRef }
defaultValue


}

fragment TypeRef on __Type {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
ofType {
kind
name
}
}
}
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1618,7 +1618,7 @@ public static GraphQLValidationResponseDTO retrieveValidatedGraphqlSchemaFromArc
try {
String schemaDefinition = loadGraphqlSDLFile(pathToArchive);
GraphQLValidationResponseDTO graphQLValidationResponseDTO = PublisherCommonUtils
.validateGraphQLSchema(file.getName(), schemaDefinition);
.validateGraphQLSchema(file.getName(), schemaDefinition, null, false);
if (!graphQLValidationResponseDTO.isIsValid()) {
String errorMessage = "Error occurred while importing the API. Invalid GraphQL schema definition "
+ "found. " + graphQLValidationResponseDTO.getErrorMessage();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,10 @@
import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
import com.google.gson.reflect.TypeToken;
import graphql.introspection.IntrospectionResultToSchema;
import graphql.language.AstPrinter;
import graphql.language.Document;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.SchemaParser;
import graphql.schema.idl.TypeDefinitionRegistry;
Expand All @@ -38,6 +42,13 @@
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.http.HttpResponse;
import org.apache.http.HttpStatus;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.entity.StringEntity;
import org.apache.http.util.EntityUtils;
import org.json.simple.JSONArray;
import org.json.simple.JSONObject;
import org.json.simple.parser.JSONParser;
Expand Down Expand Up @@ -105,7 +116,10 @@
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
Expand All @@ -126,6 +140,7 @@ public class PublisherCommonUtils {

private static final Log log = LogFactory.getLog(PublisherCommonUtils.class);
public static final String SESSION_TIMEOUT_CONFIG_KEY = "sessionTimeOut";
private static String graphQLIntrospectionQuery = null;

/**
* Update API and API definition.
Expand Down Expand Up @@ -2000,47 +2015,58 @@ public static API addGraphQLSchema(API originalAPI, String schemaDefinition, API
* Validate GraphQL Schema.
*
* @param filename file name of the schema
* @param schema GraphQL schema
* @param schema GraphQL schema
* @param url URL of the schema
* @param useIntrospection use introspection to obtain schema
*/
public static GraphQLValidationResponseDTO validateGraphQLSchema(String filename, String schema)
throws APIManagementException {
public static GraphQLValidationResponseDTO validateGraphQLSchema(String filename, String schema, String url,
Boolean useIntrospection) throws APIManagementException {

String errorMessage;
GraphQLValidationResponseDTO validationResponse = new GraphQLValidationResponseDTO();
boolean isValid = false;
try {
if (filename.endsWith(".graphql") || filename.endsWith(".txt") || filename.endsWith(".sdl")) {
if (schema.isEmpty()) {
throw new APIManagementException("GraphQL Schema cannot be empty or null to validate it",
ExceptionCodes.GRAPHQL_SCHEMA_CANNOT_BE_NULL);
}
SchemaParser schemaParser = new SchemaParser();
TypeDefinitionRegistry typeRegistry = schemaParser.parse(schema);
GraphQLSchema graphQLSchema = UnExecutableSchemaGenerator.makeUnExecutableSchema(typeRegistry);
SchemaValidator schemaValidation = new SchemaValidator();
Set<SchemaValidationError> validationErrors = schemaValidation.validateSchema(graphQLSchema);

if (validationErrors.toArray().length > 0) {
errorMessage = "InValid Schema";
validationResponse.isValid(Boolean.FALSE);
validationResponse.errorMessage(errorMessage);
if (url != null && !url.isEmpty()) {
if (useIntrospection) {
schema = generateGraphQLSchemaFromIntrospection(url);
} else {
validationResponse.setIsValid(Boolean.TRUE);
GraphQLValidationResponseGraphQLInfoDTO graphQLInfo = new GraphQLValidationResponseGraphQLInfoDTO();
GraphQLSchemaDefinition graphql = new GraphQLSchemaDefinition();
List<URITemplate> operationList = graphql.extractGraphQLOperationList(typeRegistry, null);
List<APIOperationsDTO> operationArray = APIMappingUtil
.fromURITemplateListToOprationList(operationList);
graphQLInfo.setOperations(operationArray);
GraphQLSchemaDTO schemaObj = new GraphQLSchemaDTO();
schemaObj.setSchemaDefinition(schema);
graphQLInfo.setGraphQLSchema(schemaObj);
validationResponse.setGraphQLInfo(graphQLInfo);
schema = retrieveGraphQLSchemaFromURL(url);
}
} else {
} else if (filename == null) {
throw new APIManagementException("GraphQL filename cannot be null",
ExceptionCodes.INVALID_GRAPHQL_FILE);
} else if (!filename.endsWith(".graphql") && !filename.endsWith(".txt") && !filename.endsWith(".sdl")) {
throw new APIManagementException("Unsupported extension type of file: " + filename,
ExceptionCodes.UNSUPPORTED_GRAPHQL_FILE_EXTENSION);
}

if (schema == null || schema.isEmpty()) {
throw new APIManagementException("GraphQL Schema cannot be empty or null to validate it",
ExceptionCodes.GRAPHQL_SCHEMA_CANNOT_BE_NULL);
}

SchemaParser schemaParser = new SchemaParser();
TypeDefinitionRegistry typeRegistry = schemaParser.parse(schema);
GraphQLSchema graphQLSchema = UnExecutableSchemaGenerator.makeUnExecutableSchema(typeRegistry);
SchemaValidator schemaValidation = new SchemaValidator();
Set<SchemaValidationError> validationErrors = schemaValidation.validateSchema(graphQLSchema);

if (validationErrors.toArray().length > 0) {
errorMessage = "InValid Schema";
validationResponse.isValid(Boolean.FALSE);
validationResponse.errorMessage(errorMessage);
} else {
validationResponse.setIsValid(Boolean.TRUE);
GraphQLValidationResponseGraphQLInfoDTO graphQLInfo = new GraphQLValidationResponseGraphQLInfoDTO();
GraphQLSchemaDefinition graphql = new GraphQLSchemaDefinition();
List<URITemplate> operationList = graphql.extractGraphQLOperationList(typeRegistry, null);
List<APIOperationsDTO> operationArray = APIMappingUtil.fromURITemplateListToOprationList(operationList);
graphQLInfo.setOperations(operationArray);
GraphQLSchemaDTO schemaObj = new GraphQLSchemaDTO();
schemaObj.setSchemaDefinition(schema);
graphQLInfo.setGraphQLSchema(schemaObj);
validationResponse.setGraphQLInfo(graphQLInfo);
}
isValid = validationResponse.isIsValid();
errorMessage = validationResponse.getErrorMessage();
} catch (SchemaProblem e) {
Expand All @@ -2054,6 +2080,80 @@ public static GraphQLValidationResponseDTO validateGraphQLSchema(String filename
return validationResponse;
}

/**
* Generate the GraphQL schema by performing an introspection query on the provided endpoint.
*
* @param url The URL of the GraphQL endpoint to perform the introspection query on.
* @return The GraphQL schema as a string.
* @throws APIManagementException If an error occurs during the schema generation process
*/
public static String generateGraphQLSchemaFromIntrospection(String url) throws APIManagementException {
String schema = null;
try {
URL urlObj = new URL(url);
HttpClient httpClient = APIUtil.getHttpClient(urlObj.getPort(), urlObj.getProtocol());
Gson gson = new Gson();

if (graphQLIntrospectionQuery == null || graphQLIntrospectionQuery.isEmpty()) {
graphQLIntrospectionQuery = APIUtil.getIntrospectionQuery();
}
String requestBody = gson.toJson(
JsonParser.parseString("{\"query\": \"" + graphQLIntrospectionQuery + "\"}"));

HttpPost httpPost = new HttpPost(url);
httpPost.setHeader("Content-Type", "application/json");
httpPost.setEntity(new StringEntity(requestBody));
HttpResponse response = httpClient.execute(httpPost);

if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
String schemaResponse = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
Type type = new TypeToken<Map<String, Object>>() {
}.getType();
Map<String, Object> schemaMap = gson.fromJson(schemaResponse, type);
Document schemaDocument = new IntrospectionResultToSchema().createSchemaDefinition(
(Map<String, Object>) schemaMap.get("data"));
schema = AstPrinter.printAst(schemaDocument);
} else {
log.error("Unable to generate GraphQL schema from introspection. URL."
+ " Returned response code: " + response.getStatusLine().getStatusCode()
+ " from endpoint: " + url);
throw new APIManagementException("Error occurred while generating GraphQL schema from introspection",
ExceptionCodes.GENERATE_GRAPHQL_SCHEMA_FROM_INTROSPECTION_ERROR);
}
} catch (Exception e) {
log.error("Exception occurred while generating GraphQL schema from introspection. URL: " + url
+ ". Exception: " + e.getMessage(), e);
throw new APIManagementException("Error occurred while generating GraphQL schema from introspection",
ExceptionCodes.GENERATE_GRAPHQL_SCHEMA_FROM_INTROSPECTION_ERROR);
}
return schema;
}

/**
* Retrieve the GraphQL schema from the specified URL.
*
* @param url The URL of the GraphQL schema to retrieve.
* @return The GraphQL schema as a string.
* @throws APIManagementException If an error occurs while retrieving the schema
*/
public static String retrieveGraphQLSchemaFromURL(String url) throws APIManagementException {
String schema = null;
try {
URL urlObj = new URL(url);
HttpClient httpClient = APIUtil.getHttpClient(urlObj.getPort(), urlObj.getProtocol());
HttpGet httpGet = new HttpGet(url);
HttpResponse response = httpClient.execute(httpGet);

if (HttpStatus.SC_OK == response.getStatusLine().getStatusCode()) {
schema = EntityUtils.toString(response.getEntity(), StandardCharsets.UTF_8);
}
} catch (IOException e) {
throw new APIManagementException("Error occurred while retrieving GraphQL schema from schema URL",
ExceptionCodes.RETRIEVE_GRAPHQL_SCHEMA_FROM_URL_ERROR);
}
return schema;
}

/**
* Update thumbnail of an API/API Product
*
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1518,8 +1518,8 @@ public Response importAsyncAPISpecification( @Multipart(value = "file", required
@ApiResponse(code = 201, message = "Created. Successful response with the newly created object as entity in the body. Location header contains URL of newly created entity. ", response = APIDTO.class),
@ApiResponse(code = 400, message = "Bad Request. Invalid request or validation error.", response = ErrorDTO.class),
@ApiResponse(code = 415, message = "Unsupported Media Type. The entity of the request was not in a supported format.", response = ErrorDTO.class) })
public Response importGraphQLSchema( @ApiParam(value = "Validator for conditional requests; based on ETag. " )@HeaderParam("If-Match") String ifMatch, @Multipart(value = "type", required = false) String type, @Multipart(value = "file", required = false) InputStream fileInputStream, @Multipart(value = "file" , required = false) Attachment fileDetail, @Multipart(value = "additionalProperties", required = false) String additionalProperties) throws APIManagementException{
return delegate.importGraphQLSchema(ifMatch, type, fileInputStream, fileDetail, additionalProperties, securityContext);
public Response importGraphQLSchema( @ApiParam(value = "Validator for conditional requests; based on ETag. " )@HeaderParam("If-Match") String ifMatch, @Multipart(value = "type", required = false) String type, @Multipart(value = "file", required = false) InputStream fileInputStream, @Multipart(value = "file" , required = false) Attachment fileDetail, @Multipart(value = "url", required = false) String url, @Multipart(value = "schema", required = false) String schema, @Multipart(value = "additionalProperties", required = false) String additionalProperties) throws APIManagementException{
return delegate.importGraphQLSchema(ifMatch, type, fileInputStream, fileDetail, url, schema, additionalProperties, securityContext);
}

@POST
Expand Down Expand Up @@ -2018,8 +2018,8 @@ public Response validateEndpoint( @NotNull @ApiParam(value = "API endpoint url",
@ApiResponse(code = 200, message = "OK. API definition validation information is returned ", response = GraphQLValidationResponseDTO.class),
@ApiResponse(code = 400, message = "Bad Request. Invalid request or validation error.", response = ErrorDTO.class),
@ApiResponse(code = 404, message = "Not Found. The specified resource does not exist.", response = ErrorDTO.class) })
public Response validateGraphQLSchema( @Multipart(value = "file") InputStream fileInputStream, @Multipart(value = "file" ) Attachment fileDetail) throws APIManagementException{
return delegate.validateGraphQLSchema(fileInputStream, fileDetail, securityContext);
public Response validateGraphQLSchema( @ApiParam(value = "Specify whether to use Introspection to obtain the GraphQL Schema ", defaultValue="false") @DefaultValue("false") @QueryParam("useIntrospection") Boolean useIntrospection, @Multipart(value = "file", required = false) InputStream fileInputStream, @Multipart(value = "file" , required = false) Attachment fileDetail, @Multipart(value = "url", required = false) String url) throws APIManagementException{
return delegate.validateGraphQLSchema(useIntrospection, fileInputStream, fileDetail, url, securityContext);
}

@POST
Expand Down
Loading
Loading