Skip to content

Commit

Permalink
Rest-Client-Reactive: Allow FormParams to be used in BeanParams
Browse files Browse the repository at this point in the history
  • Loading branch information
fwippe committed Jan 29, 2022
1 parent 268fa09 commit bb8091d
Show file tree
Hide file tree
Showing 7 changed files with 467 additions and 127 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@
import org.jboss.resteasy.reactive.client.processor.beanparam.BeanParamItem;
import org.jboss.resteasy.reactive.client.processor.beanparam.ClientBeanParamInfo;
import org.jboss.resteasy.reactive.client.processor.beanparam.CookieParamItem;
import org.jboss.resteasy.reactive.client.processor.beanparam.FormParamItem;
import org.jboss.resteasy.reactive.client.processor.beanparam.HeaderParamItem;
import org.jboss.resteasy.reactive.client.processor.beanparam.Item;
import org.jboss.resteasy.reactive.client.processor.beanparam.PathParamItem;
Expand Down Expand Up @@ -851,11 +852,13 @@ A more full example of generated client (with sub-resource) can is at the bottom
AssignableResultHandle invocationBuilderRef = handleBeanParamMethod
.createVariable(Invocation.Builder.class);
handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0));
addBeanParamData(methodCreator, handleBeanParamMethod,
formParams = addBeanParamData(methodCreator, handleBeanParamMethod,
invocationBuilderRef, beanParam.getItems(),
methodCreator.getMethodParam(paramIdx), methodTarget, index,
restClientInterface.getClassName(),
methodCreator.getThis(),
handleBeanParamMethod.getThis());
handleBeanParamMethod.getThis(),
formParams);

handleBeanParamMethod.returnValue(invocationBuilderRef);
invocationBuilderEnrichers.put(handleBeanParamDescriptor, methodCreator.getMethodParam(paramIdx));
Expand Down Expand Up @@ -900,22 +903,8 @@ A more full example of generated client (with sub-resource) can is at the bottom
invocationBuilderEnrichers.put(handleHeaderDescriptor, methodCreator.getMethodParam(paramIdx));
} else if (param.parameterType == ParameterType.FORM) {
formParams = createIfAbsent(methodCreator, formParams);
ResultHandle convertedFormParam = methodCreator.invokeVirtualMethod(
MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, Object.class,
Class.class),
methodCreator.getThis(), methodCreator.getMethodParam(paramIdx),
methodCreator.loadClass(param.type));
ResultHandle isString = methodCreator.instanceOf(convertedFormParam, String.class);
BranchResult isStringBranch = methodCreator.ifTrue(isString);
isStringBranch.falseBranch().throwException(IllegalStateException.class,
"Form parameter '" + param.name
+ "' could not be converted to 'String' for REST Client interface '"
+ restClientInterface.getClassName() + "'. A proper implementation of '"
+ ParamConverter.class.getName() + "' needs to be returned by a '"
+ ParamConverterProvider.class.getName()
+ "' that is registered with the client via the @RegisterProvider annotation on the REST Client interface.");
isStringBranch.trueBranch().invokeInterfaceMethod(MULTIVALUED_MAP_ADD, formParams,
methodCreator.load(param.name), convertedFormParam);
addFormParam(methodCreator, param.name, methodCreator.getMethodParam(paramIdx), param.type,
restClientInterface.getClassName(), methodCreator.getThis(), formParams);
} else if (param.parameterType == ParameterType.MULTI_PART_FORM) {
if (multipartForm != null) {
throw new IllegalArgumentException("MultipartForm data set twice for method "
Expand Down Expand Up @@ -1142,12 +1131,14 @@ private void handleSubResourceMethod(List<JaxrsClientReactiveEnricherBuildItem>
AssignableResultHandle invocationBuilderRef = handleBeanParamMethod
.createVariable(Invocation.Builder.class);
handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0));
addBeanParamData(subMethodCreator, handleBeanParamMethod,
formParams = addBeanParamData(subMethodCreator, handleBeanParamMethod,
invocationBuilderRef, beanParam.getItems(),
paramValue, methodTarget, index,
interfaceClass.name().toString(),
subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()),
handleBeanParamMethod.readInstanceField(clientField,
handleBeanParamMethod.getThis()));
handleBeanParamMethod.getThis()),
formParams);

handleBeanParamMethod.returnValue(invocationBuilderRef);
invocationBuilderEnrichers.put(handleBeanParamDescriptor, paramValue);
Expand Down Expand Up @@ -1235,12 +1226,14 @@ private void handleSubResourceMethod(List<JaxrsClientReactiveEnricherBuildItem>
AssignableResultHandle invocationBuilderRef = handleBeanParamMethod
.createVariable(Invocation.Builder.class);
handleBeanParamMethod.assign(invocationBuilderRef, handleBeanParamMethod.getMethodParam(0));
addBeanParamData(subMethodCreator, handleBeanParamMethod,
formParams = addBeanParamData(subMethodCreator, handleBeanParamMethod,
invocationBuilderRef, beanParam.getItems(),
subMethodCreator.getMethodParam(paramIdx), methodTarget, index,
interfaceClass.name().toString(),
subMethodCreator.readInstanceField(clientField, subMethodCreator.getThis()),
handleBeanParamMethod.readInstanceField(clientField,
handleBeanParamMethod.getThis()));
handleBeanParamMethod.getThis()),
formParams);

handleBeanParamMethod.returnValue(invocationBuilderRef);
invocationBuilderEnrichers.put(handleBeanParamDescriptor,
Expand Down Expand Up @@ -2114,15 +2107,39 @@ private Optional<MethodInfo> getJavaMethod(ClassInfo interfaceClass, ResourceMet
return maybeMethod;
}

private void addBeanParamData(BytecodeCreator methodCreator,
private AssignableResultHandle addBeanParamData(MethodCreator methodCreator,
BytecodeCreator invocationBuilderEnricher, // Invocation.Builder executePut$$enrichInvocationBuilder${noOfBeanParam}(Invocation.Builder)
AssignableResultHandle invocationBuilder,
List<Item> beanParamItems,
ResultHandle param,
AssignableResultHandle target, // can only be used in the current method, not in `invocationBuilderEnricher`
IndexView index,
String restClientInterfaceClassName,
ResultHandle client,
ResultHandle invocationEnricherClient) { // this client or containing client if this is a sub-client
ResultHandle invocationEnricherClient, // this client or containing client if this is a sub-client
AssignableResultHandle formParams) {
// Form params collector must be initialized at method root level before any inner blocks that may use it
if (areFormParamsDefinedIn(beanParamItems)) {
formParams = createIfAbsent(methodCreator, formParams);
}

addSubBeanParamData(methodCreator, invocationBuilderEnricher, invocationBuilder, beanParamItems, param, target,
index, restClientInterfaceClassName, client, invocationEnricherClient, formParams);

return formParams;
}

private void addSubBeanParamData(BytecodeCreator methodCreator,
BytecodeCreator invocationBuilderEnricher, // Invocation.Builder executePut$$enrichInvocationBuilder${noOfBeanParam}(Invocation.Builder)
AssignableResultHandle invocationBuilder,
List<Item> beanParamItems,
ResultHandle param,
AssignableResultHandle target, // can only be used in the current method, not in `invocationBuilderEnricher`
IndexView index,
String restClientInterfaceClassName,
ResultHandle client,
ResultHandle invocationEnricherClient, // this client or containing client if this is a sub-client
AssignableResultHandle formParams) {
BytecodeCreator creator = methodCreator.ifNotNull(param).trueBranch();
BytecodeCreator invoEnricher = invocationBuilderEnricher.ifNotNull(invocationBuilderEnricher.getMethodParam(1))
.trueBranch();
Expand All @@ -2131,8 +2148,9 @@ private void addBeanParamData(BytecodeCreator methodCreator,
case BEAN_PARAM:
BeanParamItem beanParamItem = (BeanParamItem) item;
ResultHandle beanParamElementHandle = beanParamItem.extract(creator, param);
addBeanParamData(creator, invoEnricher, invocationBuilder, beanParamItem.items(),
beanParamElementHandle, target, index, client, invocationEnricherClient);
addSubBeanParamData(creator, invoEnricher, invocationBuilder, beanParamItem.items(),
beanParamElementHandle, target, index, restClientInterfaceClassName, client,
invocationEnricherClient, formParams);
break;
case QUERY_PARAM:
QueryParamItem queryParam = (QueryParamItem) item;
Expand Down Expand Up @@ -2162,12 +2180,33 @@ private void addBeanParamData(BytecodeCreator methodCreator,
pathParam.getPathParamName(),
pathParam.extract(creator, param), pathParam.getParamType(), client);
break;
case FORM_PARAM:
FormParamItem formParam = (FormParamItem) item;
addFormParam(creator, formParam.getFormParamName(), formParam.extract(creator, param),
formParam.getParamType(), restClientInterfaceClassName, client, formParams);
break;
default:
throw new IllegalStateException("Unimplemented"); // TODO form params, etc
throw new IllegalStateException("Unimplemented");
}
}
}

private boolean areFormParamsDefinedIn(List<Item> beanParamItems) {
for (Item item : beanParamItems) {
switch (item.type()) {
case FORM_PARAM:
return true;
case BEAN_PARAM:
if (areFormParamsDefinedIn(((BeanParamItem) item).items())) {
return true;
}
break;
}
}

return false;
}

// takes a result handle to target as one of the parameters, returns a result handle to a modified target
private ResultHandle addQueryParam(BytecodeCreator methodCreator,
ResultHandle target,
Expand Down Expand Up @@ -2250,6 +2289,26 @@ private void addPathParam(BytecodeCreator methodCreator, AssignableResultHandle
methodCreator.load(paramName), handle));
}

private void addFormParam(BytecodeCreator methodCreator, String paramName, ResultHandle formParamHandle,
String parameterType, String restClientInterfaceClassName,
ResultHandle client, AssignableResultHandle formParams) {
ResultHandle convertedFormParam = methodCreator.invokeVirtualMethod(
MethodDescriptor.ofMethod(RestClientBase.class, "convertParam", Object.class, Object.class, Class.class),
client, formParamHandle,
methodCreator.loadClass(parameterType));
ResultHandle isString = methodCreator.instanceOf(convertedFormParam, String.class);
BranchResult isStringBranch = methodCreator.ifTrue(isString);
isStringBranch.falseBranch().throwException(IllegalStateException.class,
"Form parameter '" + paramName
+ "' could not be converted to 'String' for REST Client interface '"
+ restClientInterfaceClassName + "'. A proper implementation of '"
+ ParamConverter.class.getName() + "' needs to be returned by a '"
+ ParamConverterProvider.class.getName()
+ "' that is registered with the client via the @RegisterProvider annotation on the REST Client interface.");
isStringBranch.trueBranch().invokeInterfaceMethod(MULTIVALUED_MAP_ADD, formParams,
methodCreator.load(paramName), convertedFormParam);
}

private void addCookieParam(BytecodeCreator invoBuilderEnricher, AssignableResultHandle invocationBuilder,
String paramName, ResultHandle cookieParamHandle, String paramType, ResultHandle client) {
cookieParamHandle = invoBuilderEnricher.invokeVirtualMethod(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
package io.quarkus.rest.client.reactive.beanparam;

import static org.assertj.core.api.Assertions.assertThat;

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.net.URI;

import javax.ws.rs.BeanParam;
import javax.ws.rs.Consumes;
import javax.ws.rs.FormParam;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.ext.ParamConverterProvider;

import org.eclipse.microprofile.rest.client.RestClientBuilder;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.quarkus.test.common.http.TestHTTPResource;

public class BeanFormParamTest {
@RegisterExtension
static final QuarkusUnitTest TEST = new QuarkusUnitTest();

@TestHTTPResource
URI baseUri;

@Test
void shouldPassFormParamsFromBeanParam() {
assertThat(formTestClient().postFormParams(new BeanWithFormParams("value1", "value2", Param.SECOND)))
.isEqualTo(
"received value1-value2-2");
}

private FormTestClient formTestClient() {
return RestClientBuilder.newBuilder().baseUri(baseUri).register(ParamConverter.class).build(FormTestClient.class);
}

@Path("/form")
public interface FormTestClient {
@POST
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
String postFormParams(@BeanParam BeanWithFormParams beanParam);
}

public static class BeanWithFormParams {
private final String param1;
private final String param2;
private final Param param3;

public BeanWithFormParams(String param1, String param2, Param param3) {
this.param1 = param1;
this.param2 = param2;
this.param3 = param3;
}

@FormParam("param1")
public String getParam1() {
return param1;
}

@FormParam("param2")
public String getParam2() {
return param2;
}

@FormParam("param3")
public Param getParam3() {
return param3;
}
}

@Path("/form")
public static class FormTestResource {
@POST
public String post(@FormParam("param1") String param1, @FormParam("param2") String param2,
@FormParam("param3") String param3) {
return String.format("received %s-%s-%s", param1, param2, param3);
}
}

enum Param {
FIRST,
SECOND
}

public static class ParamConverter implements ParamConverterProvider {
@SuppressWarnings("unchecked")
@Override
public <T> javax.ws.rs.ext.ParamConverter<T> getConverter(Class<T> rawType, Type genericType,
Annotation[] annotations) {
if (rawType == BeanFormParamTest.Param.class) {
return (javax.ws.rs.ext.ParamConverter<T>) new javax.ws.rs.ext.ParamConverter<BeanFormParamTest.Param>() {
@Override
public BeanFormParamTest.Param fromString(String value) {
return null;
}

@Override
public String toString(BeanFormParamTest.Param value) {
if (value == null) {
return null;
}
switch (value) {
case FIRST:
return "1";
case SECOND:
return "2";
default:
return "unexpected";
}
}
};
}
return null;
}
}
}
13 changes: 8 additions & 5 deletions independent-projects/resteasy-reactive/client/processor/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -38,23 +38,26 @@
<groupId>jakarta.enterprise</groupId>
<artifactId>jakarta.enterprise.cdi-api</artifactId>
</dependency>

<dependency>
<groupId>jakarta.annotation</groupId>
<artifactId>jakarta.annotation-api</artifactId>
</dependency>
<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
</dependency>

<!-- Test -->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.jboss.logging</groupId>
<artifactId>jboss-logging</artifactId>
<groupId>org.assertj</groupId>
<artifactId>assertj-core</artifactId>
<scope>test</scope>
</dependency>

</dependencies>

</project>
Loading

0 comments on commit bb8091d

Please sign in to comment.