GithubHelp home page GithubHelp logo

Comments (18)

kaqqao avatar kaqqao commented on May 17, 2024

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 ResolverBuilders are used in those cases (top level queries by default require annotations while the nested don't).

from graphql-spqr.

joseandresromero avatar joseandresromero commented on May 17, 2024

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.

kaqqao avatar kaqqao commented on May 17, 2024

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.

joseandresromero avatar joseandresromero commented on May 17, 2024

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.

kaqqao avatar kaqqao commented on May 17, 2024

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.

joseandresromero avatar joseandresromero commented on May 17, 2024

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.

miguelocarvajal avatar miguelocarvajal commented on May 17, 2024

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.

kaqqao avatar kaqqao commented on May 17, 2024

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.

joseandresromero avatar joseandresromero commented on May 17, 2024

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.

miguelocarvajal avatar miguelocarvajal commented on May 17, 2024

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.

kaqqao avatar kaqqao commented on May 17, 2024

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.

miguelocarvajal avatar miguelocarvajal commented on May 17, 2024

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.

kaqqao avatar kaqqao commented on May 17, 2024

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.

miguelocarvajal avatar miguelocarvajal commented on May 17, 2024

You the man! I'm looking forward to testing the new release.

from graphql-spqr.

miguelocarvajal avatar miguelocarvajal commented on May 17, 2024

Just for sake of completeness: tested this out and it works like a charm. Many thanks!

from graphql-spqr.

kaqqao avatar kaqqao commented on May 17, 2024

Awesome! Thanks for testing/confirming!

from graphql-spqr.

askmi avatar askmi commented on May 17, 2024

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.

askmi avatar askmi commented on May 17, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.