diff --git a/elide-async/src/main/java/com/yahoo/elide/async/service/AsyncExecutorService.java b/elide-async/src/main/java/com/yahoo/elide/async/service/AsyncExecutorService.java index 000bcad354..019f64c5dd 100644 --- a/elide-async/src/main/java/com/yahoo/elide/async/service/AsyncExecutorService.java +++ b/elide-async/src/main/java/com/yahoo/elide/async/service/AsyncExecutorService.java @@ -14,6 +14,7 @@ import com.yahoo.elide.core.security.User; import com.yahoo.elide.graphql.QueryRunner; +import graphql.execution.DataFetcherExceptionHandler; import graphql.execution.SimpleDataFetcherExceptionHandler; import jakarta.inject.Inject; import lombok.Data; @@ -23,6 +24,7 @@ import java.util.Date; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.ExecutorService; @@ -59,16 +61,14 @@ private static class AsyncAPIResultFuture { } @Inject - public AsyncExecutorService(Elide elide, ExecutorService executor, ExecutorService updater, - AsyncAPIDAO asyncAPIDao) { + public AsyncExecutorService(Elide elide, ExecutorService executor, ExecutorService updater, AsyncAPIDAO asyncAPIDao, + Optional optionalDataFetcherExceptionHandler) { this.elide = elide; runners = new HashMap<>(); for (String apiVersion : elide.getElideSettings().getDictionary().getApiVersions()) { - - //Because the queries are async, there is no need to override the default exception handler to customize - //the error response. - runners.put(apiVersion, new QueryRunner(elide, apiVersion, new SimpleDataFetcherExceptionHandler())); + runners.put(apiVersion, new QueryRunner(elide, apiVersion, + optionalDataFetcherExceptionHandler.orElseGet(SimpleDataFetcherExceptionHandler::new))); } this.executor = executor; diff --git a/elide-async/src/test/java/com/yahoo/elide/async/service/AsyncExecutorServiceTest.java b/elide-async/src/test/java/com/yahoo/elide/async/service/AsyncExecutorServiceTest.java index c719ed0d61..69d36adf4e 100644 --- a/elide-async/src/test/java/com/yahoo/elide/async/service/AsyncExecutorServiceTest.java +++ b/elide-async/src/test/java/com/yahoo/elide/async/service/AsyncExecutorServiceTest.java @@ -9,6 +9,8 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -30,13 +32,19 @@ import com.yahoo.elide.core.security.User; import com.yahoo.elide.core.security.checks.Check; import com.yahoo.elide.core.utils.DefaultClassScanner; + import org.apache.http.NoHttpResponseException; import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestInstance; +import graphql.execution.DataFetcherExceptionHandler; +import graphql.execution.SimpleDataFetcherExceptionHandler; + import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.Callable; import java.util.concurrent.Executors; @@ -49,6 +57,12 @@ public class AsyncExecutorServiceTest { private User testUser; private RequestScope scope; private ResultStorageEngine resultStorageEngine; + private DataFetcherExceptionHandler dataFetcherExceptionHandler = spy(new SimpleDataFetcherExceptionHandler()); + + @BeforeEach + void resetMocks() { + reset(dataFetcherExceptionHandler); + } @BeforeAll public void setupMockElide() { @@ -66,7 +80,7 @@ public void setupMockElide() { scope = mock(RequestScope.class); resultStorageEngine = mock(FileResultStorageEngine.class); service = new AsyncExecutorService(elide, Executors.newFixedThreadPool(5), Executors.newFixedThreadPool(5), - asyncAPIDao); + asyncAPIDao, Optional.of(dataFetcherExceptionHandler)); } diff --git a/elide-datastore/elide-datastore-jms/src/main/java/com/yahoo/elide/datastores/jms/websocket/SubscriptionWebSocketConfigurator.java b/elide-datastore/elide-datastore-jms/src/main/java/com/yahoo/elide/datastores/jms/websocket/SubscriptionWebSocketConfigurator.java index 2fa1c1ab18..5384143807 100644 --- a/elide-datastore/elide-datastore-jms/src/main/java/com/yahoo/elide/datastores/jms/websocket/SubscriptionWebSocketConfigurator.java +++ b/elide-datastore/elide-datastore-jms/src/main/java/com/yahoo/elide/datastores/jms/websocket/SubscriptionWebSocketConfigurator.java @@ -26,6 +26,8 @@ import graphql.ExecutionResult; import graphql.GraphQLError; +import graphql.execution.DataFetcherExceptionHandler; +import graphql.execution.SimpleDataFetcherExceptionHandler; import jakarta.jms.ConnectionFactory; import jakarta.websocket.server.ServerEndpointConfig; import lombok.AccessLevel; @@ -73,6 +75,9 @@ public class SubscriptionWebSocketConfigurator extends ServerEndpointConfig.Conf @Builder.Default protected boolean sendPingOnSubscribe = false; + @Builder.Default + protected DataFetcherExceptionHandler dataFetcherExceptionHandler = new SimpleDataFetcherExceptionHandler(); + @Override public T getEndpointInstance(Class endpointClass) throws InstantiationException { if (endpointClass.equals(SubscriptionWebSocket.class)) { @@ -134,6 +139,7 @@ protected SubscriptionWebSocket buildWebSocket(Elide elide) { .userFactory(userFactory) .sendPingOnSubscribe(sendPingOnSubscribe) .verboseErrors(verboseErrors) + .dataFetcherExceptionHandler(dataFetcherExceptionHandler) .build(); } } diff --git a/elide-graphql/pom.xml b/elide-graphql/pom.xml index 646792e577..1438be7c8d 100644 --- a/elide-graphql/pom.xml +++ b/elide-graphql/pom.xml @@ -56,15 +56,17 @@ jackson-databind provided + + com.apollographql.federation + federation-graphql-java-support + com.graphql-java graphql-java - 19.2 com.graphql-java graphql-java-extended-scalars - 20.0 jakarta.websocket @@ -131,11 +133,6 @@ jakarta.persistence-api test - - com.apollographql.federation - federation-graphql-java-support - 2.3.1 - org.glassfish.jersey.containers jersey-container-servlet diff --git a/elide-graphql/src/main/java/com/yahoo/elide/graphql/GraphQLEndpoint.java b/elide-graphql/src/main/java/com/yahoo/elide/graphql/GraphQLEndpoint.java index 873ef1c988..7f111a64b5 100644 --- a/elide-graphql/src/main/java/com/yahoo/elide/graphql/GraphQLEndpoint.java +++ b/elide-graphql/src/main/java/com/yahoo/elide/graphql/GraphQLEndpoint.java @@ -16,6 +16,7 @@ import com.yahoo.elide.utils.ResourceUtils; import org.apache.commons.lang3.StringUtils; +import graphql.execution.DataFetcherExceptionHandler; import graphql.execution.SimpleDataFetcherExceptionHandler; import jakarta.inject.Inject; import jakarta.inject.Named; @@ -35,6 +36,7 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Optional; import java.util.UUID; /** @@ -50,13 +52,15 @@ public class GraphQLEndpoint { private final HeaderUtils.HeaderProcessor headerProcessor; @Inject - public GraphQLEndpoint(@Named("elide") Elide elide) { + public GraphQLEndpoint(@Named("elide") Elide elide, + Optional optionalDataFetcherExceptionHandler) { log.debug("Started ~~"); this.elide = elide; this.headerProcessor = elide.getElideSettings().getHeaderProcessor(); this.runners = new HashMap<>(); for (String apiVersion : elide.getElideSettings().getDictionary().getApiVersions()) { - runners.put(apiVersion, new QueryRunner(elide, apiVersion, new SimpleDataFetcherExceptionHandler())); + runners.put(apiVersion, new QueryRunner(elide, apiVersion, + optionalDataFetcherExceptionHandler.orElseGet(SimpleDataFetcherExceptionHandler::new))); } } diff --git a/elide-graphql/src/main/java/com/yahoo/elide/graphql/QueryRunner.java b/elide-graphql/src/main/java/com/yahoo/elide/graphql/QueryRunner.java index 157c8069fe..d168708ab7 100644 --- a/elide-graphql/src/main/java/com/yahoo/elide/graphql/QueryRunner.java +++ b/elide-graphql/src/main/java/com/yahoo/elide/graphql/QueryRunner.java @@ -100,6 +100,7 @@ public QueryRunner(Elide elide, String apiVersion, DataFetcherExceptionHandler e nonEntityDictionary, elide.getElideSettings(), fetcher, apiVersion); api = GraphQL.newGraphQL(builder.build()) + .defaultDataFetcherExceptionHandler(exceptionHandler) .queryExecutionStrategy(new AsyncSerialExecutionStrategy(exceptionHandler)) .build(); diff --git a/elide-graphql/src/main/java/com/yahoo/elide/graphql/subscriptions/websocket/SubscriptionWebSocket.java b/elide-graphql/src/main/java/com/yahoo/elide/graphql/subscriptions/websocket/SubscriptionWebSocket.java index f38c3a6e86..61c44b3313 100644 --- a/elide-graphql/src/main/java/com/yahoo/elide/graphql/subscriptions/websocket/SubscriptionWebSocket.java +++ b/elide-graphql/src/main/java/com/yahoo/elide/graphql/subscriptions/websocket/SubscriptionWebSocket.java @@ -20,6 +20,8 @@ import graphql.GraphQL; import graphql.execution.AsyncSerialExecutionStrategy; +import graphql.execution.DataFetcherExceptionHandler; +import graphql.execution.SimpleDataFetcherExceptionHandler; import graphql.execution.SubscriptionExecutionStrategy; import jakarta.websocket.OnClose; import jakarta.websocket.OnError; @@ -70,6 +72,9 @@ public class SubscriptionWebSocket { @Builder.Default private boolean verboseErrors = false; + @Builder.Default + private DataFetcherExceptionHandler dataFetcherExceptionHandler = new SimpleDataFetcherExceptionHandler(); + private final Map apis = new HashMap<>(); private final ConcurrentMap openSessions = new ConcurrentHashMap<>(); @@ -106,7 +111,8 @@ protected SubscriptionWebSocket( long maxIdleTimeoutMs, int maxMessageSize, boolean sendPingOnSubscribe, - boolean verboseErrors + boolean verboseErrors, + DataFetcherExceptionHandler dataFetcherExceptionHandler ) { this.elide = elide; this.executorService = executorService; @@ -117,6 +123,7 @@ protected SubscriptionWebSocket( this.maxIdleTimeoutMs = maxIdleTimeoutMs; this.maxMessageSize = maxMessageSize; this.verboseErrors = verboseErrors; + this.dataFetcherExceptionHandler = dataFetcherExceptionHandler; EntityDictionary dictionary = elide.getElideSettings().getDictionary(); for (String apiVersion : dictionary.getApiVersions()) { @@ -127,8 +134,9 @@ protected SubscriptionWebSocket( new SubscriptionDataFetcher(nonEntityDictionary), NO_VERSION); GraphQL api = GraphQL.newGraphQL(builder.build()) - .queryExecutionStrategy(new AsyncSerialExecutionStrategy()) - .subscriptionExecutionStrategy(new SubscriptionExecutionStrategy()) + .defaultDataFetcherExceptionHandler(this.dataFetcherExceptionHandler) + .queryExecutionStrategy(new AsyncSerialExecutionStrategy(this.dataFetcherExceptionHandler)) + .subscriptionExecutionStrategy(new SubscriptionExecutionStrategy(this.dataFetcherExceptionHandler)) .build(); apis.put(apiVersion, api); diff --git a/elide-graphql/src/test/java/com/yahoo/elide/graphql/FetcherFetchTest.java b/elide-graphql/src/test/java/com/yahoo/elide/graphql/FetcherFetchTest.java index abaffd245c..009ce4e12b 100644 --- a/elide-graphql/src/test/java/com/yahoo/elide/graphql/FetcherFetchTest.java +++ b/elide-graphql/src/test/java/com/yahoo/elide/graphql/FetcherFetchTest.java @@ -7,6 +7,8 @@ package com.yahoo.elide.graphql; import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; import com.yahoo.elide.ElideResponse; import org.junit.jupiter.api.Test; @@ -37,6 +39,7 @@ public void testMutationInQueryThrowsError() throws Exception { + "}"; assertQueryFailsWith(query, "Exception while fetching data (/book) : Data model writes are only allowed in mutations"); + verify(dataFetcherExceptionHandler).handleException(any()); } @Test diff --git a/elide-graphql/src/test/java/com/yahoo/elide/graphql/FetcherUpdateTest.java b/elide-graphql/src/test/java/com/yahoo/elide/graphql/FetcherUpdateTest.java index e6651c9040..7d1acd0888 100644 --- a/elide-graphql/src/test/java/com/yahoo/elide/graphql/FetcherUpdateTest.java +++ b/elide-graphql/src/test/java/com/yahoo/elide/graphql/FetcherUpdateTest.java @@ -5,6 +5,9 @@ */ package com.yahoo.elide.graphql; +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; + import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; @@ -48,6 +51,7 @@ public void testRootCollectionInvalidIds() throws Exception { // Update 1, create for id 42, create new book with title "abc" String expectedMessage = "Exception while fetching data (/book) : Unknown identifier [42] for book"; runErrorComparisonTest("rootCollectionInvalidIds", expectedMessage); + verify(dataFetcherExceptionHandler).handleException(any()); } @Test @@ -55,6 +59,7 @@ public void testRootCollectionMissingIds() throws Exception { // Update 1, create for id 42, create new book with title "abc" String expectedMessage = "Exception while fetching data (/book) : UPDATE data objects must include ids"; runErrorComparisonTest("rootCollectionMissingIds", expectedMessage); + verify(dataFetcherExceptionHandler).handleException(any()); } @Test diff --git a/elide-graphql/src/test/java/com/yahoo/elide/graphql/GraphQLEndpointTest.java b/elide-graphql/src/test/java/com/yahoo/elide/graphql/GraphQLEndpointTest.java index 58e1cb7de8..298bae5fb0 100644 --- a/elide-graphql/src/test/java/com/yahoo/elide/graphql/GraphQLEndpointTest.java +++ b/elide-graphql/src/test/java/com/yahoo/elide/graphql/GraphQLEndpointTest.java @@ -20,6 +20,7 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.reset; import static org.mockito.Mockito.spy; import static org.mockito.Mockito.verify; @@ -31,6 +32,7 @@ import com.yahoo.elide.core.dictionary.EntityDictionary; import com.yahoo.elide.core.security.checks.Check; import com.yahoo.elide.core.utils.DefaultClassScanner; + import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.fasterxml.jackson.databind.node.JsonNodeFactory; @@ -44,6 +46,9 @@ import org.mockito.Mockito; import org.skyscreamer.jsonassert.JSONAssert; +import graphql.execution.DataFetcherExceptionHandler; +import graphql.execution.SimpleDataFetcherExceptionHandler; + import graphqlEndpointTestModels.Author; import graphqlEndpointTestModels.Book; import graphqlEndpointTestModels.DisallowTransfer; @@ -64,6 +69,7 @@ import java.util.HashMap; import java.util.Iterator; import java.util.Map; +import java.util.Optional; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -80,6 +86,7 @@ public class GraphQLEndpointTest { private final AuditLogger audit = Mockito.mock(AuditLogger.class); private final UriInfo uriInfo = Mockito.mock(UriInfo.class); private final HttpHeaders requestHeaders = Mockito.mock(HttpHeaders.class); + private final DataFetcherExceptionHandler dataFetcherExceptionHandler = Mockito.spy(new SimpleDataFetcherExceptionHandler()); private Elide elide; @@ -140,7 +147,7 @@ public void setupTest() throws Exception { ); elide.doScans(); - endpoint = new GraphQLEndpoint(elide); + endpoint = new GraphQLEndpoint(elide, Optional.of(dataFetcherExceptionHandler)); DataStoreTransaction tx = inMemoryStore.beginTransaction(); @@ -185,6 +192,8 @@ public void setupTest() throws Exception { tx.save(noShare, null); tx.commit(null); + + reset(dataFetcherExceptionHandler); } @Test @@ -414,6 +423,7 @@ void testFailedMutationAndRead() throws IOException, JSONException { Response response = endpoint.post(uriInfo, requestHeaders, user2, graphQLRequestToJSON(graphQLRequest)); assertHasErrors(response); + verify(dataFetcherExceptionHandler).handleException(any()); graphQLRequest = document( selection( diff --git a/elide-graphql/src/test/java/com/yahoo/elide/graphql/PersistentResourceFetcherTest.java b/elide-graphql/src/test/java/com/yahoo/elide/graphql/PersistentResourceFetcherTest.java index 0a2e0fb4b3..90d729b793 100644 --- a/elide-graphql/src/test/java/com/yahoo/elide/graphql/PersistentResourceFetcherTest.java +++ b/elide-graphql/src/test/java/com/yahoo/elide/graphql/PersistentResourceFetcherTest.java @@ -11,6 +11,8 @@ import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import com.yahoo.elide.Elide; import com.yahoo.elide.ElideResponse; @@ -42,6 +44,7 @@ import graphql.GraphQL; import graphql.GraphQLError; +import graphql.execution.DataFetcherExceptionHandler; import graphql.execution.SimpleDataFetcherExceptionHandler; import java.io.IOException; @@ -74,6 +77,8 @@ public abstract class PersistentResourceFetcherTest extends GraphQLTest { protected HashMapDataStore hashMapDataStore; protected ElideSettings settings; + protected DataFetcherExceptionHandler dataFetcherExceptionHandler = spy(new SimpleDataFetcherExceptionHandler()); + @BeforeAll public void initializeQueryRunner() { RSQLFilterDialect filterDialect = RSQLFilterDialect.builder().dictionary(dictionary).build(); @@ -94,7 +99,7 @@ public void initializeQueryRunner() { Elide elide = new Elide(settings); elide.doScans(); - runner = new QueryRunner(elide, NO_VERSION, new SimpleDataFetcherExceptionHandler()); + runner = new QueryRunner(elide, NO_VERSION, dataFetcherExceptionHandler); } protected void initializeMocks() { @@ -183,6 +188,8 @@ public void initTestData() { tx.save(publisher1, null); tx.save(publisher2, null); tx.commit(null); + + reset(dataFetcherExceptionHandler); } protected void assertQueryEquals(String graphQLRequest, String expectedResponse) throws Exception { diff --git a/elide-graphql/src/test/java/com/yahoo/elide/graphql/subscriptions/SubscriptionWebSocketTest.java b/elide-graphql/src/test/java/com/yahoo/elide/graphql/subscriptions/SubscriptionWebSocketTest.java index 9cf933a600..4bf0d144a0 100644 --- a/elide-graphql/src/test/java/com/yahoo/elide/graphql/subscriptions/SubscriptionWebSocketTest.java +++ b/elide-graphql/src/test/java/com/yahoo/elide/graphql/subscriptions/SubscriptionWebSocketTest.java @@ -15,6 +15,7 @@ import static org.mockito.Mockito.mock; import static org.mockito.Mockito.never; import static org.mockito.Mockito.reset; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.timeout; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; @@ -52,6 +53,8 @@ import graphql.ExecutionResult; import graphql.GraphQLError; +import graphql.execution.DataFetcherExceptionHandler; +import graphql.execution.SimpleDataFetcherExceptionHandler; import jakarta.websocket.CloseReason; import jakarta.websocket.RemoteEndpoint; import jakarta.websocket.Session; @@ -78,6 +81,7 @@ public class SubscriptionWebSocketTest extends GraphQLTest { protected RemoteEndpoint.Async remote; protected Elide elide; protected ExecutorService executorService = MoreExecutors.newDirectExecutorService(); + protected DataFetcherExceptionHandler dataFetcherExceptionHandler = spy(new SimpleDataFetcherExceptionHandler()); public SubscriptionWebSocketTest() { RSQLFilterDialect filterDialect = RSQLFilterDialect.builder().dictionary(dictionary).build(); @@ -122,6 +126,7 @@ public void resetMocks() throws Exception { reset(dataStore); reset(dataStoreTransaction); reset(session); + reset(dataFetcherExceptionHandler); when(session.getRequestURI()).thenReturn(new URI("http://localhost:1234/subscription")); when(session.getAsyncRemote()).thenReturn(remote); when(dataStore.beginTransaction()).thenReturn(dataStoreTransaction); @@ -306,7 +311,9 @@ void testSubscribeUnsubscribeSubscribe() throws IOException { void testErrorInStream() throws IOException { SubscriptionWebSocket endpoint = SubscriptionWebSocket.builder() .executorService(executorService) - .elide(elide).build(); + .elide(elide) + .dataFetcherExceptionHandler(dataFetcherExceptionHandler) + .build(); Book book1 = new Book(); book1.setTitle("Book 1"); @@ -344,6 +351,7 @@ void testErrorInStream() throws IOException { ArgumentCaptor message = ArgumentCaptor.forClass(String.class); verify(remote, times(4)).sendText(message.capture()); assertEquals(expected, message.getAllValues()); + verify(dataFetcherExceptionHandler, times(2)).handleException(any()); } @Test diff --git a/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/framework/AsyncIntegrationTestApplicationResourceConfig.java b/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/framework/AsyncIntegrationTestApplicationResourceConfig.java index 5e3f2c9776..363fc1c429 100644 --- a/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/framework/AsyncIntegrationTestApplicationResourceConfig.java +++ b/elide-integration-tests/src/test/java/com/yahoo/elide/async/integration/tests/framework/AsyncIntegrationTestApplicationResourceConfig.java @@ -41,10 +41,13 @@ import example.models.triggers.Invoice; import example.models.triggers.InvoiceCompletionHook; import example.models.triggers.services.BillingService; + import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.jersey.internal.inject.AbstractBinder; import org.glassfish.jersey.server.ResourceConfig; +import graphql.execution.SimpleDataFetcherExceptionHandler; + import jakarta.inject.Inject; import jakarta.servlet.ServletContext; import jakarta.ws.rs.core.Context; @@ -55,6 +58,7 @@ import java.util.Collections; import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ExecutorService; public class AsyncIntegrationTestApplicationResourceConfig extends ResourceConfig { @@ -113,7 +117,7 @@ protected void configure() { ExecutorService executorService = (ExecutorService) servletContext.getAttribute(ASYNC_EXECUTOR_ATTR); AsyncExecutorService asyncExecutorService = new AsyncExecutorService(elide, - executorService, executorService, asyncAPIDao); + executorService, executorService, asyncAPIDao, Optional.of(new SimpleDataFetcherExceptionHandler())); // Create ResultStorageEngine Path storageDestination = (Path) servletContext.getAttribute(STORAGE_DESTINATION_ATTR); diff --git a/elide-spring/elide-spring-boot-autoconfigure/src/main/java/com/yahoo/elide/spring/config/ElideAsyncConfiguration.java b/elide-spring/elide-spring-boot-autoconfigure/src/main/java/com/yahoo/elide/spring/config/ElideAsyncConfiguration.java index ad63dcbe2f..ffc439da16 100644 --- a/elide-spring/elide-spring-boot-autoconfigure/src/main/java/com/yahoo/elide/spring/config/ElideAsyncConfiguration.java +++ b/elide-spring/elide-spring-boot-autoconfigure/src/main/java/com/yahoo/elide/spring/config/ElideAsyncConfiguration.java @@ -29,7 +29,7 @@ import com.yahoo.elide.core.dictionary.EntityDictionary; import com.yahoo.elide.core.exceptions.InvalidOperationException; import com.yahoo.elide.core.security.RequestScope; -import org.springframework.beans.factory.annotation.Autowired; + import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -38,8 +38,11 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import graphql.execution.DataFetcherExceptionHandler; + import java.util.HashMap; import java.util.Map; +import java.util.Optional; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; @@ -58,6 +61,8 @@ public class ElideAsyncConfiguration { * @param elide elideObject. * @param settings Elide settings. * @param asyncQueryDao AsyncDao object. + * @param optionalResultStorageEngine Result Storage Engine. + * @param optionalDataFetcherExceptionHandler GraphQL data fetcher exception handler. * @return a AsyncExecutorService. */ @Bean @@ -66,14 +71,15 @@ public AsyncExecutorService buildAsyncExecutorService( RefreshableElide elide, ElideConfigProperties settings, AsyncAPIDAO asyncQueryDao, - @Autowired(required = false) ResultStorageEngine resultStorageEngine + Optional optionalResultStorageEngine, + Optional optionalDataFetcherExceptionHandler ) { AsyncProperties asyncProperties = settings.getAsync(); ExecutorService executor = Executors.newFixedThreadPool(asyncProperties.getThreadPoolSize()); ExecutorService updater = Executors.newFixedThreadPool(asyncProperties.getThreadPoolSize()); AsyncExecutorService asyncExecutorService = new AsyncExecutorService(elide.getElide(), executor, - updater, asyncQueryDao); + updater, asyncQueryDao, optionalDataFetcherExceptionHandler); // Binding AsyncQuery LifeCycleHook AsyncQueryHook asyncQueryHook = new AsyncQueryHook(asyncExecutorService, @@ -97,7 +103,7 @@ public AsyncExecutorService buildAsyncExecutorService( // Binding TableExport LifeCycleHook TableExportHook tableExportHook = getTableExportHook(asyncExecutorService, settings, supportedFormatters, - resultStorageEngine); + optionalResultStorageEngine.orElse(null)); dictionary.bindTrigger(TableExport.class, CREATE, PREFLUSH, tableExportHook, false); dictionary.bindTrigger(TableExport.class, CREATE, POSTCOMMIT, tableExportHook, false); dictionary.bindTrigger(TableExport.class, CREATE, PRESECURITY, tableExportHook, false); diff --git a/elide-spring/elide-spring-boot-autoconfigure/src/main/java/com/yahoo/elide/spring/config/ElideSubscriptionConfiguration.java b/elide-spring/elide-spring-boot-autoconfigure/src/main/java/com/yahoo/elide/spring/config/ElideSubscriptionConfiguration.java index ff6ed3efff..523b034429 100644 --- a/elide-spring/elide-spring-boot-autoconfigure/src/main/java/com/yahoo/elide/spring/config/ElideSubscriptionConfiguration.java +++ b/elide-spring/elide-spring-boot-autoconfigure/src/main/java/com/yahoo/elide/spring/config/ElideSubscriptionConfiguration.java @@ -11,6 +11,7 @@ import com.yahoo.elide.core.exceptions.ErrorMapper; import com.yahoo.elide.datastores.jms.websocket.SubscriptionWebSocketConfigurator; import com.yahoo.elide.graphql.subscriptions.websocket.SubscriptionWebSocket; + import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression; import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; @@ -19,6 +20,8 @@ import org.springframework.context.annotation.Configuration; import org.springframework.web.socket.server.standard.ServerEndpointExporter; +import graphql.execution.DataFetcherExceptionHandler; + import jakarta.jms.ConnectionFactory; import jakarta.websocket.server.ServerEndpointConfig; @@ -36,7 +39,8 @@ ServerEndpointConfig serverEndpointConfig( ElideConfigProperties config, SubscriptionWebSocket.UserFactory userFactory, ConnectionFactory connectionFactory, - ErrorMapper errorMapper + ErrorMapper errorMapper, + DataFetcherExceptionHandler dataFetcherExceptionHandler ) { return ServerEndpointConfig.Builder .create(SubscriptionWebSocket.class, config.getSubscription().getPath()) @@ -52,6 +56,7 @@ ServerEndpointConfig serverEndpointConfig( .auditLogger(new Slf4jLogger()) .verboseErrors(config.isVerboseErrors()) .errorMapper(errorMapper) + .dataFetcherExceptionHandler(dataFetcherExceptionHandler) .build()) .build(); } diff --git a/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideResourceConfig.java b/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideResourceConfig.java index f59568b74c..fc06ec4125 100644 --- a/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideResourceConfig.java +++ b/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideResourceConfig.java @@ -42,12 +42,14 @@ import com.yahoo.elide.modelconfig.DynamicConfiguration; import com.yahoo.elide.standalone.Util; import com.yahoo.elide.swagger.resources.DocEndpoint; + import com.codahale.metrics.MetricRegistry; import com.codahale.metrics.health.HealthCheckRegistry; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.hk2.api.TypeLiteral; import org.glassfish.jersey.internal.inject.AbstractBinder; import org.glassfish.jersey.server.ResourceConfig; +import graphql.execution.DataFetcherExceptionHandler; import jakarta.inject.Inject; import jakarta.persistence.EntityManagerFactory; @@ -158,8 +160,8 @@ protected void bindAsync( ExecutorService executor = (ExecutorService) servletContext.getAttribute(ASYNC_EXECUTOR_ATTR); ExecutorService updater = (ExecutorService) servletContext.getAttribute(ASYNC_UPDATER_ATTR); - AsyncExecutorService asyncExecutorService = - new AsyncExecutorService(elide, executor, updater, asyncAPIDao); + AsyncExecutorService asyncExecutorService = new AsyncExecutorService(elide, executor, updater, asyncAPIDao, + Optional.of(settings.getDataFetcherExceptionHandler())); bind(asyncExecutorService).to(AsyncExecutorService.class); if (asyncProperties.enableExport()) { @@ -229,6 +231,8 @@ protected void configure() { classScanner, settings.getModelPackageName(), asyncProperties.enabled())).to(Set.class).named("elideAllModels"); + bind(settings.getDataFetcherExceptionHandler()).to(DataFetcherExceptionHandler.class) + .named("dataFetcherExceptionHandler"); } }); diff --git a/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideStandaloneSettings.java b/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideStandaloneSettings.java index b441559841..92fde5ca04 100644 --- a/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideStandaloneSettings.java +++ b/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideStandaloneSettings.java @@ -51,12 +51,16 @@ import com.yahoo.elide.modelconfig.validator.DynamicConfigValidator; import com.yahoo.elide.swagger.SwaggerBuilder; import com.yahoo.elide.swagger.resources.DocEndpoint; + import org.apache.commons.lang3.StringUtils; import org.eclipse.jetty.servlet.ServletContextHandler; import org.glassfish.hk2.api.ServiceLocator; import org.glassfish.jersey.server.ResourceConfig; import org.hibernate.Session; +import graphql.execution.DataFetcherExceptionHandler; +import graphql.execution.SimpleDataFetcherExceptionHandler; + import io.swagger.models.Info; import io.swagger.models.Swagger; import jakarta.persistence.EntityManager; @@ -607,4 +611,13 @@ default ErrorMapper getErrorMapper() { default JsonApiMapper getObjectMapper() { return new JsonApiMapper(); } + + /** + * Gets the DataFetcherExceptionHandler for GraphQL. + * + * @return the DataFetcherExceptionHandler for GraphQL + */ + default DataFetcherExceptionHandler getDataFetcherExceptionHandler() { + return new SimpleDataFetcherExceptionHandler(); + } } diff --git a/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideStandaloneSubscriptionSettings.java b/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideStandaloneSubscriptionSettings.java index 32a2c6f44e..5fafc6c818 100644 --- a/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideStandaloneSubscriptionSettings.java +++ b/elide-standalone/src/main/java/com/yahoo/elide/standalone/config/ElideStandaloneSubscriptionSettings.java @@ -160,6 +160,7 @@ default ServerEndpointConfig serverEndpointConfig( .auditLogger(settings.getAuditLogger()) .verboseErrors(settings.verboseErrors()) .errorMapper(settings.getErrorMapper()) + .dataFetcherExceptionHandler(settings.getDataFetcherExceptionHandler()) .build()) .build(); diff --git a/elide-test/pom.xml b/elide-test/pom.xml index e84f2fc1dd..b7992e032c 100644 --- a/elide-test/pom.xml +++ b/elide-test/pom.xml @@ -47,7 +47,6 @@ com.graphql-java graphql-java - 19.2 com.fasterxml.jackson.core diff --git a/pom.xml b/pom.xml index da95315984..f67baec1cf 100644 --- a/pom.xml +++ b/pom.xml @@ -94,6 +94,8 @@ 1.18.24 5.3.0 0.11.0 + 3.0.1 + 20.2 6.1.5.Final 4.3.2 2.8.0 @@ -111,6 +113,21 @@ + + com.apollographql.federation + federation-graphql-java-support + ${federation-graphql-java-support-api} + + + com.graphql-java + graphql-java + ${graphql-java.version} + + + com.graphql-java + graphql-java-extended-scalars + ${graphql-java.version} + org.projectlombok lombok