Skip to content

Commit

Permalink
Merge pull request #28429 from manovotn/issue28115
Browse files Browse the repository at this point in the history
Revisit the non-standard behavior of ArcContainer#instanceSupplier()
  • Loading branch information
manovotn authored Oct 11, 2022
2 parents 8e1750c + c5b87cf commit f7509ba
Show file tree
Hide file tree
Showing 39 changed files with 270 additions and 68 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -77,11 +77,11 @@ public void setHandlerClass(Class<? extends RequestHandler<?, ?>> handler, BeanC
*/
public static void handle(InputStream inputStream, OutputStream outputStream, Context context) throws IOException {
if (streamHandlerClass != null) {
RequestStreamHandler handler = beanContainer.instance(streamHandlerClass);
RequestStreamHandler handler = beanContainer.beanInstance(streamHandlerClass);
handler.handleRequest(inputStream, outputStream, context);
} else {
Object request = objectReader.readValue(inputStream);
RequestHandler handler = beanContainer.instance(handlerClass);
RequestHandler handler = beanContainer.beanInstance(handlerClass);
Object response = handler.handleRequest(request, context);
objectWriter.writeValue(outputStream, response);
}
Expand Down Expand Up @@ -166,7 +166,7 @@ public void startPollLoop(ShutdownContext context, LaunchMode launchMode) {

@Override
protected Object processRequest(Object input, AmazonLambdaContext context) throws Exception {
RequestHandler handler = beanContainer.instance(handlerClass);
RequestHandler handler = beanContainer.beanInstance(handlerClass);
return handler.handleRequest(input, context);
}

Expand All @@ -188,7 +188,7 @@ protected boolean isStream() {
@Override
protected void processRequest(InputStream input, OutputStream output, AmazonLambdaContext context)
throws Exception {
RequestStreamHandler handler = beanContainer.instance(streamHandlerClass);
RequestStreamHandler handler = beanContainer.beanInstance(streamHandlerClass);
handler.handleRequest(input, output, context);

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ public static class TestRecorder {

public void test(BeanContainer beanContainer) {
// This should trigger the warning - Gama was removed
Gama gama = beanContainer.instance(Gama.class);
Gama gama = beanContainer.beanInstance(Gama.class);
// Test that fallback was used - no injection was performed
Assertions.assertNull(gama.beanManager);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,63 @@
public interface BeanContainer {

/**
* Returns a bean instance for given bean type and qualifiers.
* <p/>
* This method follows standard CDI rules meaning that if there are two or more eligible beans, an ambiguous
* dependency exception is thrown.
* Note that the method is allowed to return {@code null} if there is no matching bean which allows
* for fallback implementations.
*
* @param type
* @param qualifiers
* @return a bean instance or {@code null} if no matching bean is found
*/
default <T> T beanInstance(Class<T> type, Annotation... qualifiers) {
return beanInstanceFactory(type, qualifiers).create().get();
}

/**
* This method is deprecated and will be removed in future versions.
* Use {@link #beanInstance(Class, Annotation...)} instead.
* </p>
* As opposed to {@link #beanInstance(Class, Annotation...)}, this method does <b>NOT</b> follow CDI
* resolution rules and in case of ambiguous resolution performs a choice based on the class type parameter.
*
* @param type
* @param qualifiers
* @return a bean instance or {@code null} if no matching bean is found
*/
@Deprecated
default <T> T instance(Class<T> type, Annotation... qualifiers) {
return instanceFactory(type, qualifiers).create().get();
}

/**
* Note that if there are multiple sub classes of the given type this will return the exact match. This means
* that this can be used to directly instantiate superclasses of other beans without causing problems. This behavior differs
* to standard CDI rules where an ambiguous dependency would exist.
* Returns an instance factory for given bean type and qualifiers.
* <p/>
* This method follows standard CDI rules meaning that if there are two or more beans, an ambiguous dependency
* exception is thrown.
* Note that the factory itself is still allowed to return {@code null} if there is no matching bean which allows
* for fallback implementations.
*
* @param type
* @param qualifiers
* @return a bean instance factory, never {@code null}
*/
<T> Factory<T> beanInstanceFactory(Class<T> type, Annotation... qualifiers);

/**
* This method is deprecated and will be removed in future versions.
* Use {@link #beanInstanceFactory(Class, Annotation...)} instead.
* </p>
* As opposed to {@link #beanInstanceFactory(Class, Annotation...)}, this method does <b>NOT</b> follow CDI
* resolution rules and in case of ambiguous resolution performs a choice based on the class type parameter.
*
* @param type
* @param qualifiers
* @return a bean instance factory, never {@code null}
*/
@Deprecated
<T> Factory<T> instanceFactory(Class<T> type, Annotation... qualifiers);

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,19 @@ public BeanContainerImpl(ArcContainer container) {
this.container = container;
}

@Override
public <T> Factory<T> beanInstanceFactory(Class<T> type, Annotation... qualifiers) {
Supplier<InstanceHandle<T>> handleSupplier = container.beanInstanceSupplier(type, qualifiers);
return createFactory(handleSupplier, type, qualifiers);
}

@Override
public <T> Factory<T> instanceFactory(Class<T> type, Annotation... qualifiers) {
Supplier<InstanceHandle<T>> handleSupplier = container.instanceSupplier(type, qualifiers);
return createFactory(handleSupplier, type, qualifiers);
}

private <T> Factory<T> createFactory(Supplier<InstanceHandle<T>> handleSupplier, Class<T> type, Annotation... qualifiers) {
if (handleSupplier == null) {
LOGGER.debugf(
"No matching bean found for type %s and qualifiers %s. The bean might have been marked as unused and removed during build.",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public void runLoadTask(Runnable runnable) {
}

public void setDomainForIdentityProvider(BeanContainer bc, RuntimeValue<SecurityDomain> domain) {
bc.instance(ElytronSecurityDomainManager.class).setDomain(domain.getValue());
bc.beanInstance(ElytronSecurityDomainManager.class).setDomain(domain.getValue());
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public class FunqyCloudFunctionsBindingRecorder {

public void init(BeanContainer bc) {
beanContainer = bc;
objectMapper = beanContainer.instance(ObjectMapper.class);
objectMapper = beanContainer.beanInstance(ObjectMapper.class);

for (FunctionInvoker invoker : FunctionRecorder.registry.invokers()) {
if (invoker.hasInput()) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public FunctionConstructor(Class<T> cls) {

public T construct() {
if (factory == null)
factory = CONTAINER.instanceFactory(cls);
factory = CONTAINER.beanInstanceFactory(cls);
return factory.create().get();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public DataSourceTenantConnectionResolver get() {
}

public void startAllPersistenceUnits(BeanContainer beanContainer) {
beanContainer.instance(JPAConfig.class).startAll();
beanContainer.beanInstance(JPAConfig.class).startAll();
}

public Supplier<SessionFactory> sessionFactorySupplier(String persistenceUnitName) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ public class InfinispanRecorder {

public BeanContainerListener configureInfinispan(@RelaxedValidation Properties properties) {
return container -> {
InfinispanClientProducer instance = container.instance(InfinispanClientProducer.class);
InfinispanClientProducer instance = container.beanInstance(InfinispanClientProducer.class);
instance.configure(properties);
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ public Object construct(boolean unwrapAsync) {
if (factory != null) {
return factory.get().get();
}
factory = Arc.container().instanceSupplier(this.ctor.getDeclaringClass());
factory = Arc.container().beanInstanceSupplier(this.ctor.getDeclaringClass());
if (factory == null) {
return delegate.construct(unwrapAsync);
}
Expand All @@ -45,7 +45,7 @@ public Object construct(HttpRequest request, HttpResponse response, boolean unwr
if (factory != null) {
return factory.get().get();
}
factory = Arc.container().instanceSupplier(this.ctor.getDeclaringClass());
factory = Arc.container().beanInstanceSupplier(this.ctor.getDeclaringClass());
if (factory == null) {
return delegate.construct(request, response, unwrapAsync);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class ArcBeanFactory<T> implements BeanFactory<T> {

public ArcBeanFactory(Class<T> target, BeanContainer beanContainer) {
targetClassName = target.getName();
factory = beanContainer.instanceFactory(target);
factory = beanContainer.beanInstanceFactory(target);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@
import javax.ws.rs.ext.MessageBodyReader;
import javax.ws.rs.ext.MessageBodyWriter;
import javax.ws.rs.ext.Providers;
import javax.ws.rs.ext.ReaderInterceptor;
import javax.ws.rs.ext.WriterInterceptor;

import org.eclipse.microprofile.config.Config;
import org.eclipse.microprofile.config.ConfigProvider;
Expand All @@ -57,6 +59,8 @@
import org.jboss.resteasy.reactive.common.core.Serialisers;
import org.jboss.resteasy.reactive.common.core.SingletonBeanFactory;
import org.jboss.resteasy.reactive.common.model.InjectableBean;
import org.jboss.resteasy.reactive.common.model.InterceptorContainer;
import org.jboss.resteasy.reactive.common.model.PreMatchInterceptorContainer;
import org.jboss.resteasy.reactive.common.model.ResourceClass;
import org.jboss.resteasy.reactive.common.model.ResourceDynamicFeature;
import org.jboss.resteasy.reactive.common.model.ResourceFeature;
Expand Down Expand Up @@ -718,6 +722,75 @@ private FilterClassIntrospector createFilterClassIntrospector() {
return ab.get();
}

// We want to add @Typed to resources and providers so that they can be resolved as CDI bean using purely their
// class as a bean type. This removes any ambiguity that potential subclasses may have.
@BuildStep
public void transformEndpoints(
ResourceScanningResultBuildItem resourceScanningResultBuildItem,
ResourceInterceptorsBuildItem resourceInterceptorsBuildItem,
BuildProducer<io.quarkus.arc.deployment.AnnotationsTransformerBuildItem> annotationsTransformer) {

// all found resources and sub-resources
Set<DotName> allResources = new HashSet<>();
allResources.addAll(resourceScanningResultBuildItem.getResult().getScannedResources().keySet());
allResources.addAll(resourceScanningResultBuildItem.getResult().getPossibleSubResources().keySet());

// discovered filters and interceptors
Set<String> filtersAndInterceptors = new HashSet<>();
InterceptorContainer<ReaderInterceptor> readerInterceptors = resourceInterceptorsBuildItem.getResourceInterceptors()
.getReaderInterceptors();
readerInterceptors.getNameResourceInterceptors().forEach(i -> filtersAndInterceptors.add(i.getClassName()));
readerInterceptors.getGlobalResourceInterceptors().forEach(i -> filtersAndInterceptors.add(i.getClassName()));
InterceptorContainer<WriterInterceptor> writerInterceptors = resourceInterceptorsBuildItem.getResourceInterceptors()
.getWriterInterceptors();
writerInterceptors.getNameResourceInterceptors().forEach(i -> filtersAndInterceptors.add(i.getClassName()));
writerInterceptors.getGlobalResourceInterceptors().forEach(i -> filtersAndInterceptors.add(i.getClassName()));
PreMatchInterceptorContainer<ContainerRequestFilter> containerRequestFilters = resourceInterceptorsBuildItem
.getResourceInterceptors().getContainerRequestFilters();
containerRequestFilters.getPreMatchInterceptors().forEach(i -> filtersAndInterceptors.add(i.getClassName()));
containerRequestFilters.getNameResourceInterceptors().forEach(i -> filtersAndInterceptors.add(i.getClassName()));
containerRequestFilters.getGlobalResourceInterceptors().forEach(i -> filtersAndInterceptors.add(i.getClassName()));
InterceptorContainer<ContainerResponseFilter> containerResponseFilters = resourceInterceptorsBuildItem
.getResourceInterceptors().getContainerResponseFilters();
containerResponseFilters.getGlobalResourceInterceptors().forEach(i -> filtersAndInterceptors.add(i.getClassName()));
containerResponseFilters.getNameResourceInterceptors().forEach(i -> filtersAndInterceptors.add(i.getClassName()));

annotationsTransformer.produce(new io.quarkus.arc.deployment.AnnotationsTransformerBuildItem(
new io.quarkus.arc.processor.AnnotationsTransformer() {

@Override
public boolean appliesTo(AnnotationTarget.Kind kind) {
return kind == AnnotationTarget.Kind.CLASS;
}

@Override
public void transform(TransformationContext context) {
ClassInfo clazz = context.getTarget().asClass();
// check if the class is one of resources/sub-resources
if (allResources.contains(clazz.name())
&& clazz.declaredAnnotation(ResteasyReactiveDotNames.TYPED) == null) {
context.transform().add(createTypedAnnotationInstance(clazz)).done();
return;
}
// check if the class is one of providers, either explicitly declaring the annotation
// or discovered as resource interceptor or filter
if ((clazz.declaredAnnotation(ResteasyReactiveDotNames.PROVIDER) != null
|| filtersAndInterceptors.contains(clazz.name().toString()))
&& clazz.declaredAnnotation(ResteasyReactiveDotNames.TYPED) == null) {
// Add @Typed(MyResource.class)
context.transform().add(createTypedAnnotationInstance(clazz)).done();
}
}
}));
}

private AnnotationInstance createTypedAnnotationInstance(ClassInfo clazz) {
return AnnotationInstance.create(ResteasyReactiveDotNames.TYPED, clazz,
new AnnotationValue[] { AnnotationValue.createArrayValue("value",
new AnnotationValue[] { AnnotationValue.createClassValue("value",
Type.create(clazz.name(), Type.Kind.CLASS)) }) });
}

private Collection<DotName> additionalContextTypes(List<ContextTypeBuildItem> contextTypeBuildItems) {
if (contextTypeBuildItems.isEmpty()) {
return CONTEXT_TYPES;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,14 +7,21 @@
import java.io.IOException;
import java.io.OutputStream;
import java.io.UncheckedIOException;
import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.nio.charset.StandardCharsets;

import javax.annotation.Priority;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Priorities;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.ext.Provider;

import org.jboss.resteasy.reactive.server.providers.serialisers.ServerStringMessageBodyHandler;
import org.jboss.resteasy.reactive.server.spi.ResteasyReactiveResourceInfo;
import org.jboss.resteasy.reactive.server.spi.ServerMessageBodyWriter;
import org.jboss.resteasy.reactive.server.spi.ServerRequestContext;
import org.jboss.shrinkwrap.api.asset.StringAsset;
import org.junit.jupiter.api.Test;
Expand All @@ -30,7 +37,7 @@ public class ChunkedResponseTest {
static QuarkusUnitTest runner = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(HelloResource.class)
.addAsResource(new StringAsset("quarkus.rest.output-buffer-size = 256"),
.addAsResource(new StringAsset("quarkus.resteasy-reactive.output-buffer-size = 256"),
"application.properties"));

@Test
Expand Down Expand Up @@ -68,14 +75,39 @@ public String helloSmall() {
}

@Provider
public static final class CustomStringMessageBodyWriter extends ServerStringMessageBodyHandler {
public static class CustomStringMessageBodyWriter implements ServerMessageBodyWriter<String> {

@Override
public void writeResponse(Object o, Type genericType, ServerRequestContext context)
public boolean isWriteable(Class<?> type, Type genericType, ResteasyReactiveResourceInfo target, MediaType mediaType) {
return true;
}

@Override
public void writeResponse(String o, Type genericType, ServerRequestContext context) throws WebApplicationException {
context.serverResponse().end(o);
}

public boolean isWriteable(Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType) {
return true;
}

public void writeTo(String o, Class<?> type, Type genericType, Annotation[] annotations, MediaType mediaType,
MultivaluedMap<String, Object> httpHeaders, OutputStream entityStream)
throws IOException, WebApplicationException {
entityStream.write(o.getBytes(StandardCharsets.UTF_8));
}
}

@Provider
@Priority(Priorities.USER + 1) // the spec says that when it comes to writers, higher number means higher priority...
public static final class CustomStringMessageBodyWriter2 extends CustomStringMessageBodyWriter {

@Override
public void writeResponse(String o, Type genericType, ServerRequestContext context)
throws WebApplicationException {

try (OutputStream stream = context.getOrCreateOutputStream()) {
stream.write(((String) o).getBytes());
stream.write(o.getBytes());
} catch (IOException e) {
throw new UncheckedIOException(e);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,7 +133,7 @@ public RuntimeValue<Deployment> createDeployment(DeploymentInfo info,
}

CurrentRequestManager
.setCurrentRequestInstance(new QuarkusCurrentRequest(beanContainer.instance(CurrentVertxRequest.class)));
.setCurrentRequestInstance(new QuarkusCurrentRequest(beanContainer.beanInstance(CurrentVertxRequest.class)));

BlockingOperationSupport.setIoThreadDetector(new BlockingOperationSupport.IOThreadDetector() {
@Override
Expand Down
Loading

0 comments on commit f7509ba

Please sign in to comment.