简体   繁体   中英

HashMap in Java GraphQL schema

I'm trying to create a GraphQL Spring Boot application to create a GraphQL layer on top of an existing REST Web API and I'm having trouble handling a HashMap in the schema.

The Timeline class has a field called dataReference which is a HashMap. I tried defining it in the graphql schema as a list of type DataReference which is a key/value pair, but I'm getting the following error:

Exception in thread "main" org.springframework.context.ApplicationContextException: Unable to start web server; nested exception is org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:155)
    at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:540)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140)
    at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:775)
    at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:397)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:316)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1260)
    at org.springframework.boot.SpringApplication.run(SpringApplication.java:1248)
    at com.foo.bar.graphql.AppLauncher.main(AppLauncher.java:43)
Caused by: org.springframework.boot.web.server.WebServerException: Unable to start embedded Tomcat
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:125)
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.<init>(TomcatWebServer.java:86)
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getTomcatWebServer(TomcatServletWebServerFactory.java:414)
    at org.springframework.boot.web.embedded.tomcat.TomcatServletWebServerFactory.getWebServer(TomcatServletWebServerFactory.java:174)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.createWebServer(ServletWebServerApplicationContext.java:179)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.onRefresh(ServletWebServerApplicationContext.java:152)
    ... 8 more
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'servletRegistrationBean' defined in com.foo.bar.graphql.AppLauncher: Bean instantiation via factory method failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.ServletRegistrationBean]: Factory method 'servletRegistrationBean' threw exception; nested exception is com.coxautodev.graphql.tools.SchemaClassScannerError: Unable to match type definition (ListType{type=TypeName{name='DataReference'}}) with java type (java.util.Map<java.lang.String, java.lang.String>): Java class is not a List or generic type information was lost: java.util.Map<java.lang.String, java.lang.String>
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:625)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiateUsingFactoryMethod(ConstructorResolver.java:455)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.instantiateUsingFactoryMethod(AbstractAutowireCapableBeanFactory.java:1288)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBeanInstance(AbstractAutowireCapableBeanFactory.java:1127)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:538)
    at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:498)
    at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:320)
    at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:222)
    at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:318)
    at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:204)
    at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:236)
    at org.springframework.boot.web.servlet.ServletContextInitializerBeans.getOrderedBeansOfType(ServletContextInitializerBeans.java:224)
    at org.springframework.boot.web.servlet.ServletContextInitializerBeans.addServletContextInitializerBeans(ServletContextInitializerBeans.java:100)
    at org.springframework.boot.web.servlet.ServletContextInitializerBeans.<init>(ServletContextInitializerBeans.java:88)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.getServletContextInitializerBeans(ServletWebServerApplicationContext.java:250)
    at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.selfInitialize(ServletWebServerApplicationContext.java:237)
    at org.springframework.boot.web.embedded.tomcat.TomcatStarter.onStartup(TomcatStarter.java:54)
    at org.apache.catalina.core.StandardContext.startInternal(StandardContext.java:5098)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1429)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1419)
    at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
    at java.util.concurrent.FutureTask.run(FutureTask.java)
    at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
    at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:944)
    at org.apache.catalina.core.StandardHost.startInternal(StandardHost.java:839)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1429)
    at org.apache.catalina.core.ContainerBase$StartChild.call(ContainerBase.java:1419)
    at java.util.concurrent.FutureTask.run$$$capture(FutureTask.java:266)
    at java.util.concurrent.FutureTask.run(FutureTask.java)
    at org.apache.tomcat.util.threads.InlineExecutorService.execute(InlineExecutorService.java:75)
    at java.util.concurrent.AbstractExecutorService.submit(AbstractExecutorService.java:134)
    at org.apache.catalina.core.ContainerBase.startInternal(ContainerBase.java:944)
    at org.apache.catalina.core.StandardEngine.startInternal(StandardEngine.java:261)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.StandardService.startInternal(StandardService.java:422)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.core.StandardServer.startInternal(StandardServer.java:770)
    at org.apache.catalina.util.LifecycleBase.start(LifecycleBase.java:183)
    at org.apache.catalina.startup.Tomcat.start(Tomcat.java:370)
    at org.springframework.boot.web.embedded.tomcat.TomcatWebServer.initialize(TomcatWebServer.java:106)
    ... 13 more
Caused by: org.springframework.beans.BeanInstantiationException: Failed to instantiate [org.springframework.boot.web.servlet.ServletRegistrationBean]: Factory method 'servletRegistrationBean' threw exception; nested exception is com.coxautodev.graphql.tools.SchemaClassScannerError: Unable to match type definition (ListType{type=TypeName{name='DataReference'}}) with java type (java.util.Map<java.lang.String, java.lang.String>): Java class is not a List or generic type information was lost: java.util.Map<java.lang.String, java.lang.String>
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:185)
    at org.springframework.beans.factory.support.ConstructorResolver.instantiate(ConstructorResolver.java:620)
    ... 55 more
Caused by: com.coxautodev.graphql.tools.SchemaClassScannerError: Unable to match type definition (ListType{type=TypeName{name='DataReference'}}) with java type (java.util.Map<java.lang.String, java.lang.String>): Java class is not a List or generic type information was lost: java.util.Map<java.lang.String, java.lang.String>
    at com.coxautodev.graphql.tools.TypeClassMatcher.error(TypeClassMatcher.kt:22)
    at com.coxautodev.graphql.tools.TypeClassMatcher.match(TypeClassMatcher.kt:65)
    at com.coxautodev.graphql.tools.TypeClassMatcher.match(TypeClassMatcher.kt:28)
    at com.coxautodev.graphql.tools.SchemaClassScanner.scanResolverInfoForPotentialMatches(SchemaClassScanner.kt:215)
    at com.coxautodev.graphql.tools.SchemaClassScanner.scanQueueItemForPotentialMatches(SchemaClassScanner.kt:206)
    at com.coxautodev.graphql.tools.SchemaClassScanner.scanQueue(SchemaClassScanner.kt:103)
    at com.coxautodev.graphql.tools.SchemaClassScanner.scanForClasses(SchemaClassScanner.kt:81)
    at com.coxautodev.graphql.tools.SchemaParserBuilder.scan(SchemaParserBuilder.kt:149)
    at com.coxautodev.graphql.tools.SchemaParserBuilder.build(SchemaParserBuilder.kt:155)
    at com.foo.bar.graphql.AppLauncher.servletRegistrationBean(AppLauncher.java:55)
    at com.foo.bar.graphql.AppLauncher$$EnhancerBySpringCGLIB$$1ac15725.CGLIB$servletRegistrationBean$0(<generated>)
    at com.foo.bar.graphql.AppLauncher$$EnhancerBySpringCGLIB$$1ac15725$$FastClassBySpringCGLIB$$c7dabbfc.invoke(<generated>)
    at org.springframework.cglib.proxy.MethodProxy.invokeSuper(MethodProxy.java:244)
    at org.springframework.context.annotation.ConfigurationClassEnhancer$BeanMethodInterceptor.intercept(ConfigurationClassEnhancer.java:363)
    at com.foo.bar.graphql.AppLauncher$$EnhancerBySpringCGLIB$$1ac15725.servletRegistrationBean(<generated>)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at org.springframework.beans.factory.support.SimpleInstantiationStrategy.instantiate(SimpleInstantiationStrategy.java:154)
    ... 56 more

This is my schema file:

extend type Query {
    getFoo: Timeline
}

type Timeline {
    dataReference: [DataReference]
}

type DataReference {
    key: String
    value: String
}

And my model class:

package com.foo.bar.graphql.model;

import lombok.Getter;
import lombok.Setter;

import java.util.HashMap;
import java.util.Map;

@Getter @Setter
public class Timeline {
  private Map<String, String> dataReference = new HashMap<>();
}

I found this thread Return HashMap<String, Object> from GraphQL-Java regarding issues with defining Maps in the schema. In the accepted answer, Option 1) is not viable for me since the models are taken from the existing Web API and connot be changed. My attempt at Option 2 is listed above and is not working. I also tried Option 3 (using a scalar) with the following:

Schema:

extend type Query {
    getFoo: Timeline
}

scalar Object

type Timeline {
    dataReference: Object
}

Servlet Registration:

@Bean
    public ServletRegistrationBean servletRegistrationBean() {

        GraphQLSchema schema  = SchemaParser.newParser()
                        .resolvers(fooResolver)
                        .file("graphql/foo.graphqls")
                        .scalars(ExtendedScalars.Object)
                        .build()
                        .makeExecutableSchema();
        ExecutionStrategy executionStrategy = new AsyncExecutionStrategy();
        GraphQLServlet servlet = new SimpleGraphQLServlet(schema, executionStrategy);
        ServletRegistrationBean bean = new ServletRegistrationBean(servlet, "/graphql");
        return bean;
    }

pom.xml:

<dependency>
  <groupId>com.graphql-java</groupId>
  <artifactId>graphql-java-extended-scalars</artifactId>
  <version>1.0</version>
</dependency>

But I get this error: Caused by: com.coxautodev.graphql.tools.SchemaClassScannerError: Expected a user-defined GraphQL scalar type with name 'Object' but found none!

I would prefer the method of having the key/value pair in the schema if someone could please help me get that working.

The simplest way is using a DTO. Using a DTO mapper like mapstruct for example, will allow you to automatically map all fields except the Map field where you will apply the transformation.

You create a DTO like this :

@Data
@NoArgsConstructor
@AllArgsConstructor
public class TimelineDTO {

private List<DataReference> dataReference = new ArrayList();
}

When you get the TimeLine object, map it to TimelineDTO and operate the transformations from Map to List.

List<DataReference> dataReferences = new ArrayList<>();
for (Map.Entry<String,String> entry : super.getDataReference().entrySet()) {

    dataReferences.add(new DataReference(entry.getKey(), entry.getValue()));

}

return dataReferences;

In your GraphQL contract , you return the DTO instead :

 extend type Query {
    getFoo: TimelineDTO
}

type TimelineDTO {
    dataReference: [DataReference]
}

type DataReference {
    key: String
    value: String
}

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM