Netflix/dgs-framework

bug: No qualifying bean of type 'com.fasterxml.jackson.databind.ObjectMapper' available

ps-tunnelsoft opened this issue · 6 comments

Expected behavior

When
DGS Version: 8.5.5
spring-graphql: 1.2.6
graphql-dgs-spring-graphql-starter: 8.5.5
graphql-dgs-subscriptions-websockets-autoconfigure: 8.5.5

Autoconfiguration for graphql-dgs-spring-graphql-starter includes bean for ObjectMapper with qualifier "dgsObjectMapper".

Actual behavior

Caused by: org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'webSocketHandler' defined in class path resource [com/netflix/graphql/dgs/subscriptions/websockets/DgsWebSocketAutoConfig.class]: Unsatisfied dependency expressed through method 'webSocketHandler' parameter 2: No qualifying bean of type 'com.fasterxml.jackson.databind.ObjectMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier("dgsObjectMapper")}
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:795)
	at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:542)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1335)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1165)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:562)
	at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:522)
	at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:326)
	at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:234)
	at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:324)
	at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:200)
	at org.springframework.beans.factory.config.DependencyDescriptor.resolveCandidate(DependencyDescriptor.java:254)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1443)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:904)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:782)
	... 101 common frames omitted
Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: No qualifying bean of type 'com.fasterxml.jackson.databind.ObjectMapper' available: expected at least 1 bean which qualifies as autowire candidate. Dependency annotations: {@org.springframework.beans.factory.annotation.Qualifier("dgsObjectMapper")}
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.raiseNoMatchingBeanFound(DefaultListableBeanFactory.java:1880)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.doResolveDependency(DefaultListableBeanFactory.java:1406)
	at org.springframework.beans.factory.support.DefaultListableBeanFactory.resolveDependency(DefaultListableBeanFactory.java:1353)
	at org.springframework.beans.factory.support.ConstructorResolver.resolveAutowiredArgument(ConstructorResolver.java:904)
	at org.springframework.beans.factory.support.ConstructorResolver.createArgumentArray(ConstructorResolver.java:782)
	... 115 common frames omitted

If I provide my own bean this problem does not exists.

    @Bean(name = "dgsObjectMapper")
    @ConditionalOnMissingBean(name = "dgsObjectMapper")
    public ObjectMapper dgsObjectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.registerModule(new JavaTimeModule());
        return objectMapper;
    }

Steps to reproduce

run dgs-examples-java with graphql-dgs-spring-graphql-starter instead of graphql-dgs-spring-boot-starter.

Thanks for reporting. We'll look into it. The examples do need some updates so will fix those and post an update.

I think, I have a follow up issue. But it may just be connected to my workaround providing my own ObjectMapper to dgs.
If I want to test a simple subscription, I get this errror

ExceptionWebSocketHandlerDecorator - Closing session due to exception for StandardWebSocketSession[id=0abecb5b-e29e-64be-ba95-40e723869dfa, uri=ws://localhost:8080/subscriptions?token=TOKEN] - tryCloseWithError
com.fasterxml.jackson.databind.exc.InvalidDefinitionException: Cannot construct instance of `com.netflix.graphql.types.subscription.websockets.Message$SubscribeMessage` (no Creators, like default constructor, exist): cannot deserialize from Object value (no delegate- or property-based Creator)
 at [Source: (String)"{"id":"ebc576a5-3d3d-48fc-8c59-271bbf62904f","type":"subscribe","payload":{"variables":{},"extensions":{},"operationName":"test","query":"subscription test {\n  test\n}"}}"; line: 1, column: 53]
	at com.fasterxml.jackson.databind.exc.InvalidDefinitionException.from(InvalidDefinitionException.java:67)
	at com.fasterxml.jackson.databind.DeserializationContext.reportBadDefinition(DeserializationContext.java:1915)
	at com.fasterxml.jackson.databind.DatabindContext.reportBadDefinition(DatabindContext.java:414)
	at com.fasterxml.jackson.databind.DeserializationContext.handleMissingInstantiator(DeserializationContext.java:1360)
	at com.fasterxml.jackson.databind.deser.BeanDeserializerBase.deserializeFromObjectUsingNonDefault(BeanDeserializerBase.java:1434)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserializeFromObject(BeanDeserializer.java:352)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer._deserializeOther(BeanDeserializer.java:220)
	at com.fasterxml.jackson.databind.deser.BeanDeserializer.deserialize(BeanDeserializer.java:187)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer._deserializeTypedForId(AsPropertyTypeDeserializer.java:170)
	at com.fasterxml.jackson.databind.jsontype.impl.AsPropertyTypeDeserializer.deserializeTypedFromObject(AsPropertyTypeDeserializer.java:136)
	at com.fasterxml.jackson.databind.deser.AbstractDeserializer.deserializeWithType(AbstractDeserializer.java:263)
	at com.fasterxml.jackson.databind.deser.impl.TypeWrappedDeserializer.deserialize(TypeWrappedDeserializer.java:74)
	at com.fasterxml.jackson.databind.deser.DefaultDeserializationContext.readRootValue(DefaultDeserializationContext.java:323)
	at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:4825)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3772)
	at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:3740)
	at com.netflix.graphql.dgs.subscriptions.websockets.WebsocketGraphQLTransportWSProtocolHandler.handleTextMessage(WebsocketGraphQLTransportWSProtocolHandler.kt:109)
	at com.netflix.graphql.dgs.subscriptions.websockets.DgsWebSocketHandler.handleTextMessage(DgsWebSocketHandler.kt:109)
	at org.springframework.web.socket.handler.AbstractWebSocketHandler.handleMessage(AbstractWebSocketHandler.java:43)
	at org.springframework.web.socket.handler.WebSocketHandlerDecorator.handleMessage(WebSocketHandlerDecorator.java:75)
	at org.springframework.web.socket.handler.LoggingWebSocketHandlerDecorator.handleMessage(LoggingWebSocketHandlerDecorator.java:56)
	at org.springframework.web.socket.handler.ExceptionWebSocketHandlerDecorator.handleMessage(ExceptionWebSocketHandlerDecorator.java:58)
	at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter.handleTextMessage(StandardWebSocketHandlerAdapter.java:113)
	at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:84)
	at org.springframework.web.socket.adapter.standard.StandardWebSocketHandlerAdapter$3.onMessage(StandardWebSocketHandlerAdapter.java:81)
	at org.apache.tomcat.websocket.WsFrameBase.sendMessageText(WsFrameBase.java:390)
	at org.apache.tomcat.websocket.server.WsFrameServer.sendMessageText(WsFrameServer.java:130)
	at org.apache.tomcat.websocket.WsFrameBase.processDataText(WsFrameBase.java:484)
	at org.apache.tomcat.websocket.WsFrameBase.processData(WsFrameBase.java:284)
	at org.apache.tomcat.websocket.WsFrameBase.processInputBuffer(WsFrameBase.java:130)
	at org.apache.tomcat.websocket.server.WsFrameServer.onDataAvailable(WsFrameServer.java:85)
	at org.apache.tomcat.websocket.server.WsFrameServer.doOnDataAvailable(WsFrameServer.java:184)
	at org.apache.tomcat.websocket.server.WsFrameServer.notifyDataAvailable(WsFrameServer.java:164)
	at org.apache.tomcat.websocket.server.WsHttpUpgradeHandler.upgradeDispatch(WsHttpUpgradeHandler.java:152)
	at org.apache.coyote.http11.upgrade.UpgradeProcessorInternal.dispatch(UpgradeProcessorInternal.java:60)
	at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:57)
	at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:896)
	at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1744)
	at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:52)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
	at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
	at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:63)
	at java.base/java.lang.Thread.run(Thread.java:840)

So actually one thing that needs to be called out here is the usage of implementation("com.netflix.graphql.dgs:graphql-dgs-subscriptions-websockets-autoconfigure") that needs to be replaced by implementation("org.springframework.boot:spring-boot-starter-websocket")

With the spring-graphql integration, we rely on spring-graphql's implementation of websocket based subscriptions and therefore the DGS module won't work here. This will eliminate the need to provide a dgsObjectMapper as well.

Similarly, for multipart file uploads we need to explicitly add implementation("name.nkonev.multipart-spring-graphql:multipart-spring-graphql:1.1.4 instead of relying on the framework's implementation.

I will update our docs to reflect the websocket part.

ok thanks. I will wait for the documentation update.

Replacing implementation("com.netflix.graphql.dgs:graphql-dgs-subscriptions-websockets-autoconfigure") with implementation("org.springframework.boot:spring-boot-starter-websocket") does not work for me. Maybe I am just missing some configuration now

WARN  o.s.web.servlet.PageNotFound        - No mapping for GET /subscriptions - noHandlerFound
WARN  o.s.web.servlet.PageNotFound        - No endpoint GET /subscriptions. - handleNoHandlerFoundException

Yes it does as documented here: https://github.com/spring-projects/spring-graphql/blob/1.0.x/samples/webflux-websocket/src/main/resources/application.properties#L1
You need to add spring.graphql.websocket.path: /graphql

I will add this to the docs.

Thanks for helping me out. I wasn't familiar with the spring graphql setup process.

The GraphQL WebSocket endpoint is off by default. To enable it:

  • For a Servlet application, add the WebSocket starter spring-boot-starter-websocket
  • For a WebFlux application, no additional dependency is required
  • For both, the spring.graphql.websocket.path application property must be set