Skip to content

Commit

Permalink
Merge pull request #41294 from gsmet/3.11.3-backports-2
Browse files Browse the repository at this point in the history
[3.11] 3.11.3 backports 2
  • Loading branch information
gsmet authored Jun 19, 2024
2 parents 912ee1a + 8b78a0c commit 1eef1c7
Show file tree
Hide file tree
Showing 14 changed files with 498 additions and 13 deletions.
1 change: 1 addition & 0 deletions docs/src/main/asciidoc/http-reference.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -388,6 +388,7 @@ include::{generated-dir}/config/quarkus-vertx-http-config-group-access-log-confi
|First line of the request | `%r` | `%{REQUEST_LINE}`
|HTTP status code of the response | `%s` | `%{RESPONSE_CODE}`
|Date and time, in Common Log Format format | `%t` | `%{DATE_TIME}`
|Date and time as defined by a DateTimeFormatter compliant string | | `%{time,date_fime_formatter_string}`
|Remote user that was authenticated | `%u` | `%{REMOTE_USER}`
|Requested URL path | `%U` | `%{REQUEST_URL}`
|Request relative path | `%R` | `%{REQUEST_PATH}`
Expand Down
59 changes: 59 additions & 0 deletions docs/src/main/asciidoc/rest.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -2865,6 +2865,65 @@ public class Endpoint {
}
----

==== Separating Query parameter values

Normally a collection of `String` values is used to capture the values used in multiple occurrences of the same query parameter.
For example, for the following resource method:

[source,java]
----
@Path("hello")
public static class HelloResource {
@GET
public String hello(@RestQuery("name") List<String> names) {
if (names.isEmpty()) {
return "hello world";
} else {
return "hello " + String.join(" ", names);
}
}
}
----

and the following request:

[source,http]
----
GET /hello?name=foo&name=bar HTTP/1.1
----

the `names` variable will contain both `foo` and `bar` and the response will be `hello foo bar`.

It is not uncommon however to need to convert a single query parameter into a collection of values based on some delimiting character. That is where the `@org.jboss.resteasy.reactive.Separator` annotation comes into play.

If we update the resource method to:

[source,java]
----
@Path("hello")
public static class HelloResource {
@GET
public String hello(@RestQuery("name") @Separator(",") List<String> names) {
if (names.isEmpty()) {
return "hello world";
} else {
return "hello " + String.join(" ", names);
}
}
}
----

and use the following request:

[source,http]
----
GET /hello?name=foo,bar HTTP/1.1
----

then the response will be `hello foo bar`.

==== Handling dates

Quarkus REST supports the use of the implementations of `java.time.Temporal` (like `java.time.LocalDateTime`) as query, path, or form params.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -222,6 +222,7 @@ public void build(
BuildProducer<ExtensionSslNativeSupportBuildItem> sslNativeSupport) {
final Set<DotName> toRegister = new HashSet<>();

nativeLibs.produce(new NativeImageResourceBuildItem("kafka/kafka-version.properties"));
collectImplementors(toRegister, indexBuildItem, Serializer.class);
collectImplementors(toRegister, indexBuildItem, Deserializer.class);
collectImplementors(toRegister, indexBuildItem, Partitioner.class);
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.quarkus.resteasy.reactive.server.test.preconditions;

import static io.restassured.RestAssured.get;

import java.time.Instant;
import java.util.Date;

import jakarta.ws.rs.GET;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.Request;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.Response.ResponseBuilder;

import org.hamcrest.Matchers;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;

import io.quarkus.test.QuarkusUnitTest;
import io.restassured.RestAssured;

public class DatePreconditionTests {

@RegisterExtension
static final QuarkusUnitTest config = new QuarkusUnitTest()
.withApplicationRoot((jar) -> jar
.addClasses(Resource.class));

// Make sure we test a subtype of Date, since that is what Hibernate ORM gives us most of the time (hah)
// Also make sure we have non-zero milliseconds, since that will be the case for most date values representing
// "now", and we want to make sure pre-conditions work (second-resolution)
static final Date date = new Date(Date.from(Instant.parse("2007-12-03T10:15:30.24Z")).getTime()) {
};

public static class Something {
}

@Test
public void test() {
get("/preconditions")
.then()
.statusCode(200)
.header("Last-Modified", "Mon, 03 Dec 2007 10:15:30 GMT")
.body(Matchers.equalTo("foo"));
RestAssured
.with()
.header("If-Modified-Since", "Mon, 03 Dec 2007 10:15:30 GMT")
.get("/preconditions")
.then()
.statusCode(304);
}

@Path("/preconditions")
public static class Resource {
@GET
public Response get(Request request) {
ResponseBuilder resp = request.evaluatePreconditions(date);
if (resp != null) {
return resp.build();
}
return Response.ok("foo").lastModified(date).build();
}
}
}
2 changes: 1 addition & 1 deletion independent-projects/bootstrap/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@
<plexus-sec-dispatcher.version>2.0</plexus-sec-dispatcher.version>
<plexus-utils.version>3.5.1</plexus-utils.version>
<smallrye-common.version>2.3.0</smallrye-common.version>
<smallrye-beanbag.version>1.4.1</smallrye-beanbag.version>
<smallrye-beanbag.version>1.5.2</smallrye-beanbag.version>
<gradle-tooling.version>8.7</gradle-tooling.version>
<quarkus-fs-util.version>0.0.10</quarkus-fs-util.version>
<org-crac.version>0.1.3</org-crac.version>
Expand Down
2 changes: 1 addition & 1 deletion independent-projects/extension-maven-plugin/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@
<version.enforcer.plugin>3.2.1</version.enforcer.plugin>
<version.surefire.plugin>3.2.5</version.surefire.plugin>
<jackson-bom.version>2.17.1</jackson-bom.version>
<smallrye-beanbag.version>1.4.1</smallrye-beanbag.version>
<smallrye-beanbag.version>1.5.2</smallrye-beanbag.version>
<junit.jupiter.version>5.10.2</junit.jupiter.version>
</properties>
<build>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,10 @@ public <T> HeaderDelegate<T> createHeaderDelegate(Class<T> type) throws IllegalA
}
if (type.equals(MediaType.class)) {
return (HeaderDelegate<T>) MediaTypeHeaderDelegate.INSTANCE;
} else if (type.equals(Date.class)) {
} else if (Date.class.isAssignableFrom(type)) {
// for Date, we do subtypes too, because ORM will instantiate java.util.Date as subtypes
// and it's extremely likely we get those here, and we still have to generate a valid
// date representation for them, rather than Object.toString which will be wrong
return (HeaderDelegate<T>) DateDelegate.INSTANCE;
} else if (type.equals(CacheControl.class)) {
return (HeaderDelegate<T>) CacheControlDelegate.INSTANCE;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@ private boolean isRfc7232preconditions() {
return true;//todo: do we need config for this?
}

@Override
public Variant selectVariant(List<Variant> variants) throws IllegalArgumentException {
if (variants == null || variants.size() == 0)
throw new IllegalArgumentException("Variant list must not be empty");
Expand All @@ -53,7 +54,7 @@ public Variant selectVariant(List<Variant> variants) throws IllegalArgumentExcep
return negotiation.getBestMatch(variants);
}

public List<EntityTag> convertEtag(List<String> tags) {
private List<EntityTag> convertEtag(List<String> tags) {
ArrayList<EntityTag> result = new ArrayList<EntityTag>();
for (String tag : tags) {
String[] split = tag.split(",");
Expand All @@ -64,7 +65,7 @@ public List<EntityTag> convertEtag(List<String> tags) {
return result;
}

public Response.ResponseBuilder ifMatch(List<EntityTag> ifMatch, EntityTag eTag) {
private Response.ResponseBuilder ifMatch(List<EntityTag> ifMatch, EntityTag eTag) {
boolean match = false;
for (EntityTag tag : ifMatch) {
if (tag.equals(eTag) || tag.getValue().equals("*")) {
Expand All @@ -78,7 +79,7 @@ public Response.ResponseBuilder ifMatch(List<EntityTag> ifMatch, EntityTag eTag)

}

public Response.ResponseBuilder ifNoneMatch(List<EntityTag> ifMatch, EntityTag eTag) {
private Response.ResponseBuilder ifNoneMatch(List<EntityTag> ifMatch, EntityTag eTag) {
boolean match = false;
for (EntityTag tag : ifMatch) {
if (tag.equals(eTag) || tag.getValue().equals("*")) {
Expand All @@ -96,6 +97,7 @@ public Response.ResponseBuilder ifNoneMatch(List<EntityTag> ifMatch, EntityTag e
return null;
}

@Override
public Response.ResponseBuilder evaluatePreconditions(EntityTag eTag) {
if (eTag == null)
throw new IllegalArgumentException("ETag was null");
Expand All @@ -118,26 +120,36 @@ public Response.ResponseBuilder evaluatePreconditions(EntityTag eTag) {
return builder;
}

public Response.ResponseBuilder ifModifiedSince(String strDate, Date lastModified) {
private Response.ResponseBuilder ifModifiedSince(String strDate, Date lastModified) {
Date date = DateUtil.parseDate(strDate);

if (date.getTime() >= lastModified.getTime()) {
if (date.getTime() >= millisecondsWithSecondsPrecision(lastModified)) {
return Response.notModified();
}
return null;

}

public Response.ResponseBuilder ifUnmodifiedSince(String strDate, Date lastModified) {
private Response.ResponseBuilder ifUnmodifiedSince(String strDate, Date lastModified) {
Date date = DateUtil.parseDate(strDate);

if (date.getTime() >= lastModified.getTime()) {
if (date.getTime() >= millisecondsWithSecondsPrecision(lastModified)) {
return null;
}
return Response.status(Response.Status.PRECONDITION_FAILED).lastModified(lastModified);

}

/**
* We must compare header dates (seconds-precision) with dates that have the same precision,
* otherwise they may include milliseconds and they will never match the Last-Modified
* values that we generate from them (since we drop their milliseconds when we write the headers)
*/
private long millisecondsWithSecondsPrecision(Date lastModified) {
return (lastModified.getTime() / 1000) * 1000;
}

@Override
public Response.ResponseBuilder evaluatePreconditions(Date lastModified) {
if (lastModified == null)
throw new IllegalArgumentException("Param cannot be null");
Expand All @@ -159,6 +171,7 @@ public Response.ResponseBuilder evaluatePreconditions(Date lastModified) {
return builder;
}

@Override
public Response.ResponseBuilder evaluatePreconditions(Date lastModified, EntityTag eTag) {
if (lastModified == null)
throw new IllegalArgumentException("Last modified was null");
Expand All @@ -182,6 +195,7 @@ else if (lastModifiedBuilder == null && etagBuilder != null)
return rtn;
}

@Override
public Response.ResponseBuilder evaluatePreconditions() {
List<String> ifMatch = requestContext.getHttpHeaders().getRequestHeaders().get(HttpHeaders.IF_MATCH);
if (ifMatch == null || ifMatch.size() == 0) {
Expand Down

Large diffs are not rendered by default.

4 changes: 4 additions & 0 deletions independent-projects/tools/codestarts/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,10 @@
<groupId>io.quarkus.qute</groupId>
<artifactId>qute-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-artifact</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-bootstrap-app-model</artifactId>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletionStage;
import java.util.regex.Pattern;

import org.apache.commons.io.FilenameUtils;
import org.apache.maven.artifact.versioning.ComparableVersion;

import io.quarkus.devtools.codestarts.CodestartException;
import io.quarkus.devtools.codestarts.CodestartResource;
Expand All @@ -30,6 +32,7 @@ final class QuteCodestartFileReader implements CodestartFileReader {
private static final String ENTRY_QUTE_FLAG = ".entry.qute";
public static final String INCLUDE_QUTE_FLAG = ".include.qute";
public static final String SKIP_TAG = "<SKIP>";
private static final Pattern VERSION_PATTERN = Pattern.compile("^(\\d+)(\\.(\\d+))?(\\.(\\d+))?");

@Override
public boolean matches(String fileName) {
Expand Down Expand Up @@ -187,9 +190,24 @@ public CompletionStage<Object> resolve(EvalContext context) {
return CompletedStage.of(value.endsWith((String) e));
});
}

case "compareVersionTo":
if (context.getParams().size() == 1) {
return context.evaluate(context.getParams().get(0)).thenCompose(e -> {
return CompletedStage.of(compareVersionTo(value, (String) e));
});
}
default:
return Results.notFound(context);
}
}
}

static int compareVersionTo(String currentVersionString, String comparedVersionString) {
if (!VERSION_PATTERN.matcher(comparedVersionString).matches()) {
throw new IllegalArgumentException("Let's not put template condition on qualifier: " + comparedVersionString);
}
return new ComparableVersion(currentVersionString).compareTo(new ComparableVersion(comparedVersionString));
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,13 @@
import io.quarkus.devtools.codestarts.CodestartStructureException;
import io.quarkus.devtools.codestarts.core.reader.TargetFile;

/**
*
* @deprecated this was a quick-n-dirty way to allow extensions to provide content to the index.html,
* we don't need it anymore with the dynamic index.html. If we need something similar in the future let's find a
* more elegant way.
*/
@Deprecated
final class ContentMergeCodestartFileStrategyHandler implements CodestartFileStrategyHandler {

static final String NAME = "content-merge";
Expand Down Expand Up @@ -40,6 +47,10 @@ public void process(Path targetDirectory, String relativePath, List<TargetFile>
return;
}
createDirectories(targetPath);
writeFile(targetPath, template.get().getContent().replace("{merged-content}", mergedContent.toString()));
final String content = template.get().getContent();
if (content.isBlank()) {
return;
}
writeFile(targetPath, content.replace("{merged-content}", mergedContent.toString()));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package io.quarkus.devtools.codestarts.core.reader;

import static io.quarkus.devtools.codestarts.core.reader.QuteCodestartFileReader.*;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatThrownBy;

import org.junit.jupiter.api.Test;

class QuteCodestartFileReaderTest {

@Test
void testCompareVersion() {
assertThat(compareVersionTo("3.12.0.Final", "1.0")).isGreaterThan(0);
assertThat(compareVersionTo("3.12.0.Final", "3.12")).isEqualTo(0);
assertThat(compareVersionTo("3.13.0", "3.12")).isGreaterThan(0);
assertThat(compareVersionTo("3.2.1", "3.12")).isLessThan(0);
assertThat(compareVersionTo("999-SNAPSHOT", "3.12")).isGreaterThan(0);
assertThat(compareVersionTo("1.0.0.Final-redhat-00001", "1.0")).isGreaterThan(0);
assertThat(compareVersionTo("1.0.1.Final-redhat-00001", "1.0")).isGreaterThan(0);
assertThatThrownBy(() -> compareVersionTo("999-SNAP", "1.0.1.Final-redhat-00001"))
.isInstanceOf(IllegalArgumentException.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -154,10 +154,10 @@ private static List<String> getMavenUpdateCommand(String mvnBinary, String rewri
command.add(
String.format("%s:%s:%s:%s", MAVEN_REWRITE_PLUGIN_GROUP, MAVEN_REWRITE_PLUGIN_ARTIFACT, rewritePluginVersion,
dryRun ? "dryRun" : "run"));
command.add(String.format("-DplainTextMasks=%s", ADDITIONAL_SOURCE_FILES));
command.add(String.format("-Drewrite.plainTextMasks=%s", ADDITIONAL_SOURCE_FILES));
command.add(String.format("-Drewrite.configLocation=%s", recipe.toAbsolutePath()));
command.add(String.format("-Drewrite.recipeArtifactCoordinates=%s", recipesGAV));
command.add(String.format("-DactiveRecipes=%s", RECIPE_IO_QUARKUS_OPENREWRITE_QUARKUS));
command.add(String.format("-Drewrite.activeRecipes=%s", RECIPE_IO_QUARKUS_OPENREWRITE_QUARKUS));
command.add("-Drewrite.pomCacheEnabled=false");
final String mavenSettings = getMavenSettingsArg();
if (mavenSettings != null) {
Expand Down

0 comments on commit 1eef1c7

Please sign in to comment.