Skip to content

RESTEasy Reactive (Server)

Stéphane Épardaud edited this page Aug 23, 2023 · 2 revisions

This page documents some of the internals of RESTEasy Reactive.

@BeanParam classes aka Parameter Containers

These are classes that contain other request parameters, such as @FormParam, @HeaderParam, @PathParam, @MatrixParam, @CookieParam, @QueryParam or other parameter containers.

Typically, these parameters are defined as fields on the endpoint, or as method parameters, but for convenience or reusability we can group them into parameter containers. The endpoint parameter which is a parameter container may be annotated with @BeanParam but it is not required because we can autodetect them based on the presence of its annotated fields.

NOTE: we treat endpoint classes as parameter containers if they have parameter fields, so it's exactly the same code.

These are documented for the users at https://quarkus.io/guides/resteasy-reactive#grouping-parameters-in-a-custom-class but this documents how this is implemented.

Generally speaking, we have a transformer (in ClassInjectorTransformer) that transforms these parameter container classes by making them implement the ResteasyReactiveInjectionTarget interface, and implementing an __quarkus_rest_inject method which takes care of populating the fields by using the request context.

Here is an example of the transformation we apply:

import java.io.File;
import java.lang.reflect.Type;
import java.util.Collection;
import java.util.List;

import org.jboss.resteasy.reactive.PartType;
import org.jboss.resteasy.reactive.RestForm;
import org.jboss.resteasy.reactive.common.util.DeploymentUtils;
import org.jboss.resteasy.reactive.multipart.FileUpload;
import org.jboss.resteasy.reactive.server.core.Deployment;
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
import org.jboss.resteasy.reactive.server.core.multipart.MultipartSupport;
import org.jboss.resteasy.reactive.server.core.parameters.converters.CharParamConverter;
import org.jboss.resteasy.reactive.server.core.parameters.converters.ListConverter;
import org.jboss.resteasy.reactive.server.core.parameters.converters.ParameterConverter;
import org.jboss.resteasy.reactive.server.injection.ResteasyReactiveInjectionContext;
import org.jboss.resteasy.reactive.server.injection.ResteasyReactiveInjectionTarget;

import jakarta.ws.rs.BadRequestException;
import jakarta.ws.rs.DefaultValue;
import jakarta.ws.rs.WebApplicationException;
import jakarta.ws.rs.core.MediaType;

class X {}

class OtherBeanParamClass /* generated if our supertype is not already injectable: */ implements ResteasyReactiveInjectionTarget {
	// ...
	@Override
	public void __quarkus_rest_inject(ResteasyReactiveInjectionContext ctx) {
		// ...
	}
}

class BeanParamClass /* generated if our supertype is not already injectable: */ implements ResteasyReactiveInjectionTarget {
	@DefaultValue("default")
	@RestForm
	String regular;

	@RestForm
	char converted;

	@RestForm
	List<String> list;

	@RestForm
	List<Character> convertedList;

	@RestForm
	File multipartSpecial;

	@RestForm
	@PartType(MediaType.APPLICATION_JSON)
	X multipartViaMessageBodyReader;
	
	OtherBeanParamClass otherBeanParamClass;
	
	// the rest of this class is generated
	
	private static ParameterConverter __quarkus_converter__converted;

	// this will be called at startup
	public static void __quarkus_init_converter__converted(Deployment deployment) {
		ParameterConverter converter = deployment.getRuntimeParamConverter(BeanParamClass.class, "converted", true);
		// we have predefined ones in some cases
		if(converter == null) {
			converter = new CharParamConverter();
		}
		__quarkus_converter__converted = converter;
	}

	private static ParameterConverter __quarkus_converter__convertedList;

	// this will be called at startup
	public static void __quarkus_init_converter__convertedList(Deployment deployment) {
		ParameterConverter converter = deployment.getRuntimeParamConverter(BeanParamClass.class, "convertedList", true);
		// we have predefined ones in some cases
		if(converter == null) {
			converter = new CharParamConverter();
		}
		// this is a collection
		converter = new ListConverter(converter);
		__quarkus_converter__convertedList = converter;
	}

	private static Class multipartViaMessageBodyReader_type;
	private static Type multipartViaMessageBodyReader_genericType;
	private static MediaType multipartViaMessageBodyReader_mediaType;

	static {
		Class var0 = DeploymentUtils.loadClass("com.example.X");
		// Note that in the case of collections or arrays, type/genericType represents the element type
		multipartViaMessageBodyReader_type = var0;
		// or TypeSignatureParser.parse("Ljava/util/Map<Ljava/lang/String;Ljava/lang/String;>;"); for generic types
		multipartViaMessageBodyReader_genericType = var0;
		multipartViaMessageBodyReader_mediaType = MediaType.valueOf("application/json");
	}

	@Override
	public void __quarkus_rest_inject(ResteasyReactiveInjectionContext ctx) {
		// if our supertype is injectable
		//	  super.__quarkus_rest_inject(ctx);

		// a regular field with no converter
		try {
			Object val = ctx.getFormParameter("regular", true, true);
			// if we have a default value
			if(val == null) {
				val = "default";
			}
			if(val != null) {
				regular = (String)val;
			}
		} catch (WebApplicationException x) {
			throw x;
		} catch (Throwable x) {
			throw new BadRequestException();
		}

		// a converted field
		try {
			Object val = ctx.getFormParameter("converted", true, true);
			if(val != null) {
				converted = (char) __quarkus_converter__converted.convert(val);
			}
		} catch (Throwable x){ /* omitted */}

		// a collection field
		try {
			Object val = ctx.getFormParameter("list", true, true);
			if(val != null && !((Collection)val).isEmpty()) {
				list = (List) val;
			}
		} catch (Throwable x){ /* omitted */}

		// a converted collection field
		try {
			Object val = ctx.getFormParameter("convertedList", true, true);
			if(val != null && !((Collection)val).isEmpty()) {
				convertedList = (List) __quarkus_converter__convertedList.convert(val);
			}
		} catch (Throwable x){ /* omitted */}

		// a special multipart type
		try {
			// there are variants for List<X> or X[] or even for name.equals(FileUpload.ALL)
			// where X is FileUpload, String, byte[], File, Path, InputStream
			FileUpload val = MultipartSupport.getFileUpload("multipartSpecial", (ResteasyReactiveRequestContext) ctx);
			if(val != null) {
				multipartSpecial = val.uploadedFile().toFile();
			}
		} catch (Throwable x){ /* omitted */}

		// a multipart via MessageBodyReader
		try {
			// there are variants for List<X> or X[]
			Object val = MultipartSupport.getConvertedFormAttribute("multipartViaMessageBodyReader", multipartViaMessageBodyReader_type, multipartViaMessageBodyReader_genericType,
					multipartViaMessageBodyReader_mediaType,
					(ResteasyReactiveRequestContext) ctx);
			if(val != null) {
				multipartViaMessageBodyReader = (X) val;
			}
		} catch (Throwable x){ /* omitted */}
		
		// another bean param class
		otherBeanParamClass = new OtherBeanParamClass();
		otherBeanParamClass.__quarkus_rest_inject(ctx);
	}
}