Comments (18)
I'll take a look in more detail in the morning (late night here now), but just make sure you're setting the base package on the schema generator (withBasePackage
) to something that contains all the interfaces. Also, are these top level or nested queries? Asking because different ResolverBuilder
s are used in those cases (top level queries by default require annotations while the nested don't).
from graphql-spqr.
I tried to use the withBasePackage
method but it doesn't work, then I used the withResolverBuilders
method to add a PublicResolverBulider
specifying the package where is my interface and that works perfectly.
So then I tried to use the same method (adding another PublicResolverBuilder) to map some other methods from a Spring built-in interface (https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/repository/CrudRepository.html) but I'm having problems with Generics, and I got the following stack trace:
Caused by: io.leangen.geantyref.UnresolvedTypeVariableException: An exact type is requested, but the type contains a type variable that cannot be resolved.
Variable: S from public abstract java.lang.Object org.springframework.data.repository.CrudRepository.save(java.lang.Object)
Hint: This is usually caused by trying to get an exact type when a generic method who's type parameters are not given is involved.
at io.leangen.geantyref.VarMap.map(VarMap.java:93)
at io.leangen.geantyref.GenericTypeReflector.mapTypeParameters(GenericTypeReflector.java:91)
at io.leangen.geantyref.GenericTypeReflector.getExactReturnType(GenericTypeReflector.java:484)
at io.leangen.graphql.util.ClassUtils.getReturnType(ClassUtils.java:117)
at io.leangen.graphql.metadata.execution.MethodInvoker.resolveReturnType(MethodInvoker.java:36)
at io.leangen.graphql.metadata.execution.MethodInvoker.<init>(MethodInvoker.java:21)
at io.leangen.graphql.metadata.execution.SingletonMethodInvoker.<init>(SingletonMethodInvoker.java:15)
at io.leangen.graphql.metadata.strategy.query.PublicResolverBuilder.lambda$4(PublicResolverBuilder.java:55)
at java.util.stream.ReferencePipeline$3$1.accept(ReferencePipeline.java:193)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
at java.util.stream.ReferencePipeline$2$1.accept(ReferencePipeline.java:175)
at java.util.Spliterators$ArraySpliterator.forEachRemaining(Spliterators.java:948)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at io.leangen.graphql.metadata.strategy.query.PublicResolverBuilder.buildQueryResolvers(PublicResolverBuilder.java:59)
at io.leangen.graphql.metadata.strategy.query.PublicResolverBuilder.buildQueryResolvers(PublicResolverBuilder.java:35)
at io.leangen.graphql.generator.OperationRepository.lambda$8(OperationRepository.java:88)
at io.leangen.graphql.generator.OperationRepository.lambda$11(OperationRepository.java:100)
at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:267)
at java.util.Iterator.forEachRemaining(Iterator.java:116)
at java.util.Spliterators$IteratorSpliterator.forEachRemaining(Spliterators.java:1801)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ForEachOps$ForEachOp.evaluateSequential(ForEachOps.java:151)
at java.util.stream.ForEachOps$ForEachOp$OfRef.evaluateSequential(ForEachOps.java:174)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.forEach(ReferencePipeline.java:418)
at java.util.stream.ReferencePipeline$7$1.accept(ReferencePipeline.java:270)
at java.util.HashMap$KeySpliterator.forEachRemaining(HashMap.java:1548)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:471)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
at io.leangen.graphql.generator.OperationRepository.buildResolvers(OperationRepository.java:102)
at io.leangen.graphql.generator.OperationRepository.buildQueryResolvers(OperationRepository.java:87)
at io.leangen.graphql.generator.OperationRepository.<init>(OperationRepository.java:29)
at io.leangen.graphql.GraphQLSchemaGenerator.generate(GraphQLSchemaGenerator.java:686)
at com.example.graphqlserver.GraphQLController.<init>(GraphQLController.java:146)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at org.springframework.beans.BeanUtils.instantiateClass(BeanUtils.java:165)
... 26 more
from graphql-spqr.
Can you show a bit of the relevant code, where you register the beans and resolver builders? Or an example that illustrates the situation.
Generics can be very tricky, but it's pretty much always solvable.
from graphql-spqr.
Here is where I'm registering resolvers:
GraphQLSchemaGenerator generator = new GraphQLSchemaGenerator()
.withResolverBuilders(
//Resolve by annotations
new AnnotatedResolverBuilder(),
//This is the package where are my custom interfaces
new PublicResolverBuilder("cci.data.repository"),
// This is the package of the Spring built-in interface (CrudRepository)
new PublicResolverBuilder("org.springframework.data.repository"))
........
And here is where I'm registering beans, I loop all my repositories and register them:
Repositories repositories = new Repositories((ListableBeanFactory) context.getAutowireCapableBeanFactory());
for (EntityType entityType : entityManager.getMetamodel().getEntities()) {
Optional op = repositories.getRepositoryFor(entityType.getJavaType());
Optional<RepositoryInformation> info = repositories.getRepositoryInformationFor(entityType.getJavaType());
if (op.isPresent()) {
generator.withOperationsFromSingleton(op.get(), info.get().getRepositoryInterface());
}
}
from graphql-spqr.
Ok, I see. The problem here is that the top-level bean is generic and RepositoryInformation#getRepositoryInterface
returns only a Class
and not the full generic Type
, e.g. it returns CrudRepository
and not CrudRepository<BeanType, IdType>
, so the generic information is lost.
If you can't get the full type from Spring, you might still be able to reconstruct it yourself.
Maybe something along these lines:
//This assumes all results of getRepositoryInterface are generic types with 2 parameters, e.g. CrudRepository<BeanType, IdType>
//Not sure if that's true or needs more checks
Type repoType = TypeFactory.parameterizedClass(info.get().getRepositoryInterface(), entityType.getJavaType(), entityType.getIdType());
generator.withOperationsFromSingleton(op.get(), repoType);
The bottom line being that when the bean itself is of a generic type (not just inheriting or referring to one), you need to provide the full Type
or AnnotatedType
when registering it, as this info can not be discovered at runtime (due to type erasure).
from graphql-spqr.
I tried your solution but unfortunately it didn't work because our repository interface itself doesn't have generic types, it is its superclass that has generic types.
So how could I apply your solution to the interfaces superclass? Do I have to remove the public resolver that I added specifying the built-in Spring interfaces package?
from graphql-spqr.
Was also looking into this and found that the culprit is:
public interface CrudRepository<T, ID> extends Repository<T, ID> {
<S extends T> S save(S entity);
...
}
Since it's the super type in Spring that has this, how would I work around the issue?
I see how using TypeFactory would work if the service I wanted to expose was generic, but in this case it's not.
Any ideas?
from graphql-spqr.
I'm on vacation for about a week more, and can only look into this once I'm back. In the the meantime, try putting a breakpoint inside AnnotatedResolverBuilder#buildQueryResolvers
and PublicResolverBuilder#buildQueryResolvers
and try to see what goes wrong, i.e. why are some interface methods skipped.
As for the generics, TypeFactory
shouldn't be needed when the generics are coming from the super type, that information is already preserved in the Java class. So it seems you're hitting some edge case I'm unaware of... A small self contained example would help me a lot in debugging this.
from graphql-spqr.
I put some break points on PublicResolverBuilder#buildQueryResolvers
as you said, and the reason because the interface methods are being skipped is that PublicResolverBuilder#isPackageAcceptable
returns false for those methods, that is because of this condition: return method.getDeclaringClass().equals(beanType) || method.getDeclaringClass().getPackage().getName().startsWith(basePackage);
.
I am not registering a public resolver with the interface package as the base package, so the condition is not true. When I register a public resolver with the interface package ("org.springframework.data.repository") as the base package I get the previously mentioned error about the generics.
I'm sending you a small self contained example so you can reproduce the generics error:
generator.withOperationsFromSingleton(new MyConcreteClass());
.......
class MyDomainObject {
}
interface MyInterface<T> {
public <S extends T> S myMethod();
}
class MyConcreteClass implements MyInterface<MyDomainObject> {
@Override
public <S extends MyDomainObject> S myMethod() {
return null;
}
}
from graphql-spqr.
My 2 cents are:
I think I narrowed down the problem to the mapTypeParameters function in GenericTypeReflector. I made a change which got me a bit further but I'm quite sure is not the solution:
public static AnnotatedType mapTypeParameters(AnnotatedType toMapType, AnnotatedType typeAndParams) {
//>>>>Added this line
Type toMapTypeType = toMapType.getType();
if (isMissingTypeParameters(typeAndParams.getType())) {
return new AnnotatedTypeImpl(erase(toMapTypeType), toMapType.getAnnotations());
} else {
VarMap varMap = new VarMap();
AnnotatedType handlingTypeAndParams = typeAndParams;
while(handlingTypeAndParams instanceof AnnotatedParameterizedType) {
AnnotatedParameterizedType pType = (AnnotatedParameterizedType)handlingTypeAndParams;
Class<?> clazz = (Class<?>)((ParameterizedType) pType.getType()).getRawType(); // getRawType should always be Class
TypeVariable[] vars = clazz.getTypeParameters();
varMap.addAll(vars, pType.getAnnotatedActualTypeArguments());
Type owner = ((ParameterizedType) pType.getType()).getOwnerType();
handlingTypeAndParams = owner == null ? null : annotate(owner);
}
//>>>>And these
if (toMapTypeType instanceof TypeVariable) {
varMap.add((TypeVariable) toMapTypeType, typeAndParams);
}
return varMap.map(toMapType);
}
}
Hope that's of some help.
from graphql-spqr.
I investigates this issue in more detail... The problem is that the exact return type really is unknown, since it's declared as a (bounded) variable again in the implementing class.
Since this is valid code, that obviously occurs in the wild, and a sensible approach exists (map the boundary instead of the exact type), I'll try to support it...
What happens now is that GeAnTyRef explodes as it's goal is to resolve the exact type. So supporting this use case means resolving the return type as much as possible but potentially not exactly... Because this runs contrary to what GeAnTyRef is meant to do, it will have to be implemented as a new feature in GeAnTyRef e.g. getInexactReturnType
, and SPQR's type transformers will have to be updated to be able to deal with this (it is currently assumed that GeAnTyRef will either resolve the type fully or throw).
Anyway, that was a brain dump. In the meantime, you can try solving this locally by registering a custom mapper for this case, that will map the variable boundary instead of trying to resolve it. It means the boundary itself has to be an exact type and not yet another variable, as in that case you'd be reimplementing GeAnTyRef's logic.
from graphql-spqr.
Thanks for the update!
I tried to wrap my head around the problem but this one required more in-depth knowledge of how GeAnTyRef was implemented.
If there's anything I can do to help please let me know.
from graphql-spqr.
Added the needed feature to GeAnTyRef and released it in v1.3.0. I've made the necessary changes to SPQR as well and it seems to work. I'll add some tests and release v0.9.4 probably tomorrow.
from graphql-spqr.
You the man! I'm looking forward to testing the new release.
from graphql-spqr.
Just for sake of completeness: tested this out and it works like a charm. Many thanks!
from graphql-spqr.
Awesome! Thanks for testing/confirming!
from graphql-spqr.
Hi, guys Im encounter with same problem want generate queries from spring data repository. Actually Im not understand the solution from feed above. Can you help to solve it?
from graphql-spqr.
i get error for method like "<"S extends T">" S save(S s)
UnresolvedTypeVariableException(TypeVariable tv) {
super("An exact type is requested, but the type contains a type variable that cannot be resolved.\n" +
" Variable: " + tv.getName() + " from " + tv.getGenericDeclaration() + "\n" +
" Hint: This is usually caused by trying to get an exact type when a generic method who's type parameters are not given is involved.");
from graphql-spqr.
Related Issues (20)
- How to use executionResult data HOT 2
- Failing tests - ComplexityTest class
- Explicitly register Java classes to the GraphQL schema. HOT 4
- How to map a java class to GraphQL interface without using @GraphQLInterface annotation? HOT 5
- Best way to error handling HOT 1
- Help in Handling Exceptions with SPQR HOT 1
- Question: Extending an InputObject type and handle it in SPQR HOT 3
- SDL Printing Fails on Schema Directives with Arguments HOT 4
- Spring 3.2: Cannot resolve parameter names for constructor public io.leangen.graphql.spqr.spring.web.dto.GraphQLRequest(java.lang.String,java.lang.String,java.lang.String,java.util.Map) HOT 2
- Compile parameters to support Spring Boot 3.2.1 (Spring Framework 6.1) HOT 1
- null values sent in response json HOT 1
- Schema Generation Regression in 0.12.4 HOT 1
- Polymorphism type on input
- Scalars containing an Instant cannot be serialized over subscription
- Schema generation at compile time?
- BUG: The DelegatingTypeResolver breaks when updating the GraphQLSchema using a Visitor HOT 1
- Feature Request: Add support for GraphQLOutputType to the SchemaTransformer HOT 39
- Make a public OperationMapper#createResolver overload to help with creating custom field resolvers
- Mark the gson-java8-datatype dependency as optional HOT 3
- Kotlin boolean property setters are skipped if isX prefixed HOT 11
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from graphql-spqr.