Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix ambiguous bean resolution for sub resource interfaces with multiple implementations #47197

Merged
merged 1 commit into from
Apr 9, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
package io.quarkus.resteasy.reactive.server.test.resource.basic;

import static io.restassured.RestAssured.given;
import static org.hamcrest.CoreMatchers.is;

import java.util.Map;
import java.util.function.Supplier;

import jakarta.enterprise.context.RequestScoped;
import jakarta.inject.Inject;
import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.PathParam;
import jakarta.ws.rs.Produces;
import jakarta.ws.rs.core.MediaType;

import org.jboss.shrinkwrap.api.ShrinkWrap;
import org.jboss.shrinkwrap.api.spec.JavaArchive;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.resteasy.reactive.server.test.simple.PortProviderUtil;
import io.quarkus.test.QuarkusUnitTest;

class SubResourceAmbiguousInjectTest {
@RegisterExtension
static QuarkusUnitTest testExtension = new QuarkusUnitTest()
.setArchiveProducer(new Supplier<>() {
@Override
public JavaArchive get() {
JavaArchive war = ShrinkWrap.create(JavaArchive.class);
war.addClass(PortProviderUtil.class);
war.addClass(EnglishGreeterResource.class);
war.addClass(SpanishGreeterResource.class);
war.addClass(GreeterResource.class);
war.addClass(LanguageResource.class);
war.addClass(LanguageResourceV2.class);
war.addClass(GreeterResourceV2.class);
war.addClass(EnglishGreeterResource.class);
war.addClass(SpanishGreeterResource.class);
return war;
}
});

@Test
void basicTest() {
given().when().get("/languages/en").then().statusCode(200).body(is("hello"));
given().when().get("/languages/v2/es").then().statusCode(200).body(is("hola"));
}

@RequestScoped
public static class EnglishGreeterResource implements GreeterResource {
@Override
public String greeting() {
return "hello";
}
}

@RequestScoped
public static class SpanishGreeterResource implements GreeterResource {
@Override
public String greeting() {
return "hola";
}
}

public interface GreeterResource {
@GET
@Produces(MediaType.TEXT_PLAIN)
String greeting();
}

@Path("languages")
public static class LanguageResource {

private final Map<String, GreeterResource> languages;

@Inject
public LanguageResource(
final EnglishGreeterResource english, final SpanishGreeterResource spanish) {
languages = Map.of("en", english, "es", spanish);
}

@Path("{language}")
public GreeterResource locateGreeter(@PathParam("language") final String language) {
return languages.get(language);
}
}

@Path("languages/v2")
public static class LanguageResourceV2 {

private final Map<String, GreeterResourceV2> languages;

@Inject
public LanguageResourceV2(
final EnglishGreeterResourceV2 english, final SpanishGreeterResourceV2 spanish) {
languages = Map.of("en", english, "es", spanish);
}

@Path("{language}")
public GreeterResourceV2 locateGreeter(@PathParam("language") final String language) {
return languages.get(language);
}
}

public abstract static class GreeterResourceV2 {
@GET
@Produces(MediaType.TEXT_PLAIN)
public abstract String greeting();
}

@RequestScoped
public static class EnglishGreeterResourceV2 extends GreeterResourceV2 {
@Override
public String greeting() {
return "hello";
}
}

@RequestScoped
public static class SpanishGreeterResourceV2 extends GreeterResourceV2 {
@Override
public String greeting() {
return "hola";
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -297,7 +297,10 @@ public Optional<ResourceClass> createEndpoints(ClassInfo classInfo, boolean cons
}
clazz.setPath(sanitizePath(path));
}
if (factoryCreator != null) {
if (factoryCreator != null && !classInfo.isInterface()) {
// Most likely an interface for a sub resource. The ResourceLocatorHandler does not use the factory to create new instances, but uses the result of the sub resource locator method instead
// Interfaces therefore do not need a factory here
// Otherwise, when having multiple implementations of the interface, an Ambiguous Bean Resolution error occurs, since io.quarkus.arc.runtime.BeanContainerImpl.createFactory is run, even if the factory is never invoked
clazz.setFactory(factoryCreator.apply(classInfo.name().toString()));
}
Map<String, String> classLevelExceptionMappers = this.classLevelExceptionMappers.get(classInfo.name());
Expand Down
Loading