From 0f1e15fbc5c8e8890a1dec402b1dccc0e6ba7d6c Mon Sep 17 00:00:00 2001 From: Scandinave Date: Sat, 20 Jul 2019 16:35:16 +0200 Subject: [PATCH 1/4] Update dependencies to latest versions --- pom.xml | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/pom.xml b/pom.xml index c8e8e76..9240d75 100644 --- a/pom.xml +++ b/pom.xml @@ -15,7 +15,8 @@ ~ limitations under the License. --> - + 4.0.0 org.illyasviel.elide @@ -92,12 +93,12 @@ 1.8 1.8 - 2.1.0.RELEASE - 4.2.10 - 3.4 + 2.1.6.RELEASE + 4.4.5 + 3.7 2.3.0 1.1.1 - 1.18.4 + 1.18.8 @@ -179,6 +180,7 @@ + org.apache.maven.plugins @@ -246,6 +248,7 @@ + From bb65ef56d10500b4875004f66694edd1cfafa0c6 Mon Sep 17 00:00:00 2001 From: Scandinave Date: Sat, 20 Jul 2019 16:35:35 +0200 Subject: [PATCH 2/4] Remove deprecated function --- .gitignore | 3 + .../autoconfigure/ElideAutoConfiguration.java | 47 +- .../spring/boot/graphql/GraphQLHandle.java | 443 +++++++++--------- 3 files changed, 238 insertions(+), 255 deletions(-) diff --git a/.gitignore b/.gitignore index 148c592..0b8fa3a 100644 --- a/.gitignore +++ b/.gitignore @@ -24,3 +24,6 @@ nbbuild/ dist/ nbdist/ .nb-gradle/ + +### vsCode ### +.vscode \ No newline at end of file diff --git a/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/autoconfigure/ElideAutoConfiguration.java b/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/autoconfigure/ElideAutoConfiguration.java index c916be9..090779d 100644 --- a/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/autoconfigure/ElideAutoConfiguration.java +++ b/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/autoconfigure/ElideAutoConfiguration.java @@ -50,12 +50,13 @@ /** * Elide AutoConfiguration. + * * @author olOwOlo */ @Configuration @EnableConfigurationProperties(ElideProperties.class) @ConditionalOnWebApplication -@AutoConfigureAfter({HibernateJpaAutoConfiguration.class, WebMvcAutoConfiguration.class}) +@AutoConfigureAfter({ HibernateJpaAutoConfiguration.class, WebMvcAutoConfiguration.class }) public class ElideAutoConfiguration { private static final Logger logger = LoggerFactory.getLogger(ElideAutoConfiguration.class); @@ -65,11 +66,8 @@ public class ElideAutoConfiguration { */ @Bean @ConditionalOnMissingBean - public Elide elide(PlatformTransactionManager txManager, - AutowireCapableBeanFactory beanFactory, - ApplicationContext context, - EntityManager entityManager, - ObjectMapper objectMapper, + public Elide elide(PlatformTransactionManager txManager, AutowireCapableBeanFactory beanFactory, + ApplicationContext context, EntityManager entityManager, ObjectMapper objectMapper, ElideProperties elideProperties) { ConcurrentHashMap> checks = new ConcurrentHashMap<>(); @@ -79,18 +77,14 @@ public Elide elide(PlatformTransactionManager txManager, EntityDictionary entityDictionary = new EntityDictionary(checks); RSQLFilterDialect rsqlFilterDialect = new RSQLFilterDialect(entityDictionary); - DataStore springDataStore = new SpringHibernateDataStore(txManager, beanFactory, entityManager, - elideProperties, true, ScrollMode.FORWARD_ONLY); + DataStore springDataStore = new SpringHibernateDataStore(txManager, beanFactory, entityManager, elideProperties, + true, ScrollMode.FORWARD_ONLY); - Elide elide = new Elide(new ElideSettingsBuilder(springDataStore) - .withJsonApiMapper(new JsonApiMapper(entityDictionary, objectMapper)) - .withEntityDictionary(entityDictionary) - .withJoinFilterDialect(rsqlFilterDialect) - .withSubqueryFilterDialect(rsqlFilterDialect) - .withDefaultPageSize(elideProperties.getDefaultPageSize()) + Elide elide = new Elide(new ElideSettingsBuilder(springDataStore).withJsonApiMapper(new JsonApiMapper(objectMapper)) + .withEntityDictionary(entityDictionary).withJoinFilterDialect(rsqlFilterDialect) + .withSubqueryFilterDialect(rsqlFilterDialect).withDefaultPageSize(elideProperties.getDefaultPageSize()) .withDefaultMaxPageSize(elideProperties.getMaxPageSize()) - .withReturnErrorObjects(elideProperties.isReturnErrorObjects()) - .build()); + .withReturnErrorObjects(elideProperties.isReturnErrorObjects()).build()); // scan life cycle hooks scanLifeCycleHook(entityDictionary, context); @@ -105,12 +99,11 @@ private void scanChecks(ConcurrentHashMap> checks for (Class clazz : ClassIndex.getAnnotated(ElideCheck.class)) { ElideCheck elideCheck = clazz.getAnnotation(ElideCheck.class); if (Check.class.isAssignableFrom(clazz)) { - logger.debug("Register Elide Check [{}] with expression [{}]", - clazz.getCanonicalName(), elideCheck.value()); + logger.debug("Register Elide Check [{}] with expression [{}]", clazz.getCanonicalName(), elideCheck.value()); checks.put(elideCheck.value(), clazz.asSubclass(Check.class)); } else { - throw new RuntimeException("The class[" + clazz.getCanonicalName() - + "] being annotated with @ElideCheck must be a Check."); + throw new RuntimeException( + "The class[" + clazz.getCanonicalName() + "] being annotated with @ElideCheck must be a Check."); } } } @@ -136,18 +129,14 @@ private void scanLifeCycleHook(EntityDictionary entityDictionary, ApplicationCon } if (elideHook.fieldOrMethodName().equals("")) { - entityDictionary.bindTrigger(entity, elideHook.lifeCycle(), - (LifeCycleHook) context.getBean(clazz)); + entityDictionary.bindTrigger(entity, elideHook.lifeCycle(), (LifeCycleHook) context.getBean(clazz)); } else { - entityDictionary.bindTrigger(entity, elideHook.lifeCycle(), - elideHook.fieldOrMethodName(), (LifeCycleHook) context.getBean(clazz)); + entityDictionary.bindTrigger(entity, elideHook.lifeCycle(), elideHook.fieldOrMethodName(), + (LifeCycleHook) context.getBean(clazz)); } - logger.debug("Register Elide Function Hook: bindTrigger({}, {}, \"{}\", {})", - entity.getCanonicalName(), - elideHook.lifeCycle().getSimpleName(), - elideHook.fieldOrMethodName(), - clazz.getCanonicalName()); + logger.debug("Register Elide Function Hook: bindTrigger({}, {}, \"{}\", {})", entity.getCanonicalName(), + elideHook.lifeCycle().getSimpleName(), elideHook.fieldOrMethodName(), clazz.getCanonicalName()); } else { throw new RuntimeException("The class[" + clazz.getCanonicalName() diff --git a/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/graphql/GraphQLHandle.java b/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/graphql/GraphQLHandle.java index 00aeae5..52b6830 100644 --- a/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/graphql/GraphQLHandle.java +++ b/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/graphql/GraphQLHandle.java @@ -1,12 +1,9 @@ /* * Copyright (c) 2018 the original author or authors. - * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * + * http://www.apache.org/licenses/LICENSE-2.0 * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. @@ -16,6 +13,20 @@ package org.illyasviel.elide.spring.boot.graphql; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; + +import javax.ws.rs.WebApplicationException; + +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -35,241 +46,221 @@ import com.yahoo.elide.graphql.ModelBuilder; import com.yahoo.elide.graphql.PersistentResourceFetcher; import com.yahoo.elide.security.User; + import graphql.ExecutionInput; import graphql.ExecutionResult; import graphql.GraphQL; -import java.io.IOException; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; -import javax.ws.rs.WebApplicationException; -import org.apache.commons.lang3.tuple.Pair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; /** * GraphQLHandle. *
- * Based on GraphQLEndpoint.java + * Based on GraphQLEndpoint.java * Latest commit is 9d6e11efa0ba26f81286c9b0a3b298467359900a. *
    - *
  • SecurityContext -> Object(opaqueUser)
  • - *
  • javax.ws.rs.core.Response -> ElideResponse
  • + *
  • SecurityContext -> Object(opaqueUser)
  • + *
  • javax.ws.rs.core.Response -> ElideResponse
  • *
* * @author olOwOlo */ public class GraphQLHandle { - private static final Logger log = LoggerFactory.getLogger(GraphQLHandle.class); - - private Elide elide; - private ElideSettings elideSettings; - private GraphQL api; - - private static final String QUERY = "query"; - private static final String OPERATION_NAME = "operationName"; - private static final String VARIABLES = "variables"; - private static final String MUTATION = "mutation"; - - public GraphQLHandle(Elide elide) { - log.info("Elide GraphQL Started ~~"); - this.elide = elide; - this.elideSettings = elide.getElideSettings(); - PersistentResourceFetcher fetcher = new PersistentResourceFetcher(elide.getElideSettings()); - ModelBuilder builder = new ModelBuilder(elide.getElideSettings().getDictionary(), fetcher); - this.api = new GraphQL(builder.build()); - } - - /** - * Create handler. - * - * @param graphQLDocument post data as jsonapi document - * @param opaqueUser opaqueUser - * @return response - */ - public ElideResponse post(String graphQLDocument, Object opaqueUser) { - ObjectMapper mapper = elide.getMapper().getObjectMapper(); - - JsonNode topLevel; - - try { - topLevel = mapper.readTree(graphQLDocument); - } catch (IOException e) { - log.debug("Invalid json body provided to GraphQL", e); - // NOTE: Can't get at isVerbose setting here for hardcoding to false. If necessary, we can refactor - // so this can be set appropriately. - return buildErrorResponse(new InvalidEntityBodyException(graphQLDocument), false); - } - - Function executeRequest = - (node) -> executeGraphQLRequest(mapper, opaqueUser, graphQLDocument, node); - - if (topLevel.isArray()) { - Iterator nodeIterator = topLevel.iterator(); - Iterable nodeIterable = () -> nodeIterator; - // NOTE: Create a non-parallel stream - // It's unclear whether or not the expectations of the caller would be that requests are intended - // to run serially even outside of a single transaction. We should revisit this. - Stream nodeStream = StreamSupport.stream(nodeIterable.spliterator(), false); - ArrayNode result = nodeStream - .map(executeRequest) - .map(response -> { - try { - return mapper.readTree(response.getBody()); - } catch (IOException e) { - log.debug("Caught an IO exception while trying to read response body"); - return JsonNodeFactory.instance.objectNode(); - } - }) - .reduce(JsonNodeFactory.instance.arrayNode(), - (arrayNode, node) -> arrayNode.add(node), - (left, right) -> left.addAll(right)); - try { - return new ElideResponse(HttpStatus.SC_OK, mapper.writeValueAsString(result)); - } catch (JsonProcessingException e) { - log.error("An unexpected error occurred trying to serialize array response.", e); - throw new RuntimeException(e); // 500 - } - } - - return executeRequest.apply(topLevel); - } - - private ElideResponse executeGraphQLRequest( - ObjectMapper mapper, - Object opaqueUser, - String graphQLDocument, - JsonNode jsonDocument) { - boolean isVerbose = false; - try (DataStoreTransaction tx = elide.getDataStore().beginTransaction()) { - final User user = tx.accessUser(opaqueUser); - GraphQLRequestScope requestScope = new GraphQLRequestScope(tx, user, elide.getElideSettings()); - isVerbose = requestScope.getPermissionExecutor().isVerbose(); - - if (!jsonDocument.has(QUERY)) { - return new ElideResponse(HttpStatus.SC_BAD_REQUEST, "A `query` key is required."); - } - - String query = jsonDocument.get(QUERY).asText(); - - // Logging all queries. It is recommended to put any private information that shouldn't be logged into - // the "variables" section of your query. Variable values are not logged. - log.info("Processing GraphQL query:\n{}", query); - - ExecutionInput.Builder executionInput = new ExecutionInput.Builder() - .context(requestScope) - .query(query); - - if (jsonDocument.has(OPERATION_NAME) && !jsonDocument.get(OPERATION_NAME).isNull()) { - executionInput.operationName(jsonDocument.get(OPERATION_NAME).asText()); - } - - if (jsonDocument.has(VARIABLES) && !jsonDocument.get(VARIABLES).isNull()) { - Map variables = mapper.convertValue(jsonDocument.get(VARIABLES), Map.class); - executionInput.variables(variables); - } - - ExecutionResult result = api.execute(executionInput); - - tx.preCommit(); - requestScope.runQueuedPreSecurityTriggers(); - requestScope.getPermissionExecutor().executeCommitChecks(); - if (query.trim().startsWith(MUTATION)) { - if (!result.getErrors().isEmpty()) { - HashMap abortedResponseObject = new HashMap<>(); - abortedResponseObject.put("errors", result.getErrors()); - abortedResponseObject.put("data", null); - - // Do not commit. Throw OK response to process tx.close correctly. - - throw new GraphQLErrorException(200, mapper.writeValueAsString(abortedResponseObject)); - } - requestScope.saveOrCreateObjects(); - } - tx.flush(requestScope); - - requestScope.runQueuedPreCommitTriggers(); - elide.getAuditLogger().commit(requestScope); - tx.commit(requestScope); - requestScope.runQueuedPostCommitTriggers(); - - if (log.isTraceEnabled()) { - requestScope.getPermissionExecutor().printCheckStats(); - } - - return new ElideResponse(HttpStatus.SC_OK, mapper.writeValueAsString(result.toSpecification())); - } catch (WebApplicationException e) { - log.debug("WebApplicationException", e); - throw e; - } catch (GraphQLErrorException e) { - log.debug("GraphQLErrorException", e); - return e.getResponse(); - } catch (JsonProcessingException e) { - log.debug("Invalid json body provided to GraphQL", e); - return buildErrorResponse(new InvalidEntityBodyException(graphQLDocument), isVerbose); - } catch (IOException e) { - log.error("Uncaught IO Exception by Elide in GraphQL", e); - return buildErrorResponse(new TransactionException(e), isVerbose); - } catch (HttpStatusException e) { - log.debug("Caught HTTP status exception {}", e.getStatus(), e); - return buildErrorResponse(new HttpStatusException(200, "") { - private static final long serialVersionUID = 1L; - - @Override - public int getStatus() { - return 200; - } - - @Override - public Pair getErrorResponse() { - return e.getErrorResponse(); - } - - @Override - public Pair getVerboseErrorResponse() { - return e.getVerboseErrorResponse(); - } - - @Override - public String getVerboseMessage() { - return e.getVerboseMessage(); - } - - @Override - public String toString() { - return e.toString(); - } - }, isVerbose); - } catch (Exception | Error e) { - log.debug("Unhandled error or exception.", e); - throw e; - } finally { - elide.getAuditLogger().clear(); - } - } - - private ElideResponse buildErrorResponse(HttpStatusException error, boolean isVerbose) { - ObjectMapper mapper = elide.getMapper().getObjectMapper(); - JsonNode errorNode; - if (!(error instanceof CustomErrorException) && elideSettings.isReturnErrorObjects()) { - ErrorObjects errors = ErrorObjects.builder().addError() - .with("message", isVerbose ? error.getVerboseMessage() : error.toString()).build(); - errorNode = mapper.convertValue(errors, JsonNode.class); - } else { - errorNode = isVerbose - ? error.getVerboseErrorResponse().getRight() - : error.getErrorResponse().getRight(); - } - String errorBody; - try { - errorBody = mapper.writeValueAsString(errorNode); - } catch (JsonProcessingException e) { - errorBody = errorNode.toString(); - } - return new ElideResponse(error.getStatus(), errorBody); - } + private static final Logger log = LoggerFactory.getLogger(GraphQLHandle.class); + + private Elide elide; + private ElideSettings elideSettings; + private GraphQL api; + + private static final String QUERY = "query"; + private static final String OPERATION_NAME = "operationName"; + private static final String VARIABLES = "variables"; + private static final String MUTATION = "mutation"; + + public GraphQLHandle(Elide elide) { + log.info("Elide GraphQL Started ~~"); + this.elide = elide; + this.elideSettings = elide.getElideSettings(); + PersistentResourceFetcher fetcher = new PersistentResourceFetcher(elide.getElideSettings()); + ModelBuilder builder = new ModelBuilder(elide.getElideSettings().getDictionary(), fetcher); + this.api = GraphQL.newGraphQL(builder.build()).build(); + } + + /** + * Create handler. + * + * @param graphQLDocument post data as jsonapi document + * @param opaqueUser opaqueUser + * @return response + */ + public ElideResponse post(String graphQLDocument, Object opaqueUser) { + ObjectMapper mapper = elide.getMapper().getObjectMapper(); + + JsonNode topLevel; + + try { + topLevel = mapper.readTree(graphQLDocument); + } catch (IOException e) { + log.debug("Invalid json body provided to GraphQL", e); + // NOTE: Can't get at isVerbose setting here for hardcoding to false. If necessary, we can refactor + // so this can be set appropriately. + return buildErrorResponse(new InvalidEntityBodyException(graphQLDocument), false); + } + + Function executeRequest = (node) -> executeGraphQLRequest(mapper, opaqueUser, + graphQLDocument, node); + + if (topLevel.isArray()) { + Iterator nodeIterator = topLevel.iterator(); + Iterable nodeIterable = () -> nodeIterator; + // NOTE: Create a non-parallel stream + // It's unclear whether or not the expectations of the caller would be that requests are intended + // to run serially even outside of a single transaction. We should revisit this. + Stream nodeStream = StreamSupport.stream(nodeIterable.spliterator(), false); + ArrayNode result = nodeStream.map(executeRequest).map(response -> { + try { + return mapper.readTree(response.getBody()); + } catch (IOException e) { + log.debug("Caught an IO exception while trying to read response body"); + return JsonNodeFactory.instance.objectNode(); + } + }).reduce(JsonNodeFactory.instance.arrayNode(), (arrayNode, node) -> arrayNode.add(node), + (left, right) -> left.addAll(right)); + try { + return new ElideResponse(HttpStatus.SC_OK, mapper.writeValueAsString(result)); + } catch (JsonProcessingException e) { + log.error("An unexpected error occurred trying to serialize array response.", e); + throw new RuntimeException(e); // 500 + } + } + + return executeRequest.apply(topLevel); + } + + private ElideResponse executeGraphQLRequest(ObjectMapper mapper, Object opaqueUser, String graphQLDocument, + JsonNode jsonDocument) { + boolean isVerbose = false; + try (DataStoreTransaction tx = elide.getDataStore().beginTransaction()) { + final User user = tx.accessUser(opaqueUser); + GraphQLRequestScope requestScope = new GraphQLRequestScope(tx, user, elide.getElideSettings()); + isVerbose = requestScope.getPermissionExecutor().isVerbose(); + + if (!jsonDocument.has(QUERY)) { + return new ElideResponse(HttpStatus.SC_BAD_REQUEST, "A `query` key is required."); + } + + String query = jsonDocument.get(QUERY).asText(); + + // Logging all queries. It is recommended to put any private information that shouldn't be logged into + // the "variables" section of your query. Variable values are not logged. + log.info("Processing GraphQL query:\n{}", query); + + ExecutionInput.Builder executionInput = new ExecutionInput.Builder().context(requestScope).query(query); + + if (jsonDocument.has(OPERATION_NAME) && !jsonDocument.get(OPERATION_NAME).isNull()) { + executionInput.operationName(jsonDocument.get(OPERATION_NAME).asText()); + } + + if (jsonDocument.has(VARIABLES) && !jsonDocument.get(VARIABLES).isNull()) { + Map variables = mapper.convertValue(jsonDocument.get(VARIABLES), Map.class); + executionInput.variables(variables); + } + + ExecutionResult result = api.execute(executionInput); + + tx.preCommit(); + requestScope.runQueuedPreSecurityTriggers(); + requestScope.getPermissionExecutor().executeCommitChecks(); + if (query.trim().startsWith(MUTATION)) { + if (!result.getErrors().isEmpty()) { + HashMap abortedResponseObject = new HashMap<>(); + abortedResponseObject.put("errors", result.getErrors()); + abortedResponseObject.put("data", null); + + // Do not commit. Throw OK response to process tx.close correctly. + + throw new GraphQLErrorException(200, mapper.writeValueAsString(abortedResponseObject)); + } + requestScope.saveOrCreateObjects(); + } + tx.flush(requestScope); + + requestScope.runQueuedPreCommitTriggers(); + elide.getAuditLogger().commit(requestScope); + tx.commit(requestScope); + requestScope.runQueuedPostCommitTriggers(); + + if (log.isTraceEnabled()) { + requestScope.getPermissionExecutor().printCheckStats(); + } + + return new ElideResponse(HttpStatus.SC_OK, mapper.writeValueAsString(result.toSpecification())); + } catch (WebApplicationException e) { + log.debug("WebApplicationException", e); + throw e; + } catch (GraphQLErrorException e) { + log.debug("GraphQLErrorException", e); + return e.getResponse(); + } catch (JsonProcessingException e) { + log.debug("Invalid json body provided to GraphQL", e); + return buildErrorResponse(new InvalidEntityBodyException(graphQLDocument), isVerbose); + } catch (IOException e) { + log.error("Uncaught IO Exception by Elide in GraphQL", e); + return buildErrorResponse(new TransactionException(e), isVerbose); + } catch (HttpStatusException e) { + log.debug("Caught HTTP status exception {}", e.getStatus(), e); + return buildErrorResponse(new HttpStatusException(200, "") { + private static final long serialVersionUID = 1L; + + @Override + public int getStatus() { + return 200; + } + + @Override + public Pair getErrorResponse() { + return e.getErrorResponse(); + } + + @Override + public Pair getVerboseErrorResponse() { + return e.getVerboseErrorResponse(); + } + + @Override + public String getVerboseMessage() { + return e.getVerboseMessage(); + } + + @Override + public String toString() { + return e.toString(); + } + }, isVerbose); + } catch (Exception | Error e) { + log.debug("Unhandled error or exception.", e); + throw e; + } finally { + elide.getAuditLogger().clear(); + } + } + + private ElideResponse buildErrorResponse(HttpStatusException error, boolean isVerbose) { + ObjectMapper mapper = elide.getMapper().getObjectMapper(); + JsonNode errorNode; + if (!(error instanceof CustomErrorException) && elideSettings.isReturnErrorObjects()) { + ErrorObjects errors = ErrorObjects.builder().addError() + .with("message", isVerbose ? error.getVerboseMessage() : error.toString()).build(); + errorNode = mapper.convertValue(errors, JsonNode.class); + } else { + errorNode = isVerbose ? error.getVerboseErrorResponse().getRight() : error.getErrorResponse().getRight(); + } + String errorBody; + try { + errorBody = mapper.writeValueAsString(errorNode); + } catch (JsonProcessingException e) { + errorBody = errorNode.toString(); + } + return new ElideResponse(error.getStatus(), errorBody); + } } From 031a99cc53d934626c3ebfc31da44c696b6b7495 Mon Sep 17 00:00:00 2001 From: Scandinave Date: Sun, 21 Jul 2019 16:17:41 +0200 Subject: [PATCH 3/4] Update travis to use openJDK instead of oracleJDK --- .travis.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.travis.yml b/.travis.yml index f5dd731..04ba496 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,11 +2,12 @@ language: java jdk: - oraclejdk11 - - oraclejdk8 + - openjdk8 install: - chmod -R 777 ./mvnw - ./mvnw install -DskipTests=true -Dmaven.javadoc.skip=true -B -V + after_success: - ./mvnw clean test jacoco:report coveralls:report -Dcoveralls.token=$COVERALLS From 1563b7454540aff56c5f75fb28abe3c511c367ec Mon Sep 17 00:00:00 2001 From: Scandinave Date: Sun, 21 Jul 2019 23:24:39 +0200 Subject: [PATCH 4/4] Fix code style --- .gitignore | 3 +- .../autoconfigure/ElideAutoConfiguration.java | 42 +- .../spring/boot/graphql/GraphQLHandle.java | 435 +++++++++--------- 3 files changed, 250 insertions(+), 230 deletions(-) diff --git a/.gitignore b/.gitignore index 0b8fa3a..a2071b3 100644 --- a/.gitignore +++ b/.gitignore @@ -26,4 +26,5 @@ nbdist/ .nb-gradle/ ### vsCode ### -.vscode \ No newline at end of file +.vscode +/bin/ diff --git a/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/autoconfigure/ElideAutoConfiguration.java b/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/autoconfigure/ElideAutoConfiguration.java index 090779d..db04237 100644 --- a/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/autoconfigure/ElideAutoConfiguration.java +++ b/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/autoconfigure/ElideAutoConfiguration.java @@ -66,8 +66,11 @@ public class ElideAutoConfiguration { */ @Bean @ConditionalOnMissingBean - public Elide elide(PlatformTransactionManager txManager, AutowireCapableBeanFactory beanFactory, - ApplicationContext context, EntityManager entityManager, ObjectMapper objectMapper, + public Elide elide(PlatformTransactionManager txManager, + AutowireCapableBeanFactory beanFactory, + ApplicationContext context, + EntityManager entityManager, + ObjectMapper objectMapper, ElideProperties elideProperties) { ConcurrentHashMap> checks = new ConcurrentHashMap<>(); @@ -77,14 +80,18 @@ public Elide elide(PlatformTransactionManager txManager, AutowireCapableBeanFact EntityDictionary entityDictionary = new EntityDictionary(checks); RSQLFilterDialect rsqlFilterDialect = new RSQLFilterDialect(entityDictionary); - DataStore springDataStore = new SpringHibernateDataStore(txManager, beanFactory, entityManager, elideProperties, - true, ScrollMode.FORWARD_ONLY); + DataStore springDataStore = new SpringHibernateDataStore(txManager, beanFactory, entityManager, + elideProperties, true, ScrollMode.FORWARD_ONLY); - Elide elide = new Elide(new ElideSettingsBuilder(springDataStore).withJsonApiMapper(new JsonApiMapper(objectMapper)) - .withEntityDictionary(entityDictionary).withJoinFilterDialect(rsqlFilterDialect) - .withSubqueryFilterDialect(rsqlFilterDialect).withDefaultPageSize(elideProperties.getDefaultPageSize()) + Elide elide = new Elide(new ElideSettingsBuilder(springDataStore) + .withJsonApiMapper(new JsonApiMapper(objectMapper)) + .withEntityDictionary(entityDictionary) + .withJoinFilterDialect(rsqlFilterDialect) + .withSubqueryFilterDialect(rsqlFilterDialect) + .withDefaultPageSize(elideProperties.getDefaultPageSize()) .withDefaultMaxPageSize(elideProperties.getMaxPageSize()) - .withReturnErrorObjects(elideProperties.isReturnErrorObjects()).build()); + .withReturnErrorObjects(elideProperties.isReturnErrorObjects()) + .build()); // scan life cycle hooks scanLifeCycleHook(entityDictionary, context); @@ -99,11 +106,12 @@ private void scanChecks(ConcurrentHashMap> checks for (Class clazz : ClassIndex.getAnnotated(ElideCheck.class)) { ElideCheck elideCheck = clazz.getAnnotation(ElideCheck.class); if (Check.class.isAssignableFrom(clazz)) { - logger.debug("Register Elide Check [{}] with expression [{}]", clazz.getCanonicalName(), elideCheck.value()); + logger.debug("Register Elide Check [{}] with expression [{}]", clazz.getCanonicalName(), + elideCheck.value()); checks.put(elideCheck.value(), clazz.asSubclass(Check.class)); } else { - throw new RuntimeException( - "The class[" + clazz.getCanonicalName() + "] being annotated with @ElideCheck must be a Check."); + throw new RuntimeException("The class[" + clazz.getCanonicalName() + + "] being annotated with @ElideCheck must be a Check."); } } } @@ -129,14 +137,16 @@ private void scanLifeCycleHook(EntityDictionary entityDictionary, ApplicationCon } if (elideHook.fieldOrMethodName().equals("")) { - entityDictionary.bindTrigger(entity, elideHook.lifeCycle(), (LifeCycleHook) context.getBean(clazz)); - } else { - entityDictionary.bindTrigger(entity, elideHook.lifeCycle(), elideHook.fieldOrMethodName(), + entityDictionary.bindTrigger(entity, elideHook.lifeCycle(), (LifeCycleHook) context.getBean(clazz)); + } else { + entityDictionary.bindTrigger(entity, elideHook.lifeCycle(), + elideHook.fieldOrMethodName(), (LifeCycleHook) context.getBean(clazz)); } - logger.debug("Register Elide Function Hook: bindTrigger({}, {}, \"{}\", {})", entity.getCanonicalName(), - elideHook.lifeCycle().getSimpleName(), elideHook.fieldOrMethodName(), clazz.getCanonicalName()); + logger.debug("Register Elide Function Hook: bindTrigger({}, {}, \"{}\", {})", + entity.getCanonicalName(), elideHook.lifeCycle().getSimpleName(), + elideHook.fieldOrMethodName(), clazz.getCanonicalName()); } else { throw new RuntimeException("The class[" + clazz.getCanonicalName() diff --git a/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/graphql/GraphQLHandle.java b/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/graphql/GraphQLHandle.java index 52b6830..0c1ac1e 100644 --- a/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/graphql/GraphQLHandle.java +++ b/elide-spring-boot-autoconfigure/src/main/java/org/illyasviel/elide/spring/boot/graphql/GraphQLHandle.java @@ -13,20 +13,6 @@ package org.illyasviel.elide.spring.boot.graphql; -import java.io.IOException; -import java.util.HashMap; -import java.util.Iterator; -import java.util.Map; -import java.util.function.Function; -import java.util.stream.Stream; -import java.util.stream.StreamSupport; - -import javax.ws.rs.WebApplicationException; - -import org.apache.commons.lang3.tuple.Pair; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; @@ -50,10 +36,20 @@ import graphql.ExecutionInput; import graphql.ExecutionResult; import graphql.GraphQL; +import java.io.IOException; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.function.Function; +import java.util.stream.Stream; +import java.util.stream.StreamSupport; +import javax.ws.rs.WebApplicationException; +import org.apache.commons.lang3.tuple.Pair; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** - * GraphQLHandle. - *
+ * GraphQLHandle.
* Based on GraphQLEndpoint.java * Latest commit is 9d6e11efa0ba26f81286c9b0a3b298467359900a. @@ -66,201 +62,214 @@ */ public class GraphQLHandle { - private static final Logger log = LoggerFactory.getLogger(GraphQLHandle.class); - - private Elide elide; - private ElideSettings elideSettings; - private GraphQL api; - - private static final String QUERY = "query"; - private static final String OPERATION_NAME = "operationName"; - private static final String VARIABLES = "variables"; - private static final String MUTATION = "mutation"; - - public GraphQLHandle(Elide elide) { - log.info("Elide GraphQL Started ~~"); - this.elide = elide; - this.elideSettings = elide.getElideSettings(); - PersistentResourceFetcher fetcher = new PersistentResourceFetcher(elide.getElideSettings()); - ModelBuilder builder = new ModelBuilder(elide.getElideSettings().getDictionary(), fetcher); - this.api = GraphQL.newGraphQL(builder.build()).build(); - } - - /** - * Create handler. - * - * @param graphQLDocument post data as jsonapi document - * @param opaqueUser opaqueUser - * @return response - */ - public ElideResponse post(String graphQLDocument, Object opaqueUser) { - ObjectMapper mapper = elide.getMapper().getObjectMapper(); - - JsonNode topLevel; - - try { - topLevel = mapper.readTree(graphQLDocument); - } catch (IOException e) { - log.debug("Invalid json body provided to GraphQL", e); - // NOTE: Can't get at isVerbose setting here for hardcoding to false. If necessary, we can refactor - // so this can be set appropriately. - return buildErrorResponse(new InvalidEntityBodyException(graphQLDocument), false); - } - - Function executeRequest = (node) -> executeGraphQLRequest(mapper, opaqueUser, - graphQLDocument, node); - - if (topLevel.isArray()) { - Iterator nodeIterator = topLevel.iterator(); - Iterable nodeIterable = () -> nodeIterator; - // NOTE: Create a non-parallel stream - // It's unclear whether or not the expectations of the caller would be that requests are intended - // to run serially even outside of a single transaction. We should revisit this. - Stream nodeStream = StreamSupport.stream(nodeIterable.spliterator(), false); - ArrayNode result = nodeStream.map(executeRequest).map(response -> { - try { - return mapper.readTree(response.getBody()); - } catch (IOException e) { - log.debug("Caught an IO exception while trying to read response body"); - return JsonNodeFactory.instance.objectNode(); - } - }).reduce(JsonNodeFactory.instance.arrayNode(), (arrayNode, node) -> arrayNode.add(node), - (left, right) -> left.addAll(right)); - try { - return new ElideResponse(HttpStatus.SC_OK, mapper.writeValueAsString(result)); - } catch (JsonProcessingException e) { - log.error("An unexpected error occurred trying to serialize array response.", e); - throw new RuntimeException(e); // 500 - } - } - - return executeRequest.apply(topLevel); - } - - private ElideResponse executeGraphQLRequest(ObjectMapper mapper, Object opaqueUser, String graphQLDocument, - JsonNode jsonDocument) { - boolean isVerbose = false; - try (DataStoreTransaction tx = elide.getDataStore().beginTransaction()) { - final User user = tx.accessUser(opaqueUser); - GraphQLRequestScope requestScope = new GraphQLRequestScope(tx, user, elide.getElideSettings()); - isVerbose = requestScope.getPermissionExecutor().isVerbose(); - - if (!jsonDocument.has(QUERY)) { - return new ElideResponse(HttpStatus.SC_BAD_REQUEST, "A `query` key is required."); - } - - String query = jsonDocument.get(QUERY).asText(); - - // Logging all queries. It is recommended to put any private information that shouldn't be logged into - // the "variables" section of your query. Variable values are not logged. - log.info("Processing GraphQL query:\n{}", query); - - ExecutionInput.Builder executionInput = new ExecutionInput.Builder().context(requestScope).query(query); - - if (jsonDocument.has(OPERATION_NAME) && !jsonDocument.get(OPERATION_NAME).isNull()) { - executionInput.operationName(jsonDocument.get(OPERATION_NAME).asText()); - } - - if (jsonDocument.has(VARIABLES) && !jsonDocument.get(VARIABLES).isNull()) { - Map variables = mapper.convertValue(jsonDocument.get(VARIABLES), Map.class); - executionInput.variables(variables); - } - - ExecutionResult result = api.execute(executionInput); - - tx.preCommit(); - requestScope.runQueuedPreSecurityTriggers(); - requestScope.getPermissionExecutor().executeCommitChecks(); - if (query.trim().startsWith(MUTATION)) { - if (!result.getErrors().isEmpty()) { - HashMap abortedResponseObject = new HashMap<>(); - abortedResponseObject.put("errors", result.getErrors()); - abortedResponseObject.put("data", null); - - // Do not commit. Throw OK response to process tx.close correctly. - - throw new GraphQLErrorException(200, mapper.writeValueAsString(abortedResponseObject)); - } - requestScope.saveOrCreateObjects(); - } - tx.flush(requestScope); - - requestScope.runQueuedPreCommitTriggers(); - elide.getAuditLogger().commit(requestScope); - tx.commit(requestScope); - requestScope.runQueuedPostCommitTriggers(); - - if (log.isTraceEnabled()) { - requestScope.getPermissionExecutor().printCheckStats(); - } - - return new ElideResponse(HttpStatus.SC_OK, mapper.writeValueAsString(result.toSpecification())); - } catch (WebApplicationException e) { - log.debug("WebApplicationException", e); - throw e; - } catch (GraphQLErrorException e) { - log.debug("GraphQLErrorException", e); - return e.getResponse(); - } catch (JsonProcessingException e) { - log.debug("Invalid json body provided to GraphQL", e); - return buildErrorResponse(new InvalidEntityBodyException(graphQLDocument), isVerbose); - } catch (IOException e) { - log.error("Uncaught IO Exception by Elide in GraphQL", e); - return buildErrorResponse(new TransactionException(e), isVerbose); - } catch (HttpStatusException e) { - log.debug("Caught HTTP status exception {}", e.getStatus(), e); - return buildErrorResponse(new HttpStatusException(200, "") { - private static final long serialVersionUID = 1L; - - @Override - public int getStatus() { - return 200; - } - - @Override - public Pair getErrorResponse() { - return e.getErrorResponse(); - } - - @Override - public Pair getVerboseErrorResponse() { - return e.getVerboseErrorResponse(); - } - - @Override - public String getVerboseMessage() { - return e.getVerboseMessage(); - } - - @Override - public String toString() { - return e.toString(); - } - }, isVerbose); - } catch (Exception | Error e) { - log.debug("Unhandled error or exception.", e); - throw e; - } finally { - elide.getAuditLogger().clear(); - } - } - - private ElideResponse buildErrorResponse(HttpStatusException error, boolean isVerbose) { - ObjectMapper mapper = elide.getMapper().getObjectMapper(); - JsonNode errorNode; - if (!(error instanceof CustomErrorException) && elideSettings.isReturnErrorObjects()) { - ErrorObjects errors = ErrorObjects.builder().addError() - .with("message", isVerbose ? error.getVerboseMessage() : error.toString()).build(); - errorNode = mapper.convertValue(errors, JsonNode.class); - } else { - errorNode = isVerbose ? error.getVerboseErrorResponse().getRight() : error.getErrorResponse().getRight(); - } - String errorBody; - try { - errorBody = mapper.writeValueAsString(errorNode); - } catch (JsonProcessingException e) { - errorBody = errorNode.toString(); - } - return new ElideResponse(error.getStatus(), errorBody); - } + private static final Logger log = LoggerFactory.getLogger(GraphQLHandle.class); + + private Elide elide; + private ElideSettings elideSettings; + private GraphQL api; + + private static final String QUERY = "query"; + private static final String OPERATION_NAME = "operationName"; + private static final String VARIABLES = "variables"; + private static final String MUTATION = "mutation"; + + public GraphQLHandle(Elide elide) { + log.info("Elide GraphQL Started ~~"); + this.elide = elide; + this.elideSettings = elide.getElideSettings(); + PersistentResourceFetcher fetcher = new PersistentResourceFetcher(elide.getElideSettings()); + ModelBuilder builder = new ModelBuilder(elide.getElideSettings().getDictionary(), fetcher); + this.api = GraphQL.newGraphQL(builder.build()).build(); + } + + /** + * Create handler. + * + * @param graphQLDocument post data as jsonapi document + * @param opaqueUser opaqueUser + * @return response + */ + public ElideResponse post(String graphQLDocument, Object opaqueUser) { + ObjectMapper mapper = elide.getMapper().getObjectMapper(); + + JsonNode topLevel; + + try { + topLevel = mapper.readTree(graphQLDocument); + } catch (IOException e) { + log.debug("Invalid json body provided to GraphQL", e); + // NOTE: Can't get at isVerbose setting here for hardcoding to false. If + // necessary, we can refactor + // so this can be set appropriately. + return buildErrorResponse(new InvalidEntityBodyException(graphQLDocument), false); + } + + Function executeRequest = (node) -> executeGraphQLRequest(mapper, + opaqueUser, graphQLDocument, node); + + if (topLevel.isArray()) { + Iterator nodeIterator = topLevel.iterator(); + Iterable nodeIterable = () -> nodeIterator; + // NOTE: Create a non-parallel stream + // It's unclear whether or not the expectations of the caller would be that + // requests are intended + // to run serially even outside of a single transaction. We should revisit this. + Stream nodeStream = StreamSupport.stream(nodeIterable.spliterator(), false); + ArrayNode result = nodeStream.map(executeRequest).map(response -> { + try { + return mapper.readTree(response.getBody()); + } catch (IOException e) { + log.debug("Caught an IO exception while trying to read response body"); + return JsonNodeFactory.instance.objectNode(); + } + }) + .reduce(JsonNodeFactory.instance.arrayNode(), (arrayNode, node) -> arrayNode.add(node), + (left, right) -> left.addAll(right)); + try { + return new ElideResponse(HttpStatus.SC_OK, mapper.writeValueAsString(result)); + } catch (JsonProcessingException e) { + log.error("An unexpected error occurred trying to serialize array response.", e); + throw new RuntimeException(e); // 500 + } + } + + return executeRequest.apply(topLevel); + } + + private ElideResponse executeGraphQLRequest(ObjectMapper mapper, + Object opaqueUser, + String graphQLDocument, + JsonNode jsonDocument) { + boolean isVerbose = false; + try (DataStoreTransaction tx = elide.getDataStore().beginTransaction()) { + final User user = tx.accessUser(opaqueUser); + GraphQLRequestScope requestScope = new GraphQLRequestScope(tx, user, + elide.getElideSettings()); + isVerbose = requestScope.getPermissionExecutor().isVerbose(); + + if (!jsonDocument.has(QUERY)) { + return new ElideResponse(HttpStatus.SC_BAD_REQUEST, "A `query` key is required."); + } + + String query = jsonDocument.get(QUERY).asText(); + + // Logging all queries. It is recommended to put any private information that + // shouldn't be logged into + // the "variables" section of your query. Variable values are not logged. + log.info("Processing GraphQL query:\n{}", query); + + ExecutionInput.Builder executionInput = new ExecutionInput.Builder().context(requestScope) + .query(query); + + if (jsonDocument.has(OPERATION_NAME) && !jsonDocument.get(OPERATION_NAME).isNull()) { + executionInput.operationName(jsonDocument.get(OPERATION_NAME).asText()); + } + + if (jsonDocument.has(VARIABLES) && !jsonDocument.get(VARIABLES).isNull()) { + Map variables = mapper.convertValue(jsonDocument.get(VARIABLES), + Map.class); + executionInput.variables(variables); + } + + ExecutionResult result = api.execute(executionInput); + + tx.preCommit(); + requestScope.runQueuedPreSecurityTriggers(); + requestScope.getPermissionExecutor().executeCommitChecks(); + if (query.trim().startsWith(MUTATION)) { + if (!result.getErrors().isEmpty()) { + HashMap abortedResponseObject = new HashMap<>(); + abortedResponseObject.put("errors", result.getErrors()); + abortedResponseObject.put("data", null); + + // Do not commit. Throw OK response to process tx.close correctly. + + throw new GraphQLErrorException(200, mapper.writeValueAsString(abortedResponseObject)); + } + requestScope.saveOrCreateObjects(); + } + tx.flush(requestScope); + + requestScope.runQueuedPreCommitTriggers(); + elide.getAuditLogger().commit(requestScope); + tx.commit(requestScope); + requestScope.runQueuedPostCommitTriggers(); + + if (log.isTraceEnabled()) { + requestScope.getPermissionExecutor().printCheckStats(); + } + + return new ElideResponse(HttpStatus.SC_OK, + mapper.writeValueAsString(result.toSpecification())); + } catch (WebApplicationException e) { + log.debug("WebApplicationException", e); + throw e; + } catch (GraphQLErrorException e) { + log.debug("GraphQLErrorException", e); + return e.getResponse(); + } catch (JsonProcessingException e) { + log.debug("Invalid json body provided to GraphQL", e); + return buildErrorResponse(new InvalidEntityBodyException(graphQLDocument), isVerbose); + } catch (IOException e) { + log.error("Uncaught IO Exception by Elide in GraphQL", e); + return buildErrorResponse(new TransactionException(e), isVerbose); + } catch (HttpStatusException e) { + log.debug("Caught HTTP status exception {}", e.getStatus(), e); + return buildErrorResponse(new HttpStatusException(200, "") { + private static final long serialVersionUID = 1L; + + @Override + public int getStatus() { + return 200; + } + + @Override + public Pair getErrorResponse() { + return e.getErrorResponse(); + } + + @Override + public Pair getVerboseErrorResponse() { + return e.getVerboseErrorResponse(); + } + + @Override + public String getVerboseMessage() { + return e.getVerboseMessage(); + } + + @Override + public String toString() { + return e.toString(); + } + }, isVerbose); + } catch (Exception | Error e) { + log.debug("Unhandled error or exception.", e); + throw e; + } finally { + elide.getAuditLogger().clear(); + } + } + + private ElideResponse buildErrorResponse(HttpStatusException error, boolean isVerbose) { + ObjectMapper mapper = elide.getMapper().getObjectMapper(); + JsonNode errorNode; + if (!(error instanceof CustomErrorException) && elideSettings.isReturnErrorObjects()) { + ErrorObjects errors = ErrorObjects.builder() + .addError() + .with("message", isVerbose ? error.getVerboseMessage() : error.toString()) + .build(); + errorNode = mapper.convertValue(errors, JsonNode.class); + } else { + errorNode = isVerbose ? error.getVerboseErrorResponse().getRight() + : error.getErrorResponse().getRight(); + } + String errorBody; + try { + errorBody = mapper.writeValueAsString(errorNode); + } catch (JsonProcessingException e) { + errorBody = errorNode.toString(); + } + return new ElideResponse(error.getStatus(), errorBody); + } }