Skip to content

Commit

Permalink
Merge pull request #63 from CBIIT/Develop
Browse files Browse the repository at this point in the history
Develop
  • Loading branch information
AustinSMueller authored Aug 18, 2022
2 parents 5493220 + 025dfd7 commit 278340f
Show file tree
Hide file tree
Showing 26 changed files with 910 additions and 783 deletions.
7 changes: 7 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -209,6 +209,13 @@
<scope>compile</scope>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.22</version>
<scope>provided</scope>
</dependency>


</dependencies>

Expand Down
13 changes: 4 additions & 9 deletions src/main/java/gov/nih/nci/bento/BentoApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,17 @@

@SpringBootApplication
public class BentoApplication extends SpringBootServletInitializer {


private static final Logger logger = LogManager.getLogger(BentoApplication.class);


private static final Logger logger = LogManager.getLogger(BentoApplication.class);

public static void main(String[] args) {
SpringApplication.run(BentoApplication.class, args);
}

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
logger.info("Server started");

return application.sources(BentoApplication.class);

}

return application.sources(BentoApplication.class);

}
}
6 changes: 2 additions & 4 deletions src/main/java/gov/nih/nci/bento/MvcWebConfig.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,6 @@
package gov.nih.nci.bento;

import java.util.concurrent.TimeUnit;

import gov.nih.nci.bento.interceptor.AuthenticationInterceptor;
import gov.nih.nci.bento.model.ConfigurationDAO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
Expand All @@ -17,6 +13,8 @@
import org.springframework.web.servlet.view.InternalResourceViewResolver;
import org.springframework.web.servlet.view.JstlView;

import java.util.concurrent.TimeUnit;


@Configuration
@EnableWebMvc
Expand Down
2 changes: 0 additions & 2 deletions src/main/java/gov/nih/nci/bento/ServletInitializer.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,8 @@
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;

public class ServletInitializer extends SpringBootServletInitializer {

@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(BentoApplication.class);
}

}
217 changes: 57 additions & 160 deletions src/main/java/gov/nih/nci/bento/controller/GraphQLController.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,30 +5,18 @@
import com.google.gson.JsonElement;
import com.google.gson.JsonObject;
import gov.nih.nci.bento.error.ApiError;
import gov.nih.nci.bento.error.BentoGraphQLException;
import gov.nih.nci.bento.error.BentoGraphqlError;
import gov.nih.nci.bento.graphql.BentoGraphQL;
import gov.nih.nci.bento.model.ConfigurationDAO;
import gov.nih.nci.bento.model.DataFetcher;
import gov.nih.nci.bento.model.Neo4jDataFetcher;
import graphql.ExecutionInput;
import graphql.ExecutionResult;
import graphql.GraphQL;
import graphql.language.Document;
import graphql.language.OperationDefinition;
import graphql.parser.Parser;
import graphql.schema.GraphQLNamedType;
import graphql.schema.GraphQLObjectType;
import graphql.schema.GraphQLSchema;
import graphql.schema.idl.SchemaGenerator;
import graphql.schema.idl.SchemaParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import org.neo4j.graphql.SchemaBuilder;
import org.neo4j.graphql.SchemaConfig;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.DependsOn;
import org.springframework.core.io.DefaultResourceLoader;
import org.springframework.core.io.Resource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
Expand All @@ -39,34 +27,29 @@
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RestController
@DependsOn({"neo4jDataFetcher"})
public class GraphQLController {

private static final Logger logger = LogManager.getLogger(GraphQLController.class);

@Autowired
private ConfigurationDAO config;
@Autowired
private Neo4jDataFetcher dataFetcherInterceptor;
@Autowired
private DataFetcher esFilterDataFetcher;
private final ConfigurationDAO config;
private final Gson gson;
private final BentoGraphQL bentoGraphQL;


private Gson gson = new GsonBuilder().serializeNulls().create();
private GraphQL graphql;
public GraphQLController(ConfigurationDAO config, BentoGraphQL bentoGraphQL){
this.config = config;
this.bentoGraphQL = bentoGraphQL;
this.gson = new GsonBuilder().serializeNulls().create();
}

@CrossOrigin
@RequestMapping(value = "/version", method = {RequestMethod.GET}, produces = MediaType.APPLICATION_JSON_VALUE + "; charset=utf-8")
@RequestMapping(value = "/version", method = {RequestMethod.GET},
produces = MediaType.APPLICATION_JSON_VALUE + "; charset=utf-8")
public ResponseEntity<String> getVersion(HttpEntity<String> httpEntity, HttpServletResponse response){
logger.info("Hit end point:/version");
String versionString = config.getBentoApiVersion();
Expand All @@ -75,24 +58,38 @@ public ResponseEntity<String> getVersion(HttpEntity<String> httpEntity, HttpServ
}

@CrossOrigin
@RequestMapping
(value = "/v1/graphql/", method = {
RequestMethod.GET, RequestMethod.HEAD, RequestMethod.PUT, RequestMethod.DELETE,
RequestMethod.TRACE, RequestMethod.OPTIONS, RequestMethod.PATCH}
, produces = MediaType.APPLICATION_JSON_VALUE + "; charset=utf-8")
public ResponseEntity<String> getGraphQLResponseByGET(HttpEntity<String> httpEntity, HttpServletResponse response){
@RequestMapping(value = {"/v1/graphql/", "/v1/public-graphql/"}, method = {RequestMethod.GET, RequestMethod.HEAD,
RequestMethod.PUT, RequestMethod.DELETE, RequestMethod.TRACE, RequestMethod.OPTIONS, RequestMethod.PATCH},
produces = MediaType.APPLICATION_JSON_VALUE + "; charset=utf-8")
public ResponseEntity<String> getPrivateGraphQLResponseByGET(HttpEntity<String> httpEntity,
HttpServletResponse response) {
HttpStatus status = HttpStatus.METHOD_NOT_ALLOWED;
String error = ApiError.jsonApiError(new ApiError(status, "API will only accept POST requests"));
return logAndReturnError(status, error);
}

@CrossOrigin
@RequestMapping(value = "/v1/graphql/", method = RequestMethod.POST, produces = MediaType.APPLICATION_JSON_VALUE + "; charset=utf-8")
@RequestMapping(value = "/v1/graphql/", method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE + "; charset=utf-8")
@ResponseBody
public ResponseEntity<String> getGraphQLResponse(HttpEntity<String> httpEntity, HttpServletResponse response){
public ResponseEntity<String> getPrivateGraphQLResponse(HttpEntity<String> httpEntity,
HttpServletResponse response){
logger.info("hit end point:/v1/graphql/");
return getGraphQLResponse(httpEntity, response, bentoGraphQL.getPrivateGraphQL());
}

logger.info("hit end point:/v1/graphql/");
@CrossOrigin
@RequestMapping(value = "/v1/public-graphql/", method = RequestMethod.POST,
produces = MediaType.APPLICATION_JSON_VALUE + "; charset=utf-8")
@ResponseBody
public ResponseEntity<String> getPublicGraphQLResponse(HttpEntity<String> httpEntity, HttpServletResponse response){
logger.info("hit end point:/v1/public-graphql/");
return getGraphQLResponse(httpEntity, response, bentoGraphQL.getPublicGraphQL());
}

@ResponseBody
private ResponseEntity<String> getGraphQLResponse(HttpEntity<String> httpEntity, HttpServletResponse response,
GraphQL graphQL) {
// Get graphql query from request
String reqBody = httpEntity.getBody().toString();
Gson gson = new Gson();
Expand All @@ -110,154 +107,54 @@ public ResponseEntity<String> getGraphQLResponse(HttpEntity<String> httpEntity,
operation = def.getOperation().toString().toLowerCase();
}
catch(Exception e){
HttpStatus status = HttpStatus.BAD_REQUEST;
String error = ApiError.jsonApiError(status, "Invalid query in request", e.getMessage());
return logAndReturnError(status, error);
return logAndReturnError(HttpStatus.BAD_REQUEST, e.getMessage());
}

if ((operation.equals("query") && config.isAllowGraphQLQuery())
|| (operation.equals("mutation") && config.isAllowGraphQLMutation())) {
return ResponseEntity.ok(query(query, variables));
return ResponseEntity.ok(query(query, variables, graphQL));
}
else if(operation.equals("query") || operation.equals("mutation")){
HttpStatus status = HttpStatus.FORBIDDEN;
String error = ApiError.jsonApiError(status, "Request type has been disabled", operation+" operations have been disabled in the application configuration.");
String error = ApiError.jsonApiError(status, "Request type has been disabled",
operation+" operations have been disabled in the application configuration.");
return logAndReturnError(status, error);
}
else {
HttpStatus status = HttpStatus.BAD_REQUEST;
String error = ApiError.jsonApiError(status, "Unknown operation in request", operation+" operation is not recognized.");
String error = ApiError.jsonApiError(status, "Unknown operation in request",
operation+" operation type is not recognized.");
return logAndReturnError(status, error);
}
}

private String query(String sdl, Map<String, Object> variables) {
private String query(String sdl, Map<String, Object> variables, GraphQL graphQL) {
ExecutionInput.Builder builder = ExecutionInput.newExecutionInput().query(sdl);
if (variables != null) {
builder = builder.variables(variables);
}
ExecutionInput input = builder.build();
ExecutionResult executionResult = graphql.execute(input);
ExecutionResult executionResult = graphQL.execute(input);
Map<String, Object> standardResult = executionResult.toSpecification();
return gson.toJson(standardResult);
}

private ResponseEntity logAndReturnError(HttpStatus status, String error){
logger.error(error);
return ResponseEntity.status(status).body(error);
}

@PostConstruct
public void initGraphQL() throws IOException {
GraphQLSchema neo4jSchema = getNeo4jSchema();
GraphQLSchema esSchema = getEsSchema();
GraphQLSchema newSchema = mergeSchema(neo4jSchema, esSchema);
graphql = GraphQL.newGraphQL(newSchema).build();
}

private GraphQLSchema getEsSchema() throws IOException {
if (config.getEsFilterEnabled()){
File schemaFile = new DefaultResourceLoader().getResource("classpath:" + config.getEsSchemaFile()).getFile();
return new SchemaGenerator().makeExecutableSchema(new SchemaParser().parse(schemaFile), esFilterDataFetcher.buildRuntimeWiring());
}
else{
return null;
private ResponseEntity logAndReturnError(HttpStatus status, BentoGraphQLException ex){
BentoGraphqlError bentoGraphqlError = ex.getBentoGraphqlError();
List<String> errors = bentoGraphqlError.getErrors();
for(String error: errors){
logger.error(error);
}
return ResponseEntity.status(status).body(gson.toJson(bentoGraphqlError));
}

@NotNull
private GraphQLSchema getNeo4jSchema() throws IOException {
ResourceLoader resourceLoader = new DefaultResourceLoader();
Resource resource = resourceLoader.getResource("classpath:" + config.getSchemaFile());
File schemaFile = resource.getFile();
String schemaString = Files.readString(schemaFile.toPath());
SchemaConfig schemaConfig = new SchemaConfig();

GraphQLSchema neo4jSchema = SchemaBuilder.buildSchema(schemaString, schemaConfig, dataFetcherInterceptor);
return neo4jSchema;
private ResponseEntity logAndReturnError(HttpStatus status, List<String> errors){
return logAndReturnError(status, new BentoGraphQLException(errors));
}


private GraphQLSchema mergeSchema(GraphQLSchema schema1, GraphQLSchema schema2) {
String QUERY_TYPE_NAME = "Query";
String MUTATION_TYPE_NAME = "Mutation";
String SUBSCRIPTION_TYPE_NAME = "Subscription";

if (schema1 == null) {
return schema2;
}
if (schema2 == null) {
return schema1;
}

var builder = GraphQLSchema.newSchema(schema1);
var codeRegistry2 = schema2.getCodeRegistry();
builder.codeRegistry(schema1.getCodeRegistry().transform( crBuilder -> {crBuilder.dataFetchers(codeRegistry2); crBuilder.typeResolvers(codeRegistry2);}));
var allTypes = new HashMap<String, GraphQLNamedType>(schema1.getTypeMap());
allTypes.putAll(schema2.getTypeMap());

//Remove individual schema query, mutation, and subscription types from all types to prevent naming conflicts
allTypes = removeQueryMutationSubscription(allTypes, schema1);
allTypes = removeQueryMutationSubscription(allTypes, schema2);

//Add merged query, mutation, and subscription types
GraphQLNamedType mergedQuery = mergeType(schema1.getQueryType(), schema2.getQueryType());
if (mergedQuery != null){
allTypes.put(QUERY_TYPE_NAME, mergedQuery);
}

GraphQLNamedType mergedMutation = mergeType(schema1.getMutationType(), schema2.getMutationType());
if (mergedMutation != null){
allTypes.put(MUTATION_TYPE_NAME, mergedMutation);
}

GraphQLNamedType mergedSubscription = mergeType(schema1.getSubscriptionType(), schema2.getSubscriptionType());
if (mergedSubscription != null){
allTypes.put(SUBSCRIPTION_TYPE_NAME, mergedSubscription);
}

builder.query((GraphQLObjectType) allTypes.get(QUERY_TYPE_NAME));
builder.mutation((GraphQLObjectType) allTypes.get(MUTATION_TYPE_NAME));
builder.subscription((GraphQLObjectType) allTypes.get(SUBSCRIPTION_TYPE_NAME));

builder.clearAdditionalTypes();
allTypes.values().forEach(builder::additionalType);

return builder.build();
}

private HashMap<String, GraphQLNamedType> removeQueryMutationSubscription(
HashMap<String, GraphQLNamedType> allTypes, GraphQLSchema schema){
try{
String name = schema.getQueryType().getName();
allTypes.remove(name);
}
catch (NullPointerException e){}

try{
String name = schema.getMutationType().getName();
allTypes.remove(name);
}
catch (NullPointerException e){}

try{
String name = schema.getSubscriptionType().getName();
allTypes.remove(name);
}
catch (NullPointerException e){}

return allTypes;
}

private GraphQLNamedType mergeType(GraphQLObjectType type1, GraphQLObjectType type2) {
if (type1 == null) {
return type2;
}
if (type2 == null) {
return type1;
}
var builder = GraphQLObjectType.newObject(type1);
type2.getFieldDefinitions().forEach(builder::field);
return builder.build();
private ResponseEntity logAndReturnError(HttpStatus status, String error){
ArrayList<String> errors = new ArrayList<>();
errors.add(error);
return logAndReturnError(status, errors);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,6 @@
public class IndexController {

private static final Logger logger = LogManager.getLogger(IndexController.class);



@RequestMapping(value = "/", produces = "text/html")
public ModelAndView errorHtml(HttpServletRequest request) {
Expand Down
Loading

0 comments on commit 278340f

Please sign in to comment.