diff --git a/http-client/src/main/java/io/micronaut/http/client/netty/CancellableMonoSink.java b/http-client/src/main/java/io/micronaut/http/client/netty/CancellableMonoSink.java index c86796a3f19..b7cbeda3f05 100644 --- a/http-client/src/main/java/io/micronaut/http/client/netty/CancellableMonoSink.java +++ b/http-client/src/main/java/io/micronaut/http/client/netty/CancellableMonoSink.java @@ -44,6 +44,7 @@ final class CancellableMonoSink implements Publisher, Sinks.One, Subscr private T value; private Throwable failure; private boolean complete = false; + private boolean cancelled = false; private Subscriber subscriber = null; private boolean subscriberWaiting = false; @@ -72,7 +73,7 @@ public void subscribe(Subscriber s) { } private void tryForward() { - if (subscriberWaiting && complete) { + if (subscriberWaiting && complete && !cancelled) { if (failure == null) { if (value != EMPTY) { subscriber.onNext(value); @@ -181,6 +182,7 @@ public void cancel() { lock.lock(); try { complete = true; + cancelled = true; } finally { lock.unlock(); } diff --git a/http-client/src/test/groovy/io/micronaut/http/client/netty/CancellableMonoSinkSpec.groovy b/http-client/src/test/groovy/io/micronaut/http/client/netty/CancellableMonoSinkSpec.groovy new file mode 100644 index 00000000000..b15f36e27f2 --- /dev/null +++ b/http-client/src/test/groovy/io/micronaut/http/client/netty/CancellableMonoSinkSpec.groovy @@ -0,0 +1,41 @@ +package io.micronaut.http.client.netty + +import org.reactivestreams.Subscriber +import org.reactivestreams.Subscription +import reactor.core.publisher.Mono +import spock.lang.Specification + +class CancellableMonoSinkSpec extends Specification { + def "cancel before request"() { + given: + def sink = new CancellableMonoSink(null) + def result = "unset" + Subscription subscription = null + sink.subscribe(new Subscriber() { + @Override + void onSubscribe(Subscription s) { + subscription = s + } + + @Override + void onNext(String s) { + result = s + } + + @Override + void onError(Throwable t) { + } + + @Override + void onComplete() { + } + }) + + when: + sink.cancel() + subscription.request(1) + + then: + result == "unset" + } +} diff --git a/inject-java/src/main/java/io/micronaut/annotation/processing/visitor/JavaFieldElement.java b/inject-java/src/main/java/io/micronaut/annotation/processing/visitor/JavaFieldElement.java index 9c2ce27976e..9bcc5fe725c 100644 --- a/inject-java/src/main/java/io/micronaut/annotation/processing/visitor/JavaFieldElement.java +++ b/inject-java/src/main/java/io/micronaut/annotation/processing/visitor/JavaFieldElement.java @@ -22,6 +22,7 @@ import io.micronaut.inject.ast.FieldElement; import io.micronaut.inject.ast.MemberElement; import io.micronaut.inject.ast.annotation.ElementAnnotationMetadataFactory; +import io.micronaut.inject.processing.JavaModelUtils; import javax.lang.model.element.Element; import javax.lang.model.element.TypeElement; @@ -130,8 +131,8 @@ public ClassElement getDeclaringType() { if (resolvedDeclaringClass == null) { Element enclosingElement = variableElement.getEnclosingElement(); if (enclosingElement instanceof TypeElement te) { - String typeName = te.getQualifiedName().toString(); - if (owningType.getName().equals(typeName)) { + String typeName = JavaModelUtils.getClassName(te); + if (owningType.getName().equals(JavaModelUtils.getClassName(te))) { resolvedDeclaringClass = owningType; } else { TypeMirror returnType = te.asType(); diff --git a/inject-java/src/test/groovy/io/micronaut/inject/factory/generics/GenericFactorySpec.groovy b/inject-java/src/test/groovy/io/micronaut/inject/factory/generics/GenericFactorySpec.groovy index 04ed19fb0b8..03355a885e4 100644 --- a/inject-java/src/test/groovy/io/micronaut/inject/factory/generics/GenericFactorySpec.groovy +++ b/inject-java/src/test/groovy/io/micronaut/inject/factory/generics/GenericFactorySpec.groovy @@ -21,7 +21,7 @@ class SerdeFactory { protected Serde arraySerde() { return new Serde() { }; - } + } } interface Serializer { @@ -58,7 +58,7 @@ class MyBean { @Inject public BaseCache fieldInjectBase; public Cache methodInject; - + @Inject void setCache(Cache methodInject) { this.methodInject = methodInject; @@ -77,7 +77,7 @@ class CacheFactory { Cache buildCache(ArgumentInjectionPoint ip) { Class keyType = ip.asArgument().getTypeVariable("K").get().getType(); Class valueType = ip.asArgument().getTypeVariable("V").get().getType(); - + return new CacheImpl(keyType, valueType); } } @@ -89,7 +89,7 @@ interface Cache extends BaseCache {} class CacheImpl implements Cache { public final Class keyType; public final Class valueType; - + CacheImpl(Class k, Class v) { keyType = k; valueType = v; @@ -117,4 +117,77 @@ class CacheImpl implements Cache { cleanup: context.close() } + + void "test generic factory with type variables - constructor inject"() { + given: + def context = buildContext(''' +package genfact; + +import io.micronaut.context.annotation.*; +import io.micronaut.inject.ArgumentInjectionPoint; +import io.micronaut.inject.ConstructorInjectionPoint; +import jakarta.inject.*; +import io.micronaut.core.type.ArgumentCoercible; + +@Singleton +class MyBean { + public final Cache constructorInject; + MyBean(Cache constructorInject) { + this.constructorInject = constructorInject; + } +} + +@Singleton +class OtherBean { + public Cache invalid; + OtherBean(Cache invalid) { + this.invalid = invalid; + } +} + +@Factory +class CacheFactory { + @Bean + Cache buildCache(ArgumentInjectionPoint ip) { + Class keyType = ip.asArgument().getTypeVariable("K").get().getType(); + Class valueType = ip.asArgument().getTypeVariable("V").get().getType(); + if (ip.getOuterInjectionPoint() instanceof ConstructorInjectionPoint constructorInjectionPoint + && !constructorInjectionPoint.toString().equals("genfact.MyBean(Cache constructorInject)")) { + throw new IllegalStateException(); + } + return new CacheImpl(keyType, valueType); + } +} + +interface BaseCache {} + +interface Cache extends BaseCache {} + +class CacheImpl implements Cache { + public final Class keyType; + public final Class valueType; + + CacheImpl(Class k, Class v) { + keyType = k; + valueType = v; + } +} +''') + when: + def bean = getBean(context, "genfact.MyBean") + + then: + bean.constructorInject.keyType == String + bean.constructorInject.valueType == Integer + + when: + getBean(context, "genfact.OtherBean") + + then: + def e = thrown(DependencyInjectionException) + e.message.contains("No bean of type [genfact.Cache] exists") + + cleanup: + context.close() + } } diff --git a/inject-java/src/test/groovy/io/micronaut/visitors/ClassElementSpec.groovy b/inject-java/src/test/groovy/io/micronaut/visitors/ClassElementSpec.groovy index 71c2ceca91f..a528e7bddec 100644 --- a/inject-java/src/test/groovy/io/micronaut/visitors/ClassElementSpec.groovy +++ b/inject-java/src/test/groovy/io/micronaut/visitors/ClassElementSpec.groovy @@ -15,7 +15,6 @@ */ package io.micronaut.visitors - import io.micronaut.annotation.processing.test.AbstractTypeElementSpec import io.micronaut.annotation.processing.visitor.JavaClassElement import io.micronaut.context.annotation.Replaces @@ -39,14 +38,13 @@ import io.micronaut.inject.ast.WildcardElement import jakarta.validation.Valid import jakarta.validation.constraints.NotNull import jakarta.validation.constraints.Null +import java.sql.SQLException +import java.util.function.Supplier import spock.lang.IgnoreIf import spock.lang.Issue import spock.lang.Unroll import spock.util.environment.Jvm -import java.sql.SQLException -import java.util.function.Supplier - class ClassElementSpec extends AbstractTypeElementSpec { void "test package-private methods with broken different package"() { @@ -1181,6 +1179,105 @@ class MyBean {} } + void "test field type with generic in inner class"() { + given: + buildClassElement(""" +package test; + +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import java.util.List; +import java.util.Locale; + +@Controller("/test") +class TestController { + @Get + public HttpResponse>> endpoint() { + return null; + } + + public static class ResponseObject { + + public T body; + } + + public static class Dto { + + public Locale locale; + } +} +""") { ClassElement ce -> + + def responseType = ce.methods[0].returnType + + responseType.type.name == 'io.micronaut.http.HttpResponse' + assert responseType.typeArguments + assert responseType.typeArguments.size() == 1 + + def typeArg = responseType.firstTypeArgument.orElse(null) + assert typeArg + assert typeArg.fields + assert typeArg.fields.size() == 1 + + def bodyField = typeArg.fields[0] + assert bodyField.name == "body" + assert bodyField.genericType.name == "java.util.List" + assert bodyField.genericType.getFirstTypeArgument().get().name == 'test.TestController$Dto' + } + } + + void "test field type with generic in inner super class"() { + given: + buildClassElement(""" +package test; + +import io.micronaut.http.HttpResponse; +import io.micronaut.http.annotation.Controller; +import io.micronaut.http.annotation.Get; +import java.util.List; +import java.util.Locale; + +@Controller("/test") +class TestController { + @Get + public HttpResponse>> endpoint() { + return null; + } + + public static class BaseResponseObject { + + public T body; + } + + public static class ResponseObject extends BaseResponseObject { + } + + public static class Dto { + + public Locale locale; + } +} +""") { ClassElement ce -> + + def responseType = ce.methods[0].returnType + + responseType.type.name == 'io.micronaut.http.HttpResponse' + assert responseType.typeArguments + assert responseType.typeArguments.size() == 1 + + def typeArg = responseType.firstTypeArgument.orElse(null) + assert typeArg + assert typeArg.fields + assert typeArg.fields.size() == 1 + + def bodyField = typeArg.fields[0] + assert bodyField.name == "body" + assert bodyField.genericType.name == "java.util.List" + assert bodyField.genericType.getFirstTypeArgument().get().name == 'test.TestController$Dto' + } + } + @Issue('https://github.com/micronaut-projects/micronaut-openapi/issues/593') void 'test declaringType is the implementation and not the interface'() { given: diff --git a/inject/src/main/java/io/micronaut/context/AbstractBeanResolutionContext.java b/inject/src/main/java/io/micronaut/context/AbstractBeanResolutionContext.java index 1553a3d479d..959cdbbc1cb 100644 --- a/inject/src/main/java/io/micronaut/context/AbstractBeanResolutionContext.java +++ b/inject/src/main/java/io/micronaut/context/AbstractBeanResolutionContext.java @@ -499,11 +499,6 @@ public ConstructorArgumentSegment(BeanDefinition declaringType, Qualifie super(declaringType, qualifier, methodName, argument, arguments); } - @Override - public CallableInjectionPoint getOuterInjectionPoint() { - throw new UnsupportedOperationException("Outer injection point inaccessible from here"); - } - @Override public BeanDefinition getDeclaringBean() { return getDeclaringType(); diff --git a/src/main/docs/guide/httpServer/errorHandling/exceptionHandler/builtInExceptionHandlers.adoc b/src/main/docs/guide/httpServer/errorHandling/exceptionHandler/builtInExceptionHandlers.adoc index 1e565c8071b..05fa23287a5 100644 --- a/src/main/docs/guide/httpServer/errorHandling/exceptionHandler/builtInExceptionHandlers.adoc +++ b/src/main/docs/guide/httpServer/errorHandling/exceptionHandler/builtInExceptionHandlers.adoc @@ -12,13 +12,13 @@ The Micronaut framework ships with several built-in handlers: | api:http.server.exceptions.DuplicateRouteHandler[] | api:http.exceptions.HttpStatusException[] | api:http.server.exceptions.HttpStatusHandler[] -| api:http.exceptions.UnsupportedMediaException[] +| api:http.server.exceptions.UnsupportedMediaException[] | api:http.server.exceptions.HttpStatusHandler[] -| api:http.exceptions.NotFoundException[] +| api:http.server.exceptions.NotFoundException[] | api:http.server.exceptions.HttpStatusHandler[] -| api:http.exceptions.NotAcceptableException[] +| api:http.server.exceptions.NotAcceptableException[] | api:http.server.exceptions.HttpStatusHandler[] -| api:http.exceptions.NotAllowedException[] +| api:http.server.exceptions.NotAllowedException[] | api:http.server.exceptions.NotAllowedExceptionHandler[] | `com.fasterxml.jackson.core.JsonProcessingException` | api:http.server.exceptions.JsonExceptionHandler[]