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

wip 43705 #45233

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
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
Expand Up @@ -15,6 +15,7 @@
import java.util.concurrent.CompletionStage;
import java.util.concurrent.TimeUnit;
import java.util.function.Consumer;
import java.util.stream.Collectors;

import jakarta.enterprise.event.Event;
import jakarta.servlet.AsyncContext;
Expand All @@ -24,6 +25,7 @@
import jakarta.servlet.WriteListener;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.ws.rs.core.MultivaluedHashMap;
import jakarta.ws.rs.core.SecurityContext;

import org.jboss.resteasy.reactive.server.core.Deployment;
Expand Down Expand Up @@ -262,6 +264,25 @@ public List<String> getAllQueryParams(String name) {
return context.queryParam(name);
}

@Override
public Map<String, List<String>> getParametersMap() {
MultiMap entries = context.request().params();
final MultivaluedHashMap<String, String> result = new MultivaluedHashMap<>();
if (!entries.isEmpty()) {
for (Map.Entry<String, String> entry : entries) {
result.add(entry.getKey(), entry.getValue());
}

}
Map<String, List<String>> params = result.entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
Map.Entry::getValue // Values are already a List<String>
));

return params;
}

@Override
public String query() {
return request.getQueryString();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import java.lang.annotation.Annotation;
import java.lang.reflect.Type;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;
Expand All @@ -22,6 +24,7 @@

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.JavaType;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.type.TypeFactory;

Expand Down Expand Up @@ -87,17 +90,32 @@ public MapParamConverter(Class<T> rawType, Type genericType) {
this.rawType = rawType;
}

@Override
public T fromString(String value) {
if (rawType.isAssignableFrom(String.class)) {
//noinspection unchecked
return (T) value;
}
try {
return genericType != null ? objectMapper.readValue(value, genericType)
: objectMapper.readValue(value, rawType);
JsonNode jsonNode = objectMapper.readTree(value);
if (jsonNode.isArray()) {
// Process as a list of maps and merge them into a single map
JavaType listType = objectMapper.getTypeFactory()
.constructCollectionType(List.class, rawType);
List<Map<String, Object>> list = objectMapper.readValue(value, listType);

Map<String, Object> mergedMap = new LinkedHashMap<>();
for (Map<String, Object> map : list) {
mergedMap.putAll(map);
}
return (T) mergedMap;
} else {
// single object
return genericType != null
? objectMapper.readValue(value, genericType)
: objectMapper.readValue(value, rawType);
}
} catch (JsonProcessingException e) {
throw (new RuntimeException(e));
throw new RuntimeException(e);
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
import org.jboss.resteasy.reactive.common.processor.scanning.ResteasyReactiveScanner;
import org.jboss.resteasy.reactive.common.processor.transformation.AnnotationsTransformer;
import org.jboss.resteasy.reactive.common.processor.transformation.Transformation;
import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor;
import org.jboss.resteasy.reactive.server.model.FixedHandlerChainCustomizer;
import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer;
import org.jboss.resteasy.reactive.server.processor.scanning.MethodScanner;
Expand All @@ -40,19 +41,24 @@
import io.quarkus.deployment.annotations.BuildProducer;
import io.quarkus.deployment.annotations.BuildStep;
import io.quarkus.deployment.builditem.CombinedIndexBuildItem;
import io.quarkus.deployment.builditem.GeneratedClassBuildItem;
import io.quarkus.resteasy.reactive.server.deployment.ResteasyReactiveVertxWebSocketIntegrationProcessor;
import io.quarkus.resteasy.reactive.server.runtime.websocket.VertxWebSocketRestHandler;
import io.quarkus.resteasy.reactive.server.spi.AnnotationsTransformerBuildItem;
import io.quarkus.resteasy.reactive.server.spi.MethodScannerBuildItem;
import io.quarkus.resteasy.reactive.spi.AdditionalResourceClassBuildItem;
import io.quarkus.resteasy.reactive.spi.ExceptionMapperBuildItem;
import io.quarkus.resteasy.server.common.spi.AdditionalJaxRsResourceMethodParamAnnotations;
import io.quarkus.spring.web.resteasy.reactive.runtime.ResponseEntityHandler;
import io.quarkus.spring.web.resteasy.reactive.runtime.ResponseStatusHandler;
import io.quarkus.spring.web.resteasy.reactive.runtime.SpringMultiValueMapParamExtractor;
import io.quarkus.spring.web.runtime.common.ResponseStatusExceptionMapper;

public class SpringWebResteasyReactiveProcessor {

private static final Logger LOGGER = Logger.getLogger(SpringWebResteasyReactiveProcessor.class.getName());

public static final String NAME = ResteasyReactiveVertxWebSocketIntegrationProcessor.class.getName();
private static final DotName REST_CONTROLLER_ANNOTATION = DotName
.createSimple("org.springframework.web.bind.annotation.RestController");

Expand Down Expand Up @@ -82,6 +88,7 @@ public class SpringWebResteasyReactiveProcessor {

private static final DotName HTTP_ENTITY = DotName.createSimple("org.springframework.http.HttpEntity");
private static final DotName RESPONSE_ENTITY = DotName.createSimple("org.springframework.http.ResponseEntity");
private static final DotName SPRING_MULTIVALUE_MAP = DotName.createSimple("org.springframework.util.MultiValueMap");

private static final String DEFAULT_NONE = "\n\t\t\n\t\t\n\uE000\uE001\uE002\n\t\t\t\t\n"; // from ValueConstants

Expand Down Expand Up @@ -395,6 +402,59 @@ private String replaceSpringWebWildcards(String methodPath) {
}));
}

@BuildStep
MethodScannerBuildItem scanner() {
return new MethodScannerBuildItem(new MethodScanner() {
@Override
public List<HandlerChainCustomizer> scan(MethodInfo method, ClassInfo actualEndpointClass,
Map<String, Object> methodContext) {
if (methodContext.containsKey(NAME)) {
return Collections.singletonList(new VertxWebSocketRestHandler());
}
return Collections.emptyList();
}

@Override
public ParameterExtractor handleCustomParameter(Type paramType, Map<DotName, AnnotationInstance> annotations,
boolean field, Map<String, Object> methodContext) {
if (paramType.name().equals(SPRING_MULTIVALUE_MAP)) {
methodContext.put(NAME, true);
return new SpringMultiValueMapParamExtractor();
}
return null;
}

@Override
public boolean isMethodSignatureAsync(MethodInfo info) {
for (var param : info.parameterTypes()) {
if (param.name().equals(SPRING_MULTIVALUE_MAP)) {
return true;
}
}
return false;
}
});
}

// @BuildStep
public void transformSpringParameters(CombinedIndexBuildItem index,
BuildProducer<GeneratedClassBuildItem> generatedClasses) {
for (ClassInfo classInfo : index.getIndex().getKnownClasses()) {
for (MethodInfo method : classInfo.methods()) {
for (Type paramType : method.parameterTypes()) {
if (paramType.name().toString().equals("org.springframework.util.MultiValueMap")) {
Type newType = Type.create(DotName.createSimple("jakarta.ws.rs.core.MultivaluedMap"), paramType.kind());
transformParameterType(method, paramType, newType);
}
}
}
}
}

private void transformParameterType(MethodInfo method, Type oldType, Type newType) {
//rewrite the method using the jakarta multivalued type
}

@BuildStep
public MethodScannerBuildItem responseEntitySupport() {
return new MethodScannerBuildItem(new MethodScanner() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package io.quarkus.spring.web.resteasy.reactive.runtime;

import jakarta.ws.rs.ext.ParamConverter;
import jakarta.ws.rs.ext.Provider;

import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;

@Provider
public class MultiValueMapConverter implements ParamConverter<MultiValueMap<String, String>> {

@Override
public MultiValueMap<String, String> fromString(String value) {
MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
String[] pairs = value.split("&");
for (String pair : pairs) {
String[] keyValue = pair.split("=");
map.add(keyValue[0], keyValue[1]);
}
return map;
}

@Override
public String toString(MultiValueMap<String, String> value) {
return value.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package io.quarkus.spring.web.resteasy.reactive.runtime;

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

import jakarta.ws.rs.ext.ParamConverter;
import jakarta.ws.rs.ext.ParamConverterProvider;
import jakarta.ws.rs.ext.Provider;

import org.springframework.util.MultiValueMap;

@Provider
public class MultiValueMapConverterProvider implements ParamConverterProvider {
@Override
public <T> ParamConverter<T> getConverter(Class<T> rawType, Type genericType, Annotation[] annotations) {
if (rawType == MultiValueMap.class) {
return (ParamConverter<T>) new MultiValueMapConverter();
}
return null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package io.quarkus.spring.web.resteasy.reactive.runtime;

import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
import org.jboss.resteasy.reactive.server.core.parameters.ParameterExtractor;

@SuppressWarnings("ForLoopReplaceableByForEach")
public class SpringMultiValueMapParamExtractor implements ParameterExtractor {

// private final String name;
// private final boolean single;
// private final boolean encoded;
// private final String separator;

// public SpringMultiValueMapParamExtractor(String name, boolean single, boolean encoded, String separator) {
// this.name = name;
// this.single = single;
// this.encoded = encoded;
// this.separator = separator;
// }

@Override
public Object extractParameter(ResteasyReactiveRequestContext context) {
return context.serverRequest().getParametersMap();
// return context.getQueryParameter(name, single, encoded, separator);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package io.quarkus.spring.web.resteasy.reactive.runtime;

import java.util.Collections;
import java.util.List;

import org.jboss.resteasy.reactive.common.model.ResourceClass;
import org.jboss.resteasy.reactive.server.core.ResteasyReactiveRequestContext;
import org.jboss.resteasy.reactive.server.model.HandlerChainCustomizer;
import org.jboss.resteasy.reactive.server.model.ServerResourceMethod;
import org.jboss.resteasy.reactive.server.spi.ServerRestHandler;

public class SpringRestHandler implements HandlerChainCustomizer {

private static final ServerRestHandler[] AWOL = new ServerRestHandler[] {
new ServerRestHandler() {

@Override
public void handle(ResteasyReactiveRequestContext requestContext)
throws Exception {
throw new IllegalStateException("FAILURE: should never be restarted");
}
}
};

@Override
public List<ServerRestHandler> handlers(Phase phase, ResourceClass resourceClass, ServerResourceMethod resourceMethod) {
if (phase == Phase.AFTER_METHOD_INVOKE) {
return Collections.singletonList(new ServerRestHandler() {
@Override
public void handle(ResteasyReactiveRequestContext requestContext) throws Exception {
//make sure that we are never restarted
requestContext.restart(AWOL, true);
requestContext.suspend(); //we never resume
}
});
}
return Collections.emptyList();

}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package io.quarkus.spring.web.requestparam;

import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.stream.Collectors;

import jakarta.ws.rs.core.MultivaluedMap;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping
public class RequestParamController {

@GetMapping("/api/foos")
@ResponseBody
public String getFoos(@RequestParam String id) {
return "ID: " + id;
}

@PostMapping("/api/foos")
@ResponseBody
public String addFoo(@RequestParam(name = "id") String fooId, @RequestParam String name) {
return "ID: " + fooId + " Name: " + name;
}

@GetMapping("/api/foos/notParamRequired")
@ResponseBody
public String getFoosNotParamRequired2(@RequestParam(required = false) String id) {
return "ID: " + id;
}

@GetMapping("/api/foos/optional")
@ResponseBody
public String getFoosOptional(@RequestParam Optional<String> id) {
return "ID: " + id.orElseGet(() -> "not provided");
}

@GetMapping("/api/foos/defaultValue")
@ResponseBody
public String getFoosDefaultValue(@RequestParam(defaultValue = "test") String id) {
return "ID: " + id;
}

@PostMapping("/api/foos/map")
@ResponseBody
public String updateFoos(@RequestParam Map<String, String> allParams) {
return "Parameters are " + allParams.entrySet();
}

@GetMapping("/api/foos/multivalue")
@ResponseBody
public String getFoosMultiValue(@RequestParam List<String> id) {
return "IDs are " + id;
}

@PostMapping("/api/foos/multiMap")
@ResponseBody
public String updateFoos(@RequestParam MultivaluedMap<String, String> allParams) {
String result = "";
for (Map.Entry<String, List<String>> entry : allParams.entrySet()) {
result = "Parameters are " + entry.getKey() + "=" + entry.getValue().stream().collect(Collectors.joining(", "));
}
return result;
}

}
Loading
Loading