From 06caa543d6441e6aa7e62daec59515afed71e62a Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 9 Sep 2022 21:09:37 +0000 Subject: [PATCH 01/36] Bump kafka3.version from 3.2.1 to 3.2.2 Bumps `kafka3.version` from 3.2.1 to 3.2.2. Updates `kafka-clients` from 3.2.1 to 3.2.2 Updates `kafka-streams` from 3.2.1 to 3.2.2 Updates `kafka-streams-test-utils` from 3.2.1 to 3.2.2 Updates `kafka_2.13` from 3.2.1 to 3.2.2 --- updated-dependencies: - dependency-name: org.apache.kafka:kafka-clients dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.kafka:kafka-streams dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.kafka:kafka-streams-test-utils dependency-type: direct:production update-type: version-update:semver-patch - dependency-name: org.apache.kafka:kafka_2.13 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 5c60d85458a32..941daff544a10 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -138,7 +138,7 @@ 1.0.3 3.5.0.Final 1.7.0 - 3.2.1 + 3.2.2 1.8.0 1.1.8.4 0.100.0 From eee3dc2ce607093725baf69b5f98cd6eea618c6e Mon Sep 17 00:00:00 2001 From: Auri Munoz Date: Fri, 9 Sep 2022 16:02:49 +0200 Subject: [PATCH 02/36] update Stork version to align with k8s client 6.x Related to #6588 --- bom/application/pom.xml | 2 +- .../src/main/java/io/quarkus/stork/StorkConfigUtil.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 5c60d85458a32..828b66760ac41 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -53,7 +53,7 @@ 2.7.0 2.26.0 3.18.0 - 1.1.2 + 1.2.0 1.2.1 1.3.5 3.0.4 diff --git a/extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/StorkConfigUtil.java b/extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/StorkConfigUtil.java index db9d9f68b29a8..a87bb3f39b0f5 100644 --- a/extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/StorkConfigUtil.java +++ b/extensions/smallrye-stork/runtime/src/main/java/io/quarkus/stork/StorkConfigUtil.java @@ -16,9 +16,9 @@ public static List toStorkServiceConfig(StorkConfiguration storkC for (String serviceName : servicesConfigs) { builder.setServiceName(serviceName); ServiceConfiguration serviceConfiguration = storkConfiguration.serviceConfiguration.get(serviceName); - SimpleServiceConfig.SimpleServiceDiscoveryConfig storkServiceDiscoveryConfig = new SimpleServiceConfig.SimpleServiceDiscoveryConfig( + SimpleServiceConfig.SimpleConfigWithType storkServiceDiscoveryConfig = new SimpleServiceConfig.SimpleConfigWithType( serviceConfiguration.serviceDiscovery.type, serviceConfiguration.serviceDiscovery.params); - builder.setServiceDiscovery(storkServiceDiscoveryConfig); + builder = builder.setServiceDiscovery(storkServiceDiscoveryConfig); SimpleServiceConfig.SimpleLoadBalancerConfig loadBalancerConfig = new SimpleServiceConfig.SimpleLoadBalancerConfig( serviceConfiguration.loadBalancer.type, serviceConfiguration.loadBalancer.parameters); builder.setLoadBalancer(loadBalancerConfig); From 565f4d42888f2c308d3970b62e22e995059000f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9da=20Housni=20Alaoui?= Date: Fri, 22 Jul 2022 22:15:40 +0200 Subject: [PATCH 03/36] Quarkus NativeImageBuildStep fails with perm denied with docker rootless --- .../quarkus/deployment/IsDockerWorking.java | 27 ------ .../io/quarkus/deployment/OutputFilter.java | 31 +++++++ .../NativeImageBuildLocalContainerRunner.java | 90 +++++++++++++++++-- 3 files changed, 114 insertions(+), 34 deletions(-) create mode 100644 core/deployment/src/main/java/io/quarkus/deployment/OutputFilter.java diff --git a/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java b/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java index 861e77cb98b0f..61121cc405474 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/IsDockerWorking.java @@ -1,11 +1,8 @@ package io.quarkus.deployment; -import java.io.BufferedReader; import java.io.File; import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; import java.lang.reflect.InvocationTargetException; import java.net.InetSocketAddress; import java.net.Socket; @@ -15,7 +12,6 @@ import java.util.List; import java.util.Optional; import java.util.function.BooleanSupplier; -import java.util.function.Function; import java.util.function.Supplier; import org.eclipse.microprofile.config.ConfigProvider; @@ -176,29 +172,6 @@ public Result get() { } } - public static class OutputFilter implements Function { - private final StringBuilder builder = new StringBuilder(); - - @Override - public Runnable apply(InputStream is) { - return () -> { - - try (InputStreamReader isr = new InputStreamReader(is); - BufferedReader reader = new BufferedReader(isr)) { - - for (String line = reader.readLine(); line != null; line = reader.readLine()) { - builder.append(line); - } - } catch (IOException e) { - throw new RuntimeException("Error reading stream.", e); - } - }; - } - - public String getOutput() { - return builder.toString(); - } - } } private enum Result { diff --git a/core/deployment/src/main/java/io/quarkus/deployment/OutputFilter.java b/core/deployment/src/main/java/io/quarkus/deployment/OutputFilter.java new file mode 100644 index 0000000000000..1d3af6e190939 --- /dev/null +++ b/core/deployment/src/main/java/io/quarkus/deployment/OutputFilter.java @@ -0,0 +1,31 @@ +package io.quarkus.deployment; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.util.function.Function; + +public class OutputFilter implements Function { + private final StringBuilder builder = new StringBuilder(); + + @Override + public Runnable apply(InputStream is) { + return () -> { + + try (InputStreamReader isr = new InputStreamReader(is); + BufferedReader reader = new BufferedReader(isr)) { + + for (String line = reader.readLine(); line != null; line = reader.readLine()) { + builder.append(line); + } + } catch (IOException e) { + throw new RuntimeException("Error reading stream.", e); + } + }; + } + + public String getOutput() { + return builder.toString(); + } +} diff --git a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java index b63f2cb92886f..1be0521d44027 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/pkg/steps/NativeImageBuildLocalContainerRunner.java @@ -2,37 +2,112 @@ import static io.quarkus.deployment.pkg.steps.LinuxIDUtil.getLinuxID; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.LinkOption; import java.nio.file.Path; +import java.time.Duration; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.function.Predicate; +import java.util.stream.Collectors; import org.apache.commons.lang3.SystemUtils; +import org.jboss.logging.Logger; +import io.quarkus.deployment.OutputFilter; import io.quarkus.deployment.pkg.NativeConfig; +import io.quarkus.deployment.util.ExecUtil; import io.quarkus.deployment.util.FileUtil; import io.quarkus.runtime.util.ContainerRuntimeUtil; public class NativeImageBuildLocalContainerRunner extends NativeImageBuildContainerRunner { + private static final Logger LOGGER = Logger.getLogger(NativeImageBuildLocalContainerRunner.class.getName()); + public NativeImageBuildLocalContainerRunner(NativeConfig nativeConfig, Path outputDir) { super(nativeConfig, outputDir); if (SystemUtils.IS_OS_LINUX) { ArrayList containerRuntimeArgs = new ArrayList<>(Arrays.asList(baseContainerRuntimeArgs)); - String uid = getLinuxID("-ur"); - String gid = getLinuxID("-gr"); - if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) { - Collections.addAll(containerRuntimeArgs, "--user", uid + ":" + gid); - if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.PODMAN) { - // Needed to avoid AccessDeniedExceptions - containerRuntimeArgs.add("--userns=keep-id"); + if (isDockerRootless(containerRuntime)) { + Collections.addAll(containerRuntimeArgs, "--user", String.valueOf(0)); + } else { + String uid = getLinuxID("-ur"); + String gid = getLinuxID("-gr"); + if (uid != null && gid != null && !uid.isEmpty() && !gid.isEmpty()) { + Collections.addAll(containerRuntimeArgs, "--user", uid + ":" + gid); + if (containerRuntime == ContainerRuntimeUtil.ContainerRuntime.PODMAN) { + // Needed to avoid AccessDeniedExceptions + containerRuntimeArgs.add("--userns=keep-id"); + } } } baseContainerRuntimeArgs = containerRuntimeArgs.toArray(baseContainerRuntimeArgs); } } + private static boolean isDockerRootless(ContainerRuntimeUtil.ContainerRuntime containerRuntime) { + if (containerRuntime != ContainerRuntimeUtil.ContainerRuntime.DOCKER) { + return false; + } + String dockerEndpoint = fetchDockerEndpoint(); + // docker socket? + String socketUriPrefix = "unix://"; + if (dockerEndpoint == null || !dockerEndpoint.startsWith(socketUriPrefix)) { + return false; + } + String dockerSocket = dockerEndpoint.substring(socketUriPrefix.length()); + String currentUid = getLinuxID("-ur"); + if (currentUid == null || currentUid.isEmpty() || currentUid.equals(String.valueOf(0))) { + return false; + } + + int socketOwnerUid; + try { + socketOwnerUid = (int) Files.getAttribute(Path.of(dockerSocket), "unix:uid", LinkOption.NOFOLLOW_LINKS); + } catch (IOException e) { + LOGGER.infof("Owner UID lookup on '%s' failed with '%s'", dockerSocket, e.getMessage()); + return false; + } + return currentUid.equals(String.valueOf(socketOwnerUid)); + } + + private static String fetchDockerEndpoint() { + // DOCKER_HOST environment variable overrides the active context + String dockerHost = System.getenv("DOCKER_HOST"); + if (dockerHost != null) { + return dockerHost; + } + + OutputFilter outputFilter = new OutputFilter(); + if (!ExecUtil.execWithTimeout(new File("."), outputFilter, Duration.ofMillis(3000), + "docker", "context", "ls", "--format", + "'{{- if .Current -}} {{- .DockerEndpoint -}} {{- end -}}'")) { + LOGGER.debug("Docker context lookup didn't succeed in time"); + return null; + } + + Set endpoints = outputFilter.getOutput().lines() + .filter(Objects::nonNull) + .filter(Predicate.not(String::isBlank)) + .collect(Collectors.toSet()); + if (endpoints.size() == 1) { + return endpoints.stream().findFirst().orElse(null); + } + if (LOGGER.isDebugEnabled()) { + LOGGER.debugf("Found too many active Docker endpoints: [%s]", + endpoints.stream() + .map(endpoint -> String.format("'%s'", endpoint)) + .collect(Collectors.joining(","))); + } + return null; + } + @Override protected List getContainerRuntimeBuildArgs() { List containerRuntimeArgs = super.getContainerRuntimeBuildArgs(); @@ -45,4 +120,5 @@ protected List getContainerRuntimeBuildArgs() { volumeOutputPath + ":" + NativeImageBuildStep.CONTAINER_BUILD_VOLUME_PATH + ":z"); return containerRuntimeArgs; } + } From dba1b1efdd75043e82c084ee5d08df530c848d46 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Tue, 13 Sep 2022 21:10:54 +0200 Subject: [PATCH 04/36] Generate Quarkus Maven Plugin Config Docs resolves: #1204 --- .../annotation/processor/Constants.java | 2 + .../processor/generate_doc/ConfigDoc.java | 20 ++++ .../generate_doc/ConfigDocBuilder.java | 91 +++++++++++++++ .../generate_doc/ConfigDocWriter.java | 58 ++++------ .../processor/generate_doc/DocFormatter.java | 4 +- .../processor/generate_doc/JavaDocParser.java | 17 ++- .../generate_doc/MavenConfigDocBuilder.java | 98 ++++++++++++++++ .../SummaryTableDocFormatter.java | 48 +++++--- .../JavaDocConfigDescriptionParserTest.java | 6 + docs/pom.xml | 17 +++ .../main/asciidoc/quarkus-maven-plugin.adoc | 27 +++++ .../QuarkusMavenPluginDocsGenerator.java | 106 ++++++++++++++++++ 12 files changed, 437 insertions(+), 57 deletions(-) create mode 100644 core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoc.java create mode 100644 core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocBuilder.java create mode 100644 core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/MavenConfigDocBuilder.java create mode 100644 docs/src/main/asciidoc/quarkus-maven-plugin.adoc create mode 100644 docs/src/main/java/io/quarkus/docs/generation/QuarkusMavenPluginDocsGenerator.java diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java b/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java index 3f99646027c9b..60ad1ab17f006 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/Constants.java @@ -28,6 +28,8 @@ final public class Constants { public static final String DASH = "-"; public static final String ADOC_EXTENSION = ".adoc"; public static final String DIGIT_OR_LOWERCASE = "^[a-z0-9]+$"; + public static final String NEW_LINE = "\n"; + public static final String SECTION_TITLE_L1 = "= "; public static final String PARENT = "<>"; public static final String NO_DEFAULT = "<>"; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoc.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoc.java new file mode 100644 index 0000000000000..0ad3e5a327d4d --- /dev/null +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDoc.java @@ -0,0 +1,20 @@ +package io.quarkus.annotation.processor.generate_doc; + +import java.io.IOException; +import java.io.Writer; +import java.util.List; + +/** + * Represent one output file, its items are going to be appended to the file + */ +interface ConfigDoc { + + List getWriteItems(); + + /** + * An item is a summary table, note below the table, ... + */ + interface WriteItem { + void accept(Writer writer) throws IOException; + } +} diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocBuilder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocBuilder.java new file mode 100644 index 0000000000000..57edebb518529 --- /dev/null +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocBuilder.java @@ -0,0 +1,91 @@ +package io.quarkus.annotation.processor.generate_doc; + +import static io.quarkus.annotation.processor.Constants.SUMMARY_TABLE_ID_VARIABLE; +import static java.util.Objects.requireNonNull; + +import java.util.ArrayList; +import java.util.List; + +import io.quarkus.annotation.processor.Constants; + +/** + * {@link ConfigDoc} builder + */ +class ConfigDocBuilder { + + /** + * Declare AsciiDoc variable + */ + private static final String DECLARE_VAR = "\n:%s: %s\n"; + private final DocFormatter summaryTableDocFormatter; + protected final List writeItems = new ArrayList<>(); + + public ConfigDocBuilder() { + summaryTableDocFormatter = new SummaryTableDocFormatter(); + } + + protected ConfigDocBuilder(boolean showEnvVars) { + summaryTableDocFormatter = new SummaryTableDocFormatter(showEnvVars); + } + + /** + * Add documentation in a summary table and descriptive format + */ + public final ConfigDocBuilder addSummaryTable(String initialAnchorPrefix, boolean activateSearch, + List configDocItems, String fileName, + boolean includeConfigPhaseLegend) { + + writeItems.add(writer -> { + + // Create var with unique value for each summary table that will make DURATION_FORMAT_NOTE (see below) unique + var fileNameWithoutExtension = fileName.substring(0, fileName.length() - Constants.ADOC_EXTENSION.length()); + writer.append(String.format(DECLARE_VAR, SUMMARY_TABLE_ID_VARIABLE, fileNameWithoutExtension)); + + summaryTableDocFormatter.format(writer, initialAnchorPrefix, activateSearch, configDocItems, + includeConfigPhaseLegend); + + boolean hasDuration = false, hasMemory = false; + for (ConfigDocItem item : configDocItems) { + if (item.hasDurationInformationNote()) { + hasDuration = true; + } + + if (item.hasMemoryInformationNote()) { + hasMemory = true; + } + } + + if (hasDuration) { + writer.append(Constants.DURATION_FORMAT_NOTE); + } + + if (hasMemory) { + writer.append(Constants.MEMORY_SIZE_FORMAT_NOTE); + } + }); + return this; + } + + public boolean hasWriteItems() { + return !writeItems.isEmpty(); + } + + /** + * Passed strings are appended to the file + */ + public final ConfigDocBuilder write(String... strings) { + requireNonNull(strings); + writeItems.add(writer -> { + for (String str : strings) { + writer.append(str); + } + }); + return this; + } + + public final ConfigDoc build() { + final List docItemsCopy = List.copyOf(writeItems); + return () -> docItemsCopy; + } + +} diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocWriter.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocWriter.java index d7d99741ef315..b2baf426709a3 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocWriter.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/ConfigDocWriter.java @@ -1,65 +1,47 @@ package io.quarkus.annotation.processor.generate_doc; -import static io.quarkus.annotation.processor.Constants.SUMMARY_TABLE_ID_VARIABLE; - import java.io.IOException; import java.io.Writer; import java.nio.file.Files; import java.nio.file.Path; -import java.util.List; import io.quarkus.annotation.processor.Constants; final public class ConfigDocWriter { - private final DocFormatter summaryTableDocFormatter = new SummaryTableDocFormatter(); - private static final String DECLARE_VAR = "\n:%s: %s\n"; /** * Write all extension configuration in AsciiDoc format in `{root}/target/asciidoc/generated/config/` directory */ public void writeAllExtensionConfigDocumentation(ConfigDocGeneratedOutput output) throws IOException { - generateDocumentation(Constants.GENERATED_DOCS_PATH.resolve(output.getFileName()), output.getAnchorPrefix(), - output.isSearchable(), output.getConfigDocItems(), output.getFileName()); - } - /** - * Generate documentation in a summary table and descriptive format - * - */ - private void generateDocumentation(Path targetPath, String initialAnchorPrefix, boolean activateSearch, - List configDocItems, String fileName) - throws IOException { - if (configDocItems.isEmpty()) { + if (output.getConfigDocItems().isEmpty()) { return; } - try (Writer writer = Files.newBufferedWriter(targetPath)) { - - // Create var with unique value for each summary table that will make DURATION_FORMAT_NOTE (see below) unique - var fileNameWithoutExtension = fileName.substring(0, fileName.length() - Constants.ADOC_EXTENSION.length()); - writer.append(String.format(DECLARE_VAR, SUMMARY_TABLE_ID_VARIABLE, fileNameWithoutExtension)); - - summaryTableDocFormatter.format(writer, initialAnchorPrefix, activateSearch, configDocItems); + // Create single summary table + final var configDocBuilder = new ConfigDocBuilder().addSummaryTable(output.getAnchorPrefix(), output.isSearchable(), + output.getConfigDocItems(), output.getFileName(), true); - boolean hasDuration = false, hasMemory = false; - for (ConfigDocItem item : configDocItems) { - if (item.hasDurationInformationNote()) { - hasDuration = true; - } - - if (item.hasMemoryInformationNote()) { - hasMemory = true; - } - } + generateDocumentation(output.getFileName(), configDocBuilder); + } - if (hasDuration) { - writer.append(Constants.DURATION_FORMAT_NOTE); - } + public void generateDocumentation(String fileName, ConfigDocBuilder configDocBuilder) throws IOException { + generateDocumentation( + // Resolve output file path + Constants.GENERATED_DOCS_PATH.resolve(fileName), + // Write all items + configDocBuilder.build()); + } - if (hasMemory) { - writer.append(Constants.MEMORY_SIZE_FORMAT_NOTE); + private void generateDocumentation(Path targetPath, ConfigDoc configDoc) + throws IOException { + try (Writer writer = Files.newBufferedWriter(targetPath)) { + for (ConfigDoc.WriteItem writeItem : configDoc.getWriteItems()) { + // Write documentation item, f.e. summary table + writeItem.accept(writer); } } } + } diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocFormatter.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocFormatter.java index b33fe84401bb1..0564a820dbfc7 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocFormatter.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/DocFormatter.java @@ -61,8 +61,8 @@ default String getAnchor(String string) { return string.toLowerCase(); } - void format(Writer writer, String initialAnchorPrefix, boolean activateSearch, List configDocItems) - throws IOException; + void format(Writer writer, String initialAnchorPrefix, boolean activateSearch, List configDocItems, + boolean includeConfigPhaseLegend) throws IOException; void format(Writer writer, ConfigDocKey configDocKey) throws IOException; diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/JavaDocParser.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/JavaDocParser.java index 756f34b97c105..c0afad96a5ab2 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/JavaDocParser.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/JavaDocParser.java @@ -24,6 +24,7 @@ final class JavaDocParser { private static final Pattern START_OF_LINE = Pattern.compile("^", Pattern.MULTILINE); private static final Pattern REPLACE_WINDOWS_EOL = Pattern.compile("\r\n"); private static final Pattern REPLACE_MACOS_EOL = Pattern.compile("\r"); + private static final Pattern STARTING_SPACE = Pattern.compile("^ +"); private static final String BACKTICK = "`"; private static final String HASH = "#"; @@ -269,7 +270,21 @@ private void appendHtml(StringBuilder sb, Node node) { sb.append(NEW_LINE); break; case TEXT_NODE: - appendEscapedAsciiDoc(sb, ((TextNode) childNode).text()); + String text = ((TextNode) childNode).text(); + + if (text.isEmpty()) { + break; + } + + // Indenting the first line of a paragraph by one or more spaces makes the block literal + // Please see https://docs.asciidoctor.org/asciidoc/latest/verbatim/literal-blocks/ for more info + // This prevents literal blocks f.e. after
+ final var startingSpaceMatcher = STARTING_SPACE.matcher(text); + if (sb.length() > 0 && '\n' == sb.charAt(sb.length() - 1) && startingSpaceMatcher.find()) { + text = startingSpaceMatcher.replaceFirst(""); + } + + appendEscapedAsciiDoc(sb, text); break; default: appendHtml(sb, childNode); diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/MavenConfigDocBuilder.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/MavenConfigDocBuilder.java new file mode 100644 index 0000000000000..e55a495fbc1a3 --- /dev/null +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/MavenConfigDocBuilder.java @@ -0,0 +1,98 @@ +package io.quarkus.annotation.processor.generate_doc; + +import static io.quarkus.annotation.processor.Constants.EMPTY; +import static io.quarkus.annotation.processor.Constants.NEW_LINE; +import static io.quarkus.annotation.processor.Constants.SECTION_TITLE_L1; + +import java.util.ArrayList; +import java.util.List; + +import io.quarkus.annotation.processor.Constants; + +public final class MavenConfigDocBuilder extends ConfigDocBuilder { + + public MavenConfigDocBuilder() { + super(false); + } + + private final JavaDocParser javaDocParser = new JavaDocParser(); + + public void addTableTitle(String goalTitle) { + write(SECTION_TITLE_L1, goalTitle, NEW_LINE); + } + + public void addNewLine() { + write(NEW_LINE); + } + + public void addTableDescription(String goalDescription) { + write(NEW_LINE, javaDocParser.parseConfigDescription(goalDescription), NEW_LINE); + } + + public GoalParamsBuilder newGoalParamsBuilder() { + return new GoalParamsBuilder(javaDocParser); + } + + private static abstract class TableBuilder { + + protected final List configDocItems = new ArrayList<>(); + + /** + * Section name that is displayed in a table header + */ + abstract protected String getSectionName(); + + public List build() { + + // a summary table + final ConfigDocSection parameterSection = new ConfigDocSection(); + parameterSection.setShowSection(true); + parameterSection.setName(getSectionName()); + parameterSection.setSectionDetailsTitle(getSectionName()); + parameterSection.setOptional(false); + parameterSection.setConfigDocItems(List.copyOf(configDocItems)); + + // topConfigDocItem wraps the summary table + final ConfigDocItem topConfigDocItem = new ConfigDocItem(); + topConfigDocItem.setConfigDocSection(parameterSection); + + return List.of(topConfigDocItem); + } + + public boolean tableIsNotEmpty() { + return !configDocItems.isEmpty(); + } + } + + public static final class GoalParamsBuilder extends TableBuilder { + + private final JavaDocParser javaDocParser; + + private GoalParamsBuilder(JavaDocParser javaDocParser) { + this.javaDocParser = javaDocParser; + } + + public void addParam(String type, String name, String defaultValue, boolean required, String description) { + final ConfigDocKey configDocKey = new ConfigDocKey(); + configDocKey.setType(type); + configDocKey.setKey(name); + configDocKey.setConfigPhase(ConfigPhase.RUN_TIME); + configDocKey.setDefaultValue(defaultValue == null ? Constants.EMPTY : defaultValue); + if (description != null && !description.isBlank()) { + configDocKey.setConfigDoc(javaDocParser.parseConfigDescription(description)); + } else { + configDocKey.setConfigDoc(EMPTY); + } + configDocKey.setOptional(!required); + final ConfigDocItem configDocItem = new ConfigDocItem(); + configDocItem.setConfigDocKey(configDocKey); + configDocItems.add(configDocItem); + } + + @Override + protected String getSectionName() { + return "Parameter"; + } + } + +} diff --git a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java index 47935389f9c42..654b484967c47 100644 --- a/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java +++ b/core/processor/src/main/java/io/quarkus/annotation/processor/generate_doc/SummaryTableDocFormatter.java @@ -1,5 +1,7 @@ package io.quarkus.annotation.processor.generate_doc; +import static io.quarkus.annotation.processor.Constants.CONFIG_PHASE_LEGEND; +import static io.quarkus.annotation.processor.Constants.NEW_LINE; import static io.quarkus.annotation.processor.generate_doc.DocGeneratorUtil.toEnvVarName; import java.io.IOException; @@ -15,22 +17,34 @@ final class SummaryTableDocFormatter implements DocFormatter { public static final String CONFIGURATION_TABLE_CLASS = ".configuration-reference"; private static final String TABLE_ROW_FORMAT = "\n\na|%s [[%s]]`link:#%s[%s]`\n\n[.description]\n--\n%s\n--%s|%s %s\n|%s\n"; private static final String SECTION_TITLE = "[[%s]]link:#%s[%s]"; + private static final String TABLE_HEADER_FORMAT = "[%s, cols=\"80,.^10,.^10\"]\n|==="; private static final String TABLE_SECTION_ROW_FORMAT = "\n\nh|%s\n%s\nh|Type\nh|Default"; - private static final String TABLE_HEADER_FORMAT = "[.configuration-legend]%s\n[%s, cols=\"80,.^10,.^10\"]\n|==="; + private final boolean showEnvVars; private String anchorPrefix = ""; + public SummaryTableDocFormatter(boolean showEnvVars) { + this.showEnvVars = showEnvVars; + } + + public SummaryTableDocFormatter() { + this(true); + } + /** * Generate configuration keys in table format with search engine activated or not. * Useful when we want to optionally activate or deactivate search engine */ @Override - public void format(Writer writer, String initialAnchorPrefix, boolean activateSearch, List configDocItems) + public void format(Writer writer, String initialAnchorPrefix, boolean activateSearch, + List configDocItems, boolean includeConfigPhaseLegend) throws IOException { + if (includeConfigPhaseLegend) { + writer.append("[.configuration-legend]").append(CONFIG_PHASE_LEGEND).append(NEW_LINE); + } String searchableClass = activateSearch ? SEARCHABLE_TABLE_CLASS : Constants.EMPTY; String tableClasses = CONFIGURATION_TABLE_CLASS + searchableClass; - final String tableHeaders = String.format(TABLE_HEADER_FORMAT, Constants.CONFIG_PHASE_LEGEND, tableClasses); - writer.append(tableHeaders); + writer.append(String.format(TABLE_HEADER_FORMAT, tableClasses)); anchorPrefix = initialAnchorPrefix; // make sure that section-less configs get a legend @@ -74,18 +88,20 @@ public void format(Writer writer, ConfigDocKey configDocKey) throws IOException String doc = configDocKey.getConfigDoc(); - // Convert a property name to an environment variable name and show it in the config description - final String envVarExample = String.format("ifdef::add-copy-button-to-env-var[]\n" + - "Environment variable: env_var_with_copy_button:+++%1$s+++[]\n" + - "endif::add-copy-button-to-env-var[]\n" + - "ifndef::add-copy-button-to-env-var[]\n" + - "Environment variable: `+++%1$s+++`\n" + - "endif::add-copy-button-to-env-var[]", toEnvVarName(configDocKey.getKey())); - if (configDocKey.getConfigDoc().isEmpty()) { - doc = envVarExample; - } else { - // Add 2 new lines in order to show the environment variable on next line - doc += TWO_NEW_LINES + envVarExample; + if (showEnvVars) { + // Convert a property name to an environment variable name and show it in the config description + final String envVarExample = String.format("ifdef::add-copy-button-to-env-var[]\n" + + "Environment variable: env_var_with_copy_button:+++%1$s+++[]\n" + + "endif::add-copy-button-to-env-var[]\n" + + "ifndef::add-copy-button-to-env-var[]\n" + + "Environment variable: `+++%1$s+++`\n" + + "endif::add-copy-button-to-env-var[]", toEnvVarName(configDocKey.getKey())); + if (configDocKey.getConfigDoc().isEmpty()) { + doc = envVarExample; + } else { + // Add 2 new lines in order to show the environment variable on next line + doc += TWO_NEW_LINES + envVarExample; + } } final String typeDetail = DocGeneratorUtil.getTypeFormatInformationNote(configDocKey); diff --git a/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/JavaDocConfigDescriptionParserTest.java b/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/JavaDocConfigDescriptionParserTest.java index 292162160a273..b84268727d097 100644 --- a/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/JavaDocConfigDescriptionParserTest.java +++ b/core/processor/src/test/java/io/quarkus/annotation/processor/generate_doc/JavaDocConfigDescriptionParserTest.java @@ -25,6 +25,12 @@ public void parseNullJavaDoc() { assertEquals("", parsed); } + @Test + public void removeParagraphIndentation() { + String parsed = parser.parseConfigDescription("First paragraph

Second Paragraph"); + assertEquals("First paragraph\n\nSecond Paragraph", parsed); + } + @Test public void parseUntrimmedJavaDoc() { String parsed = parser.parseConfigDescription(" "); diff --git a/docs/pom.xml b/docs/pom.xml index 9d822f26545c2..4ba712b36fe97 100644 --- a/docs/pom.xml +++ b/docs/pom.xml @@ -2802,6 +2802,23 @@ + + generate-quarkus-mvn-plugin-docs + process-classes + + java + + + ${skipDocs} + io.quarkus.docs.generation.QuarkusMavenPluginDocsGenerator + + ${project.basedir}/../devtools/maven/target/classes/META-INF/maven/plugin.xml + + + ${env.MAVEN_CMD_LINE_ARGS} + + + all-build-item-classes process-classes diff --git a/docs/src/main/asciidoc/quarkus-maven-plugin.adoc b/docs/src/main/asciidoc/quarkus-maven-plugin.adoc new file mode 100644 index 0000000000000..249c48287672c --- /dev/null +++ b/docs/src/main/asciidoc/quarkus-maven-plugin.adoc @@ -0,0 +1,27 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// += Quarkus Maven Plugin + +The Quarkus Maven Plugin builds the Quarkus applications, and provides helpers to launch dev mode or build native executables. +For more information about how to use the Quarkus Maven Plugin, please refer to the xref:maven-tooling.adoc[Maven Tooling guide]. + +include::./attributes.adoc[] + +== Discover Maven goals + +Like most Maven plugins, the Quarkus Maven Plugin has a `help` goal that prints the description of the plugin, listing all available goals as well as their description. +It is also possible to print out detailed information about a goal, all its parameters and their default values. For instance, to see the help for the `create` goal, run: + +[source,shell] +---- +./mvnw quarkus:help -Ddetail -Dgoal=create +---- + +== Maven goals reference + +Here is the list of all the Quarkus Maven Plugin goals: + +include::{generated-dir}/config/quarkus-maven-plugin-goals.adoc[opts=optional, leveloffset=+2] diff --git a/docs/src/main/java/io/quarkus/docs/generation/QuarkusMavenPluginDocsGenerator.java b/docs/src/main/java/io/quarkus/docs/generation/QuarkusMavenPluginDocsGenerator.java new file mode 100644 index 0000000000000..0020be0a5c902 --- /dev/null +++ b/docs/src/main/java/io/quarkus/docs/generation/QuarkusMavenPluginDocsGenerator.java @@ -0,0 +1,106 @@ +package io.quarkus.docs.generation; + +import java.io.FileInputStream; +import java.io.IOException; +import java.io.Reader; +import java.nio.file.Files; +import java.nio.file.Path; + +import org.apache.maven.plugin.descriptor.MojoDescriptor; +import org.apache.maven.plugin.descriptor.Parameter; +import org.apache.maven.plugin.descriptor.PluginDescriptor; +import org.apache.maven.plugin.descriptor.PluginDescriptorBuilder; +import org.codehaus.plexus.util.xml.XmlStreamReader; + +import io.quarkus.annotation.processor.Constants; +import io.quarkus.annotation.processor.generate_doc.ConfigDocWriter; +import io.quarkus.annotation.processor.generate_doc.MavenConfigDocBuilder; +import io.quarkus.annotation.processor.generate_doc.MavenConfigDocBuilder.GoalParamsBuilder; + +/** + * Generates documentation for the Quarkus Maven Plugin from plugin descriptor. + */ +public class QuarkusMavenPluginDocsGenerator { + + private static final String QUARKUS_MAVEN_PLUGIN = "quarkus-maven-plugin-"; + private static final String GOALS_OUTPUT_FILE_NAME = QUARKUS_MAVEN_PLUGIN + "goals" + Constants.ADOC_EXTENSION; + private static final String GOAL_PARAMETER_ANCHOR_PREFIX = QUARKUS_MAVEN_PLUGIN + "goal-%s-"; + + public static void main(String[] args) throws Exception { + + String errorMessage = null; + + // Path to Quarkus Maven Plugin descriptor (plugin.xml) + final Path pluginXmlDescriptorPath; + if (args.length == 1) { + pluginXmlDescriptorPath = Path.of(args[0]); + } else { + pluginXmlDescriptorPath = null; + errorMessage = String.format("Expected 1 argument ('plugin.xml' file path), got %s", args.length); + } + + // Check the file exist + if (pluginXmlDescriptorPath != null + && (!Files.exists(pluginXmlDescriptorPath) || !Files.isRegularFile(pluginXmlDescriptorPath))) { + errorMessage = String.format("File does not exist: %s", pluginXmlDescriptorPath.toAbsolutePath()); + } + + // Deserialize plugin.xml to PluginDescriptor + PluginDescriptor pluginDescriptor = null; + if (errorMessage == null) { + try (Reader input = new XmlStreamReader(new FileInputStream(pluginXmlDescriptorPath.toFile()))) { + pluginDescriptor = new PluginDescriptorBuilder().build(input); + } catch (IOException e) { + errorMessage = String.format("Failed to deserialize PluginDescriptor: %s", e.getMessage()); + } + } + + // Don't generate documentation if there are no goals (shouldn't happen if correct descriptor is available) + if (pluginDescriptor != null && (pluginDescriptor.getMojos() == null || pluginDescriptor.getMojos().isEmpty())) { + errorMessage = "Found no goals"; + } + + // Don't break the build if Quarkus Maven Plugin Descriptor is not available + if (errorMessage != null) { + System.err.printf("Can't generate the documentation for the Quarkus Maven Plugin\n: %s\n", errorMessage); + return; + } + + // Build Goals documentation + final var goalsConfigDocBuilder = new MavenConfigDocBuilder(); + for (MojoDescriptor mojo : pluginDescriptor.getMojos()) { + + // Add Goal Title + goalsConfigDocBuilder.addTableTitle(mojo.getFullGoalName()); + + // Add Goal Description + if (mojo.getDescription() != null && !mojo.getDescription().isBlank()) { + goalsConfigDocBuilder.addTableDescription(mojo.getDescription()); + } + + // Collect Goal Parameters + final GoalParamsBuilder goalParamsBuilder = goalsConfigDocBuilder.newGoalParamsBuilder(); + if (mojo.getParameters() != null) { + for (Parameter parameter : mojo.getParameters()) { + goalParamsBuilder.addParam(parameter.getType(), parameter.getName(), parameter.getDefaultValue(), + parameter.isRequired(), parameter.getDescription()); + } + } + + // Add Parameters Summary Table if the goal has parameters + if (goalParamsBuilder.tableIsNotEmpty()) { + goalsConfigDocBuilder.addSummaryTable(String.format(GOAL_PARAMETER_ANCHOR_PREFIX, mojo.getGoal()), false, + goalParamsBuilder.build(), GOALS_OUTPUT_FILE_NAME, false); + + // Start next table on a new line + goalsConfigDocBuilder.addNewLine(); + } + } + + // Generate Goals documentation + if (goalsConfigDocBuilder.hasWriteItems()) { + new ConfigDocWriter().generateDocumentation(GOALS_OUTPUT_FILE_NAME, goalsConfigDocBuilder); + } + } + +} From d90af0f22f7a831257fcfa4baeca6b7223999e95 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 13 Sep 2022 23:01:39 +0000 Subject: [PATCH 05/36] Bump actions/download-artifact from 1 to 3 Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 1 to 3. - [Release notes](https://github.com/actions/download-artifact/releases) - [Commits](https://github.com/actions/download-artifact/compare/v1...v3) --- updated-dependencies: - dependency-name: actions/download-artifact dependency-type: direct:production update-type: version-update:semver-major ... Signed-off-by: dependabot[bot] --- .github/workflows/ci-actions-incremental.yml | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/.github/workflows/ci-actions-incremental.yml b/.github/workflows/ci-actions-incremental.yml index 01f4d938f4420..f8f1b5253b0f2 100644 --- a/.github/workflows/ci-actions-incremental.yml +++ b/.github/workflows/ci-actions-incremental.yml @@ -331,7 +331,7 @@ jobs: java-version: ${{ matrix.java.java-version }} - name: Download Maven Repo - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: name: maven-repo path: . @@ -413,7 +413,7 @@ jobs: run: git config --global core.longpaths true - uses: actions/checkout@v3 - name: Download Maven Repo - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: name: maven-repo path: . @@ -488,7 +488,7 @@ jobs: run: git config --global core.longpaths true - uses: actions/checkout@v3 - name: Download Maven Repo - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: name: maven-repo path: . @@ -554,7 +554,7 @@ jobs: run: git config --global core.longpaths true - uses: actions/checkout@v3 - name: Download Maven Repo - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: name: maven-repo path: . @@ -610,7 +610,7 @@ jobs: steps: - uses: actions/checkout@v3 - name: Download Maven Repo - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: name: maven-repo path: . @@ -661,7 +661,7 @@ jobs: distribution: temurin java-version: 11 - name: Download Maven Repo - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: name: maven-repo path: . @@ -746,7 +746,7 @@ jobs: run: | cat <<< $(jq '.HttpHeaders += {"User-Agent": "Quarkus-CI-Docker-Client"}' ~/.docker/config.json) > ~/.docker/config.json - name: Download Maven Repo - uses: actions/download-artifact@v1 + uses: actions/download-artifact@v3 with: name: maven-repo path: . From ec6d2cf85fa023d4fb0411c05b781f5bc3aaa1e6 Mon Sep 17 00:00:00 2001 From: Sanne Grinovero Date: Tue, 13 Sep 2022 17:28:18 +0100 Subject: [PATCH 06/36] Transform DB2 driver to Jakarta APIs --- extensions/jdbc/jdbc-db2/deployment/pom.xml | 5 ++ .../db2/deployment/JakartaEnablement.java | 82 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 extensions/jdbc/jdbc-db2/deployment/src/main/java/io/quarkus/jdbc/db2/deployment/JakartaEnablement.java diff --git a/extensions/jdbc/jdbc-db2/deployment/pom.xml b/extensions/jdbc/jdbc-db2/deployment/pom.xml index a412fa6bb6395..a803ad71c492f 100644 --- a/extensions/jdbc/jdbc-db2/deployment/pom.xml +++ b/extensions/jdbc/jdbc-db2/deployment/pom.xml @@ -48,6 +48,11 @@ assertj-core test + + org.eclipse.transformer + org.eclipse.transformer + 0.5.0 + diff --git a/extensions/jdbc/jdbc-db2/deployment/src/main/java/io/quarkus/jdbc/db2/deployment/JakartaEnablement.java b/extensions/jdbc/jdbc-db2/deployment/src/main/java/io/quarkus/jdbc/db2/deployment/JakartaEnablement.java new file mode 100644 index 0000000000000..c74ed6b81b965 --- /dev/null +++ b/extensions/jdbc/jdbc-db2/deployment/src/main/java/io/quarkus/jdbc/db2/deployment/JakartaEnablement.java @@ -0,0 +1,82 @@ +package io.quarkus.jdbc.db2.deployment; + +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import org.eclipse.transformer.action.ActionContext; +import org.eclipse.transformer.action.ByteData; +import org.eclipse.transformer.action.impl.ActionContextImpl; +import org.eclipse.transformer.action.impl.ByteDataImpl; +import org.eclipse.transformer.action.impl.ClassActionImpl; +import org.eclipse.transformer.action.impl.SelectionRuleImpl; +import org.eclipse.transformer.action.impl.SignatureRuleImpl; +import org.eclipse.transformer.util.FileUtils; +import org.objectweb.asm.ClassReader; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.quarkus.bootstrap.classloading.QuarkusClassLoader; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.builditem.BytecodeTransformerBuildItem; + +/** + * The DB2 driver is compiled using references to classes in the javax.transaction + * package; we need to transform these to fix compatibility with jakarta.transaction. + * We do this by leveraging the Eclipse Transformer project during Augmentation, so + * that end users don't need to bother. + */ +public class JakartaEnablement { + + private static final List CLASSES_NEEDING_TRANSFORMATION = List.of( + "com.ibm.db2.jcc.t2zos.ab", + "com.ibm.db2.jcc.t2zos.T2zosConnection", + "com.ibm.db2.jcc.t2zos.T2zosConfiguration"); + + @BuildStep + void transformToJakarta(BuildProducer transformers) { + if (QuarkusClassLoader.isClassPresentAtRuntime("jakarta.transaction.Transaction")) { + JakartaTransformer tr = new JakartaTransformer(); + for (String classname : CLASSES_NEEDING_TRANSFORMATION) { + final BytecodeTransformerBuildItem item = new BytecodeTransformerBuildItem.Builder() + .setCacheable(true) + .setContinueOnFailure(false) + .setClassToTransform(classname) + .setClassReaderOptions(ClassReader.SKIP_DEBUG) + .setInputTransformer(tr::transform) + .build(); + transformers.produce(item); + } + } + } + + private static class JakartaTransformer { + + private final Logger logger; + private final ActionContext ctx; + + JakartaTransformer() { + logger = LoggerFactory.getLogger("JakartaTransformer"); + Map renames = new HashMap<>(); + //N.B. we enable only this transformation, not the full set of capabilities of Eclipse Transformer; + //this might need tailoring if the same idea gets applied to a different context. + renames.put("javax.transaction", "jakarta.transaction"); + ctx = new ActionContextImpl(logger, + new SelectionRuleImpl(logger, Collections.emptyMap(), Collections.emptyMap()), + new SignatureRuleImpl(logger, renames, null, null, null, null, null, Collections.emptyMap())); + } + + byte[] transform(final String name, final byte[] bytes) { + logger.info("Jakarta EE compatibility enhancer for Quarkus: transforming " + name); + final ClassActionImpl classTransformer = new ClassActionImpl(ctx); + final ByteBuffer input = ByteBuffer.wrap(bytes); + final ByteData inputData = new ByteDataImpl(name, input, FileUtils.DEFAULT_CHARSET); + final ByteData outputData = classTransformer.apply(inputData); + return outputData.buffer().array(); + } + } + +} From 33c8625210a7691d7439d3df351bd771fe72b2a5 Mon Sep 17 00:00:00 2001 From: Clement Escoffier Date: Wed, 14 Sep 2022 10:16:47 +0200 Subject: [PATCH 07/36] Change visilibility of GeoSearchArgs.fromMember(...) to public --- .../quarkus/redis/datasource/geo/GeoSearchArgs.java | 2 +- .../io/quarkus/redis/datasource/GeoCommandsTest.java | 11 +++++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/geo/GeoSearchArgs.java b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/geo/GeoSearchArgs.java index 2070507f362c3..29b2cd9140f56 100644 --- a/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/geo/GeoSearchArgs.java +++ b/extensions/redis-client/runtime/src/main/java/io/quarkus/redis/datasource/geo/GeoSearchArgs.java @@ -56,7 +56,7 @@ public GeoSearchArgs fromMember(V member) { * @param latitude the latitude * @return the current {@code GeoSearchArgs} */ - private GeoSearchArgs fromCoordinate(double longitude, double latitude) { + public GeoSearchArgs fromCoordinate(double longitude, double latitude) { this.longitude = longitude; this.latitude = latitude; return this; diff --git a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/GeoCommandsTest.java b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/GeoCommandsTest.java index 0d2a365de53ae..ea34b4ef21c71 100644 --- a/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/GeoCommandsTest.java +++ b/extensions/redis-client/runtime/src/test/java/io/quarkus/redis/datasource/GeoCommandsTest.java @@ -494,6 +494,17 @@ void geosearchWithArgs() { assertThat(gv.geohash).isEmpty(); }); + args = new GeoSearchArgs().fromCoordinate(CRUSSOL_LONGITUDE, CRUSSOL_LATITUDE) + .byRadius(5, GeoUnit.KM).withCoordinates().withDistance().descending(); + places = geo.geosearch(key, args); + assertThat(places).hasSize(1).allSatisfy(gv -> { + assertThat(gv.member).isEqualTo(Place.crussol); + assertThat(gv.longitude).isNotEmpty(); + assertThat(gv.latitude).isNotEmpty(); + assertThat(gv.distance).isNotEmpty(); + assertThat(gv.geohash).isEmpty(); + }); + } @Test From 80b2553a3d51ef165431cc4e62bec320bbcfee6b Mon Sep 17 00:00:00 2001 From: Robbie Gemmell Date: Wed, 14 Sep 2022 10:13:29 +0100 Subject: [PATCH 08/36] Update to proton-j 0.34.0 --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index e1b284c5a8587..5470a0159dd36 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -163,7 +163,7 @@ 6.0.0 4.7.1 1.5.2 - 0.33.10 + 0.34.0 3.24.2 3.14.9 1.17.2 From a03cda3e01d8b8d7ce0735a5ec98bf2c0c6e356a Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 14 Sep 2022 11:18:07 +0300 Subject: [PATCH 09/36] Enable the registration of @LoggingFilter classes via CDI This allows for users to configure their filters via the usual Quarkus configuration approach. Follows up on: #27864 --- .../logging/LoggingResourceProcessor.java | 19 ++----- .../io/quarkus/logging/LoggingFilter.java | 2 - .../runtime/logging/LogFilterFactory.java | 57 +++++++++++++++++++ .../runtime/logging/LoggingSetupRecorder.java | 5 +- docs/src/main/asciidoc/logging.adoc | 20 ++++++- .../LoggingBeanSupportProcessor.java | 16 ++++++ .../runtime/logging/ArcLogFilterFactory.java | 29 ++++++++++ ...o.quarkus.runtime.logging.LogFilterFactory | 1 + .../minlevel/set/filter/TestFilter.java | 12 +++- .../src/main/resources/application.properties | 1 + 10 files changed, 140 insertions(+), 22 deletions(-) create mode 100644 core/runtime/src/main/java/io/quarkus/runtime/logging/LogFilterFactory.java create mode 100644 extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LoggingBeanSupportProcessor.java create mode 100644 extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/logging/ArcLogFilterFactory.java create mode 100644 extensions/arc/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.logging.LogFilterFactory diff --git a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java index e1fa11ba794b9..acaf966faf7b0 100644 --- a/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java +++ b/core/deployment/src/main/java/io/quarkus/deployment/logging/LoggingResourceProcessor.java @@ -1,6 +1,5 @@ package io.quarkus.deployment.logging; -import java.lang.reflect.Modifier; import java.nio.file.Files; import java.util.ArrayList; import java.util.Collection; @@ -38,7 +37,6 @@ import org.jboss.jandex.CompositeIndex; import org.jboss.jandex.DotName; import org.jboss.jandex.IndexView; -import org.jboss.jandex.MethodInfo; import org.jboss.logging.Logger; import org.jboss.logmanager.EmbeddedConfigurator; import org.jboss.logmanager.LogManager; @@ -112,6 +110,7 @@ import io.quarkus.runtime.logging.LogBuildTimeConfig; import io.quarkus.runtime.logging.LogCleanupFilterElement; import io.quarkus.runtime.logging.LogConfig; +import io.quarkus.runtime.logging.LogFilterFactory; import io.quarkus.runtime.logging.LogMetricsHandlerRecorder; import io.quarkus.runtime.logging.LoggingSetupRecorder; @@ -126,7 +125,7 @@ public final class LoggingResourceProcessor { "isMinLevelEnabled", boolean.class, int.class, String.class); - private static final DotName LOGGING_FILTER = DotName.createSimple(LoggingFilter.class.getName()); + public static final DotName LOGGING_FILTER = DotName.createSimple(LoggingFilter.class.getName()); private static final DotName FILTER = DotName.createSimple(Filter.class.getName()); private static final String ILLEGAL_LOGGING_FILTER_USE_MESSAGE = "'@" + LoggingFilter.class.getName() + "' can only be used on classes that implement '" @@ -235,7 +234,8 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(RecorderContext context, LoggingSe BuildProducer shutdownListenerBuildItemBuildProducer, LaunchModeBuildItem launchModeBuildItem, List logCleanupFilters, - BuildProducer reflectiveClassBuildItemBuildProducer) { + BuildProducer reflectiveClassBuildItemBuildProducer, + BuildProducer serviceProviderBuildItemBuildProducer) { if (!launchModeBuildItem.isAuxiliaryApplication() || launchModeBuildItem.getAuxiliaryDevModeType().orElse(null) == DevModeType.TEST_ONLY) { final List>> handlers = handlerBuildItems.stream() @@ -272,6 +272,8 @@ LoggingSetupBuildItem setupLoggingRuntimeInit(RecorderContext context, LoggingSe reflectiveClassBuildItemBuildProducer.produce(new ReflectiveClassBuildItem(true, false, false, discoveredLogComponents.getNameToFilterClass().values().toArray( EMPTY_STRING_ARRAY))); + serviceProviderBuildItemBuildProducer + .produce(ServiceProviderBuildItem.allProvidersFromClassPath(LogFilterFactory.class.getName())); } shutdownListenerBuildItemBuildProducer.produce(new ShutdownListenerBuildItem( @@ -317,10 +319,6 @@ private DiscoveredLogComponents discoverLogComponents(IndexView index) { throw new IllegalStateException("Unimplemented mode of use of '" + LoggingFilter.class.getName() + "'"); } ClassInfo classInfo = target.asClass(); - if (!Modifier.isFinal(classInfo.flags())) { - throw new RuntimeException( - ILLEGAL_LOGGING_FILTER_USE_MESSAGE + " Offending class is '" + classInfo.name() + "'"); - } boolean isFilterImpl = false; ClassInfo currentClassInfo = classInfo; while ((currentClassInfo != null) && (!JandexUtil.DOTNAME_OBJECT.equals(currentClassInfo.name()))) { @@ -343,11 +341,6 @@ private DiscoveredLogComponents discoverLogComponents(IndexView index) { ILLEGAL_LOGGING_FILTER_USE_MESSAGE + " Offending class is '" + classInfo.name() + "'"); } - MethodInfo ctor = classInfo.method(""); - if ((ctor == null) || (ctor.typeParameters().size() > 0)) { - throw new RuntimeException("Classes annotated with '" + LoggingFilter.class.getName() - + "' must have a no-args constructor. Offending class is '" + classInfo.name() + "'"); - } String filterName = instance.value("name").asString(); if (filtersMap.containsKey(filterName)) { throw new RuntimeException("Filter '" + filterName + "' was defined multiple times."); diff --git a/core/runtime/src/main/java/io/quarkus/logging/LoggingFilter.java b/core/runtime/src/main/java/io/quarkus/logging/LoggingFilter.java index bf2adb61f2b4f..4f07aaa0afe46 100644 --- a/core/runtime/src/main/java/io/quarkus/logging/LoggingFilter.java +++ b/core/runtime/src/main/java/io/quarkus/logging/LoggingFilter.java @@ -8,8 +8,6 @@ /** * Makes the filter class known to Quarkus by the specified name. * The filter can then be configured for a handler (like the logging handler using {@code quarkus.log.console.filter}). - * - * This class must ONLY be placed on implementations of {@link java.util.logging.Filter} that are marked as {@code final}. */ @Retention(RetentionPolicy.RUNTIME) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LogFilterFactory.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogFilterFactory.java new file mode 100644 index 0000000000000..d94d67907423d --- /dev/null +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LogFilterFactory.java @@ -0,0 +1,57 @@ +package io.quarkus.runtime.logging; + +import java.util.ServiceLoader; +import java.util.logging.Filter; + +/** + * Factory that allows for the creation of {@link Filter} classes annotated with {@link io.quarkus.logging.LoggingFilter}. + * Implementations of this class are loaded via the {@link ServiceLoader} and the implementation selected is the one + * with the lowest value returned from the {@code priority} method. + */ +public interface LogFilterFactory { + + int MIN_PRIORITY = Integer.MAX_VALUE; + int DEFAULT_PRIORITY = 0; + + Filter create(String className) throws Exception; + + default int priority() { + return DEFAULT_PRIORITY; + } + + static LogFilterFactory load() { + LogFilterFactory result = null; + ServiceLoader load = ServiceLoader.load(LogFilterFactory.class); + for (LogFilterFactory next : load) { + if (result == null) { + result = next; + } else { + if (next.priority() < result.priority()) { + result = next; + } + } + } + if (result == null) { + result = new ReflectionLogFilterFactory(); + } + return result; + } + + /** + * The default implementation used when no other implementation is found. + * This simply calls the class' no-arg constructor (and fails if one does not exist). + */ + class ReflectionLogFilterFactory implements LogFilterFactory { + + @Override + public Filter create(String className) throws Exception { + return (Filter) Class.forName(className, true, Thread.currentThread().getContextClassLoader()) + .getConstructor().newInstance(); + } + + @Override + public int priority() { + return LogFilterFactory.MIN_PRIORITY; + } + } +} diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java index f10f9c3dab731..0a3f419a390b8 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java @@ -254,13 +254,12 @@ private static Map createNamedFilters(DiscoveredLogComponents di } Map nameToFilter = new HashMap<>(); + LogFilterFactory logFilterFactory = LogFilterFactory.load(); discoveredLogComponents.getNameToFilterClass().forEach(new BiConsumer<>() { @Override public void accept(String name, String className) { try { - nameToFilter.put(name, - (Filter) Class.forName(className, true, Thread.currentThread().getContextClassLoader()) - .getConstructor().newInstance()); + nameToFilter.put(name, logFilterFactory.create(className)); } catch (Exception e) { throw new RuntimeException("Unable to create instance of Logging Filter '" + className + "'"); } diff --git a/docs/src/main/asciidoc/logging.adoc b/docs/src/main/asciidoc/logging.adoc index f946ff505ef15..3a5cb4a1cec3f 100644 --- a/docs/src/main/asciidoc/logging.adoc +++ b/docs/src/main/asciidoc/logging.adoc @@ -325,7 +325,8 @@ These filters are registered by placing the `@io.quarkus.logging.LoggingFilter` Finally, the filter is attached using the `filter` configuration property of the appropriate handler. -Let's say for example that we wanted to filter out logging records that contained the word `test` from the console logs. +Let's say for example that we wanted to filter out logging records that contained a part of text from the console logs. +The text itself is part of the application configuration and is not hardcoded. We could write a filter like so: [source,java] @@ -336,13 +337,28 @@ import java.util.logging.LogRecord; @LoggingFilter(name = "my-filter") public final class TestFilter implements Filter { + + private final String part; + + public TestFilter(@ConfigProperty(name = "my-filter.part") String part) { + this.part = part; + } + @Override public boolean isLoggable(LogRecord record) { - return !record.getMessage().contains("test"); + return !record.getMessage().contains(part); } } ---- +and configure it in the usual Quarkus way (for example using `application.properties`) like so: + +[source,properties] +---- +my-filter.part=TEST +---- + + And we would register this filter to the console handler like so: [source, properties] diff --git a/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LoggingBeanSupportProcessor.java b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LoggingBeanSupportProcessor.java new file mode 100644 index 0000000000000..71bdfdb1b8160 --- /dev/null +++ b/extensions/arc/deployment/src/main/java/io/quarkus/arc/deployment/LoggingBeanSupportProcessor.java @@ -0,0 +1,16 @@ +package io.quarkus.arc.deployment; + +import io.quarkus.arc.processor.BuiltinScope; +import io.quarkus.deployment.annotations.BuildProducer; +import io.quarkus.deployment.annotations.BuildStep; +import io.quarkus.deployment.logging.LoggingResourceProcessor; + +public class LoggingBeanSupportProcessor { + + @BuildStep + public void discoveredComponents(BuildProducer beanDefiningAnnotationProducer) { + beanDefiningAnnotationProducer.produce(new BeanDefiningAnnotationBuildItem( + LoggingResourceProcessor.LOGGING_FILTER, BuiltinScope.SINGLETON.getName(), false)); + } + +} diff --git a/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/logging/ArcLogFilterFactory.java b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/logging/ArcLogFilterFactory.java new file mode 100644 index 0000000000000..f653c300c3ada --- /dev/null +++ b/extensions/arc/runtime/src/main/java/io/quarkus/arc/runtime/logging/ArcLogFilterFactory.java @@ -0,0 +1,29 @@ +package io.quarkus.arc.runtime.logging; + +import java.util.logging.Filter; + +import io.quarkus.arc.Arc; +import io.quarkus.arc.InstanceHandle; +import io.quarkus.runtime.logging.LogFilterFactory; + +/** + * Creates the implementation of the class by getting a bean from Arc. + * This class is loaded automatically by the {@link java.util.ServiceLoader}. + */ +public class ArcLogFilterFactory implements LogFilterFactory { + + @Override + public Filter create(String className) throws Exception { + InstanceHandle instance = Arc.container().instance(Class.forName(className, true, Thread.currentThread() + .getContextClassLoader())); + if (!instance.isAvailable()) { + throw new IllegalStateException("Improper integration of '" + LogFilterFactory.class.getName() + "' detected"); + } + return (Filter) instance.get(); + } + + @Override + public int priority() { + return LogFilterFactory.DEFAULT_PRIORITY - 100; + } +} diff --git a/extensions/arc/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.logging.LogFilterFactory b/extensions/arc/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.logging.LogFilterFactory new file mode 100644 index 0000000000000..d114e9f1e84d4 --- /dev/null +++ b/extensions/arc/runtime/src/main/resources/META-INF/services/io.quarkus.runtime.logging.LogFilterFactory @@ -0,0 +1 @@ +io.quarkus.arc.runtime.logging.ArcLogFilterFactory diff --git a/integration-tests/logging-min-level-set/src/main/java/io/quarkus/it/logging/minlevel/set/filter/TestFilter.java b/integration-tests/logging-min-level-set/src/main/java/io/quarkus/it/logging/minlevel/set/filter/TestFilter.java index 30163df431240..964a97ad1850e 100644 --- a/integration-tests/logging-min-level-set/src/main/java/io/quarkus/it/logging/minlevel/set/filter/TestFilter.java +++ b/integration-tests/logging-min-level-set/src/main/java/io/quarkus/it/logging/minlevel/set/filter/TestFilter.java @@ -3,13 +3,21 @@ import java.util.logging.Filter; import java.util.logging.LogRecord; +import org.eclipse.microprofile.config.inject.ConfigProperty; + import io.quarkus.logging.LoggingFilter; @LoggingFilter(name = "my-filter") -public final class TestFilter implements Filter { +public class TestFilter implements Filter { + + private final String part; + + public TestFilter(@ConfigProperty(name = "my-filter.part") String part) { + this.part = part; + } @Override public boolean isLoggable(LogRecord record) { - return !record.getMessage().contains("TEST"); + return !record.getMessage().contains(part); } } diff --git a/integration-tests/logging-min-level-set/src/main/resources/application.properties b/integration-tests/logging-min-level-set/src/main/resources/application.properties index e33158f666c73..89dddefeda7c1 100644 --- a/integration-tests/logging-min-level-set/src/main/resources/application.properties +++ b/integration-tests/logging-min-level-set/src/main/resources/application.properties @@ -4,3 +4,4 @@ quarkus.log.category."io.quarkus.it.logging.minlevel.set.below".min-level=TRACE quarkus.log.category."io.quarkus.it.logging.minlevel.set.below.child".min-level=inherit quarkus.log.category."io.quarkus.it.logging.minlevel.set.promote".min-level=ERROR quarkus.log.console.filter=my-filter +my-filter.part=TEST From 1dec9051562da6dc8d81811f678f52164d7f9019 Mon Sep 17 00:00:00 2001 From: Rostislav Svoboda Date: Wed, 14 Sep 2022 11:38:25 +0200 Subject: [PATCH 10/36] Point to configuration of popular email services --- docs/src/main/asciidoc/mailer-reference.adoc | 4 ++++ docs/src/main/asciidoc/mailer.adoc | 24 +------------------- 2 files changed, 5 insertions(+), 23 deletions(-) diff --git a/docs/src/main/asciidoc/mailer-reference.adoc b/docs/src/main/asciidoc/mailer-reference.adoc index 67dff0bdfffcf..2aa9e6342d6f6 100644 --- a/docs/src/main/asciidoc/mailer-reference.adoc +++ b/docs/src/main/asciidoc/mailer-reference.adoc @@ -368,6 +368,8 @@ quarkus.mailer.port=587 quarkus.mailer.start-tls=REQUIRED quarkus.mailer.username=YOUREMAIL@gmail.com quarkus.mailer.password=YOURGENERATEDAPPLICATIONPASSWORD + +quarkus.mailer.mock=false # In dev mode, prevent from using the mock SMTP server ---- Or with SSL: @@ -381,6 +383,8 @@ quarkus.mailer.port=465 quarkus.mailer.ssl=true quarkus.mailer.username=YOUREMAIL@gmail.com quarkus.mailer.password=YOURGENERATEDAPPLICATIONPASSWORD + +quarkus.mailer.mock=false # In dev mode, prevent from using the mock SMTP server ---- [NOTE] diff --git a/docs/src/main/asciidoc/mailer.adoc b/docs/src/main/asciidoc/mailer.adoc index 882d096f1bcd9..e85271a91c4fa 100644 --- a/docs/src/main/asciidoc/mailer.adoc +++ b/docs/src/main/asciidoc/mailer.adoc @@ -181,32 +181,10 @@ In the `src/main/resources/application.properties` file, you need to configure t Note that the password can also be configured using system properties and environment variables. See the xref:config-reference.adoc[configuration reference guide] for details. -Here is an example using _sendgrid_: - -[source,properties] ----- -# Your email address you send from - must match the "from" address from sendgrid. -quarkus.mailer.from=test@quarkus.io - -# The SMTP host -quarkus.mailer.host=smtp.sendgrid.net -# The SMTP port -quarkus.mailer.port=465 -# If the SMTP connection requires SSL/TLS -quarkus.mailer.ssl=true -# Your username -quarkus.mailer.username=.... -# Your password -quarkus.mailer.password=.... - -# By default, in dev mode, the mailer is a mock. This disables the mock and use the configured mailer. -quarkus.mailer.mock=false ----- +Configuration of popular mail services is covered in xref:mailer-reference.adoc#popular[the reference guide]. Once you have configured the mailer, if you call the HTTP endpoint as shown above, you will send emails. -Other popular mail services are covered in xref:mailer-reference.adoc#popular[the reference guide]. - == Conclusion This guide has shown how to send emails from your Quarkus application. From b653c645914a105163089470ef47d3dae124d730 Mon Sep 17 00:00:00 2001 From: Jose Date: Wed, 14 Sep 2022 14:22:38 +0200 Subject: [PATCH 11/36] Resteasy Rest Client: Fix truststore password issue with Vert.x The truststore password was being sent as empty ("") in the JksOptions. This caused the following exception: ``` Caused by: io.vertx.core.VertxException: java.security.UnrecoverableKeyException: Get Key failed: Given final block not properly padded. Such issues can arise if a bad key is used during decryption. [09:59:27.352] [INFO] [client] at io.vertx.core.net.impl.SSLHelper.getContext(SSLHelper.java:480) [09:59:27.353] [INFO] [client] at io.vertx.core.net.impl.SSLHelper.getContext(SSLHelper.java:469) [09:59:27.353] [INFO] [client] at io.vertx.core.net.impl.SSLHelper.validate(SSLHelper.java:507) [09:59:27.353] [INFO] [client] at io.vertx.core.net.impl.NetClientImpl.(NetClientImpl.java:95) [09:59:27.353] [INFO] [client] at io.vertx.core.http.impl.HttpClientImpl.(HttpClientImpl.java:155) [09:59:27.354] [INFO] [client] at io.vertx.core.impl.VertxImpl.createHttpClient(VertxImpl.java:338) [09:59:27.354] [INFO] [client] at io.vertx.core.impl.VertxImpl.createHttpClient(VertxImpl.java:350) [09:59:27.354] [INFO] [client] at org.jboss.resteasy.reactive.client.impl.ClientImpl.(ClientImpl.java:170) [09:59:27.354] [INFO] [client] at org.jboss.resteasy.reactive.client.impl.ClientBuilderImpl.build(ClientBuilderImpl.java:244) [09:59:27.354] [INFO] [client] at io.quarkus.rest.client.reactive.runtime.RestClientBuilderImpl.build(RestClientBuilderImpl.java:332) [09:59:27.355] [INFO] [client] at io.quarkus.rest.client.reactive.runtime.RestClientCDIDelegateBuilder.build(RestClientCDIDelegateBuilder.java:64) [09:59:27.355] [INFO] [client] at io.quarkus.rest.client.reactive.runtime.RestClientCDIDelegateBuilder.createDelegate(RestClientCDIDelegateBuilder.java:42) [09:59:27.355] [INFO] [client] at io.quarkus.rest.client.reactive.runtime.RestClientReactiveCDIWrapperBase.(RestClientReactiveCDIWrapperBase.java:20) [09:59:27.355] [INFO] [client] at io.jester.examples.quarkus.greetings.Client$$CDIWrapper.(Unknown Source) [09:59:27.356] [INFO] [client] at io.jester.examples.quarkus.greetings.Client$$CDIWrapper_ClientProxy.(Unknown Source) [09:59:27.356] [INFO] [client] at io.jester.examples.quarkus.greetings.Client$$CDIWrapper_Bean.proxy(Unknown Source) [09:59:27.356] [INFO] [client] at io.jester.examples.quarkus.greetings.Client$$CDIWrapper_Bean.get(Unknown Source) [09:59:27.356] [INFO] [client] at io.jester.examples.quarkus.greetings.Client$$CDIWrapper_Bean.get(Unknown Source) [09:59:27.357] [INFO] [client] ... 26 more [09:59:27.357] [INFO] [client] Caused by: java.security.UnrecoverableKeyException: Get Key failed: Given final block not properly padded. Such issues can arise if a bad key is used during decryption. [09:59:27.357] [INFO] [client] at java.base/sun.security.pkcs12.PKCS12KeyStore.engineGetKey(PKCS12KeyStore.java:446) [09:59:27.357] [INFO] [client] at java.base/sun.security.util.KeyStoreDelegator.engineGetKey(KeyStoreDelegator.java:90) [09:59:27.357] [INFO] [client] at java.base/java.security.KeyStore.getKey(KeyStore.java:1057) [09:59:27.357] [INFO] [client] at io.vertx.core.net.impl.KeyStoreHelper.(KeyStoreHelper.java:109) [09:59:27.358] [INFO] [client] at io.vertx.core.net.KeyStoreOptionsBase.getHelper(KeyStoreOptionsBase.java:187) [09:59:27.358] [INFO] [client] at io.vertx.core.net.KeyStoreOptionsBase.getTrustManagerFactory(KeyStoreOptionsBase.java:217) [09:59:27.358] [INFO] [client] at io.vertx.core.net.impl.SSLHelper.getTrustMgrFactory(SSLHelper.java:327) [09:59:27.358] [INFO] [client] at io.vertx.core.net.impl.SSLHelper.getContext(SSLHelper.java:478) [09:59:27.358] [INFO] [client] ... 43 more [09:59:27.359] [INFO] [client] Caused by: javax.crypto.BadPaddingException: Given final block not properly padded. Such issues can arise if a bad key is used during decryption. [09:59:27.359] [INFO] [client] at java.base/com.sun.crypto.provider.CipherCore.unpad(CipherCore.java:975) [09:59:27.359] [INFO] [client] at java.base/com.sun.crypto.provider.CipherCore.fillOutputBuffer(CipherCore.java:1056) [09:59:27.359] [INFO] [client] at java.base/com.sun.crypto.provider.CipherCore.doFinal(CipherCore.java:853) [09:59:27.359] [INFO] [client] at java.base/com.sun.crypto.provider.PKCS12PBECipherCore.implDoFinal(PKCS12PBECipherCore.java:408) [09:59:27.360] [INFO] [client] at java.base/com.sun.crypto.provider.PKCS12PBECipherCore$PBEWithSHA1AndDESede.engineDoFinal(PKCS12PBECipherCore.java:440) [09:59:27.360] [INFO] [client] at java.base/javax.crypto.Cipher.doFinal(Cipher.java:2202) [09:59:27.360] [INFO] [client] at java.base/sun.security.pkcs12.PKCS12KeyStore.lambda$engineGetKey$0(PKCS12KeyStore.java:387) [09:59:27.360] [INFO] [client] at java.base/sun.security.pkcs12.PKCS12KeyStore$RetryWithZero.run(PKCS12KeyStore.java:283) [09:59:27.360] [INFO] [client] at java.base/sun.security.pkcs12.PKCS12KeyStore.engineGetKey(PKCS12KeyStore.java:381) [09:59:27.361] [INFO] [client] ... 50 more ``` --- .../runtime/RestClientBuilderImpl.java | 5 ++++ .../runtime/RestClientCDIDelegateBuilder.java | 6 ++-- .../RestClientCDIDelegateBuilderTest.java | 4 +-- .../client/impl/ClientBuilderImpl.java | 10 +++++-- .../rest-client-reactive/pom.xml | 30 +++++++++++++++++++ .../client/main/ClientCallingResource.java | 7 +++++ .../selfsigned/ExternalSelfSignedClient.java | 15 ++++++++++ .../src/main/resources/application.properties | 5 +++- .../ExternalSelfSignedTestCase.java | 21 +++++++++++++ 9 files changed, 95 insertions(+), 8 deletions(-) create mode 100644 integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/selfsigned/ExternalSelfSignedClient.java create mode 100644 integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedTestCase.java diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java index 12c830977b9c8..81c315c22837d 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientBuilderImpl.java @@ -95,6 +95,11 @@ public RestClientBuilderImpl trustStore(KeyStore trustStore) { return this; } + public RestClientBuilderImpl trustStore(KeyStore trustStore, String trustStorePassword) { + clientBuilder.trustStore(trustStore, trustStorePassword.toCharArray()); + return this; + } + @Override public RestClientBuilderImpl keyStore(KeyStore keyStore, String keystorePassword) { clientBuilder.keyStore(keyStore, keystorePassword); diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java index 83e647ae666df..d5f998178a40c 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/main/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilder.java @@ -182,7 +182,7 @@ private void configureShared(RestClientBuilder builder) { } } - private void configureSsl(RestClientBuilder builder) { + private void configureSsl(RestClientBuilderImpl builder) { Optional maybeTrustStore = oneOf(clientConfigByClassName().trustStore, clientConfigByConfigKey().trustStore, configRoot.trustStore); @@ -249,7 +249,7 @@ private void registerKeyStore(String keyStorePath, RestClientBuilder builder) { } } - private void registerTrustStore(String trustStorePath, RestClientBuilder builder) { + private void registerTrustStore(String trustStorePath, RestClientBuilderImpl builder) { Optional maybeTrustStorePassword = oneOf(clientConfigByClassName().trustStorePassword, clientConfigByConfigKey().trustStorePassword, configRoot.trustStorePassword); Optional maybeTrustStoreType = oneOf(clientConfigByClassName().trustStoreType, @@ -269,7 +269,7 @@ private void registerTrustStore(String trustStorePath, RestClientBuilder builder e); } - builder.trustStore(trustStore); + builder.trustStore(trustStore, password); } catch (KeyStoreException e) { throw new IllegalArgumentException("Failed to initialize trust store from " + trustStorePath, e); } diff --git a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java index 60f80120fb6a7..9a0ec4493673d 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java +++ b/extensions/resteasy-reactive/rest-client-reactive/runtime/src/test/java/io/quarkus/rest/client/reactive/runtime/RestClientCDIDelegateBuilderTest.java @@ -109,7 +109,7 @@ public void testClientSpecificConfigs() { Mockito.verify(restClientBuilderMock).register(MyResponseFilter1.class); Mockito.verify(restClientBuilderMock).queryParamStyle(QueryParamStyle.COMMA_SEPARATED); - Mockito.verify(restClientBuilderMock).trustStore(Mockito.any()); + Mockito.verify(restClientBuilderMock).trustStore(Mockito.any(), Mockito.anyString()); Mockito.verify(restClientBuilderMock).keyStore(Mockito.any(), Mockito.anyString()); } @@ -151,7 +151,7 @@ public void testGlobalConfigs() { Mockito.verify(restClientBuilderMock).register(MyResponseFilter2.class); Mockito.verify(restClientBuilderMock).queryParamStyle(QueryParamStyle.MULTI_PAIRS); - Mockito.verify(restClientBuilderMock).trustStore(Mockito.any()); + Mockito.verify(restClientBuilderMock).trustStore(Mockito.any(), Mockito.anyString()); Mockito.verify(restClientBuilderMock).keyStore(Mockito.any(), Mockito.anyString()); } diff --git a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java index e8020ea6c1547..6afe4fb4d6e52 100644 --- a/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java +++ b/independent-projects/resteasy-reactive/client/runtime/src/main/java/org/jboss/resteasy/reactive/client/impl/ClientBuilderImpl.java @@ -46,6 +46,7 @@ public class ClientBuilderImpl extends ClientBuilder { private char[] keystorePassword; private SSLContext sslContext; private KeyStore trustStore; + private char[] trustStorePassword; private String proxyHost; private int proxyPort; @@ -88,7 +89,12 @@ public ClientBuilder keyStore(KeyStore keyStore, char[] password) { @Override public ClientBuilder trustStore(KeyStore trustStore) { + return trustStore(trustStore, null); + } + + public ClientBuilder trustStore(KeyStore trustStore, char[] password) { this.trustStore = trustStore; + this.trustStorePassword = password; return this; } @@ -164,7 +170,7 @@ public ClientBuilder clientLogger(ClientLogger clientLogger) { @Override public ClientImpl build() { Buffer keyStore = asBuffer(this.keyStore, keystorePassword); - Buffer trustStore = asBuffer(this.trustStore, EMPTY_CHAR_ARARAY); + Buffer trustStore = asBuffer(this.trustStore, this.trustStorePassword); HttpClientOptions options = Optional.ofNullable(configuration.getFromContext(HttpClientOptions.class)) .orElseGet(HttpClientOptions::new); @@ -185,7 +191,7 @@ public ClientImpl build() { if (trustStore != null) { JksOptions jks = new JksOptions(); jks.setValue(trustStore); - jks.setPassword(""); + jks.setPassword(trustStorePassword == null ? "" : new String(trustStorePassword)); options.setTrustStoreOptions(jks); } } diff --git a/integration-tests/rest-client-reactive/pom.xml b/integration-tests/rest-client-reactive/pom.xml index 8889bbc39f7ad..d81583d01c89e 100644 --- a/integration-tests/rest-client-reactive/pom.xml +++ b/integration-tests/rest-client-reactive/pom.xml @@ -11,6 +11,11 @@ quarkus-integration-test-rest-client-reactive Quarkus - Integration Tests - REST Client Reactive + + ${project.build.directory}/self-signed.p12 + changeit + + @@ -165,6 +170,31 @@ + + + uk.co.automatictester + truststore-maven-plugin + ${truststore-maven-plugin.version} + + + self-signed-truststore + generate-test-resources + + generate-truststore + + + PKCS12 + ${self-signed.trust-store} + ${self-signed.trust-store-password} + + self-signed.badssl.com:443 + + true + LEAF + + + + diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java index 5b6252ce83a00..1e311fef17815 100644 --- a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/ClientCallingResource.java @@ -21,6 +21,7 @@ import io.opentelemetry.sdk.testing.exporter.InMemorySpanExporter; import io.quarkus.it.rest.client.main.MyResponseExceptionMapper.MyException; +import io.quarkus.it.rest.client.main.selfsigned.ExternalSelfSignedClient; import io.smallrye.mutiny.Uni; import io.vertx.core.Future; import io.vertx.core.json.Json; @@ -44,6 +45,9 @@ public class ClientCallingResource { @RestClient FaultToleranceOnInterfaceClient faultToleranceOnInterfaceClient; + @RestClient + ExternalSelfSignedClient externalSelfSignedClient; + @Inject InMemorySpanExporter inMemorySpanExporter; @@ -165,6 +169,9 @@ void init(@Observes Router router) { }); router.get("/with%20space").handler(rc -> rc.response().setStatusCode(200).end()); + + router.get("/self-signed").blockingHandler( + rc -> rc.response().setStatusCode(200).end(String.valueOf(externalSelfSignedClient.invoke().getStatus()))); } private Future success(RoutingContext rc, String body) { diff --git a/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/selfsigned/ExternalSelfSignedClient.java b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/selfsigned/ExternalSelfSignedClient.java new file mode 100644 index 0000000000000..7d2610f75b4be --- /dev/null +++ b/integration-tests/rest-client-reactive/src/main/java/io/quarkus/it/rest/client/main/selfsigned/ExternalSelfSignedClient.java @@ -0,0 +1,15 @@ +package io.quarkus.it.rest.client.main.selfsigned; + +import javax.ws.rs.GET; +import javax.ws.rs.core.Response; + +import org.eclipse.microprofile.faulttolerance.Retry; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@RegisterRestClient(baseUri = "https://self-signed.badssl.com/", configKey = "self-signed") +public interface ExternalSelfSignedClient { + + @GET + @Retry(delay = 1000) + Response invoke(); +} diff --git a/integration-tests/rest-client-reactive/src/main/resources/application.properties b/integration-tests/rest-client-reactive/src/main/resources/application.properties index 392722ce0c912..03c013f85ce30 100644 --- a/integration-tests/rest-client-reactive/src/main/resources/application.properties +++ b/integration-tests/rest-client-reactive/src/main/resources/application.properties @@ -1,4 +1,7 @@ w-exception-mapper/mp-rest/url=${test.url} w-fault-tolerance/mp-rest/url=${test.url} io.quarkus.it.rest.client.main.ParamClient/mp-rest/url=${test.url} -io.quarkus.it.rest.client.multipart.MultipartClient/mp-rest/url=${test.url} \ No newline at end of file +io.quarkus.it.rest.client.multipart.MultipartClient/mp-rest/url=${test.url} +# HTTPS +quarkus.rest-client.self-signed.trust-store=${self-signed.trust-store} +quarkus.rest-client.self-signed.trust-store-password=${self-signed.trust-store-password} diff --git a/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedTestCase.java b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedTestCase.java new file mode 100644 index 0000000000000..1348f532290fc --- /dev/null +++ b/integration-tests/rest-client-reactive/src/test/java/io/quarkus/it/rest/client/selfsigned/ExternalSelfSignedTestCase.java @@ -0,0 +1,21 @@ +package io.quarkus.it.rest.client.selfsigned; + +import static io.restassured.RestAssured.when; +import static org.hamcrest.Matchers.is; + +import org.junit.jupiter.api.Test; + +import io.quarkus.test.junit.QuarkusTest; + +@QuarkusTest +public class ExternalSelfSignedTestCase { + + @Test + public void should_accept_self_signed_certs() { + when() + .get("/self-signed") + .then() + .statusCode(200) + .body(is("200")); + } +} From 5ede0cb5d7b8c1341ac26567494b1979b2ac3af1 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Wed, 14 Sep 2022 16:28:20 +0300 Subject: [PATCH 12/36] Removed unused field from LoggingSetupRecorder --- .../java/io/quarkus/runtime/logging/LoggingSetupRecorder.java | 1 - 1 file changed, 1 deletion(-) diff --git a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java index f10f9c3dab731..70b062345f61b 100644 --- a/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java +++ b/core/runtime/src/main/java/io/quarkus/runtime/logging/LoggingSetupRecorder.java @@ -55,7 +55,6 @@ public class LoggingSetupRecorder { private static final org.jboss.logging.Logger log = org.jboss.logging.Logger.getLogger(LoggingSetupRecorder.class); - public static final String SHUTDOWN_MESSAGE = " [Error Occurred After Shutdown]"; final RuntimeValue consoleRuntimeConfig; From 48d0c87cea8f911cb2818c2cdd67d66a5c98ee55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Michal=20Vav=C5=99=C3=ADk?= Date: Wed, 14 Sep 2022 15:50:21 +0200 Subject: [PATCH 13/36] Adjust 'challenge' selection so that custom auth mechanism is called fixes: #27180 --- .../main/asciidoc/security-customization.adoc | 18 ++- .../security/CustomFormAuthChallengeTest.java | 108 ++++++++++++++++++ .../runtime/security/HttpAuthenticator.java | 19 ++- 3 files changed, 133 insertions(+), 12 deletions(-) create mode 100644 extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/CustomFormAuthChallengeTest.java diff --git a/docs/src/main/asciidoc/security-customization.adoc b/docs/src/main/asciidoc/security-customization.adoc index d3130db9ecb9c..456090e604ee2 100644 --- a/docs/src/main/asciidoc/security-customization.adoc +++ b/docs/src/main/asciidoc/security-customization.adoc @@ -84,6 +84,8 @@ In some cases such a default logic of selecting the challenge is exactly what is [source,java] ---- +@Alternative <1> +@Priority(1) @ApplicationScoped public class CustomAwareJWTAuthMechanism implements HttpAuthenticationMechanism { @@ -102,18 +104,21 @@ public class CustomAwareJWTAuthMechanism implements HttpAuthenticationMechanism @Override public Uni getChallenge(RoutingContext context) { - return selectBetweenJwtAndOidcChallenge(context).getChallenge(context); + return selectBetweenJwtAndOidcChallenge(context).getChallenge(context); } @Override public Set> getCredentialTypes() { - return selectBetweenJwtAndOidc(context).getCredentialTypes(); + Set> credentialTypes = new HashSet<>(); + credentialTypes.addAll(jwt.getCredentialTypes()); + credentialTypes.addAll(oidc.getCredentialTypes()); + return credentialTypes; } - @Override - public HttpCredentialTransport getCredentialTransport(RoutingContext context) { - return selectBetweenJwtAndOidc(context).getCredentialTransport(); - } + @Override + public Uni getCredentialTransport(RoutingContext context) { + return selectBetweenJwtAndOidc(context).getCredentialTransport(context); + } private HttpAuthenticationMechanism selectBetweenJwtAndOidc(RoutingContext context) { .... @@ -125,6 +130,7 @@ public class CustomAwareJWTAuthMechanism implements HttpAuthenticationMechanism } ---- +<1> Declaring the mechanism an alternative bean ensures this mechanism is used rather than `OidcAuthenticationMechanism` and `JWTAuthMechanism`. [[security-identity-customization]] == Security Identity Customization diff --git a/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/CustomFormAuthChallengeTest.java b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/CustomFormAuthChallengeTest.java new file mode 100644 index 0000000000000..696e130ec95d2 --- /dev/null +++ b/extensions/vertx-http/deployment/src/test/java/io/quarkus/vertx/http/security/CustomFormAuthChallengeTest.java @@ -0,0 +1,108 @@ +package io.quarkus.vertx.http.security; + +import java.util.Set; +import java.util.function.Supplier; + +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.inject.Alternative; +import javax.inject.Inject; + +import org.hamcrest.Matchers; +import org.jboss.shrinkwrap.api.ShrinkWrap; +import org.jboss.shrinkwrap.api.asset.StringAsset; +import org.jboss.shrinkwrap.api.spec.JavaArchive; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.RegisterExtension; + +import io.quarkus.arc.Priority; +import io.quarkus.security.identity.IdentityProviderManager; +import io.quarkus.security.identity.SecurityIdentity; +import io.quarkus.security.identity.request.AuthenticationRequest; +import io.quarkus.security.test.utils.TestIdentityController; +import io.quarkus.security.test.utils.TestIdentityProvider; +import io.quarkus.test.QuarkusUnitTest; +import io.quarkus.vertx.http.runtime.security.ChallengeData; +import io.quarkus.vertx.http.runtime.security.FormAuthenticationMechanism; +import io.quarkus.vertx.http.runtime.security.HttpAuthenticationMechanism; +import io.quarkus.vertx.http.runtime.security.HttpCredentialTransport; +import io.restassured.RestAssured; +import io.smallrye.mutiny.Uni; +import io.vertx.ext.web.RoutingContext; + +public class CustomFormAuthChallengeTest { + + private static final int EXPECTED_STATUS = 203; + private static final String EXPECTED_HEADER_NAME = "ElizabethII"; + private static final String EXPECTED_HEADER_VALUE = "CharlesIV"; + private static final String ADMIN = "admin"; + private static final String APP_PROPS = "" + + "quarkus.http.auth.form.enabled=true\n" + + "quarkus.http.auth.form.login-page=login\n" + + "quarkus.http.auth.form.error-page=error\n" + + "quarkus.http.auth.form.landing-page=landing\n" + + "quarkus.http.auth.policy.r1.roles-allowed=admin\n" + + "quarkus.http.auth.session.encryption-key=CHANGEIT-CHANGEIT-CHANGEIT-CHANGEIT-CHANGEIT\n"; + + @RegisterExtension + static QuarkusUnitTest test = new QuarkusUnitTest().setArchiveProducer(new Supplier<>() { + @Override + public JavaArchive get() { + return ShrinkWrap.create(JavaArchive.class) + .addClasses(CustomFormAuthenticator.class, TestIdentityProvider.class, TestIdentityController.class, + TestTrustedIdentityProvider.class, PathHandler.class) + .addAsResource(new StringAsset(APP_PROPS), "application.properties"); + } + }); + + @BeforeAll + public static void setup() { + TestIdentityController.resetRoles().add(ADMIN, ADMIN, ADMIN); + } + + @Test + public void testCustomGetChallengeIsCalled() { + RestAssured + .given() + .when() + .formParam("j_username", ADMIN) + .formParam("j_password", "wrong_password") + .post("/j_security_check") + .then() + .assertThat() + .statusCode(EXPECTED_STATUS) + .header(EXPECTED_HEADER_NAME, Matchers.is(EXPECTED_HEADER_VALUE)); + } + + @Alternative + @Priority(1) + @ApplicationScoped + public static class CustomFormAuthenticator implements HttpAuthenticationMechanism { + + @Inject + FormAuthenticationMechanism delegate; + + @Override + public Uni authenticate(RoutingContext context, IdentityProviderManager identityProviderManager) { + final var authenticate = delegate.authenticate(context, identityProviderManager); + context.put(HttpAuthenticationMechanism.class.getName(), this); + return authenticate; + } + + @Override + public Uni getChallenge(RoutingContext context) { + return Uni.createFrom().item(new ChallengeData(EXPECTED_STATUS, EXPECTED_HEADER_NAME, EXPECTED_HEADER_VALUE)); + } + + @Override + public Set> getCredentialTypes() { + return delegate.getCredentialTypes(); + } + + @Override + public Uni getCredentialTransport(RoutingContext context) { + return delegate.getCredentialTransport(context); + } + } + +} diff --git a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpAuthenticator.java b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpAuthenticator.java index 0a2ce19a3343d..8b2de1d915bd1 100644 --- a/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpAuthenticator.java +++ b/extensions/vertx-http/runtime/src/main/java/io/quarkus/vertx/http/runtime/security/HttpAuthenticator.java @@ -137,10 +137,14 @@ public Uni sendChallenge(RoutingContext routingContext) { routingContext.request().resume(); Uni result = null; - HttpAuthenticationMechanism matchingMech = routingContext.get(HttpAuthenticationMechanism.class.getName()); - if (matchingMech != null) { - result = matchingMech.sendChallenge(routingContext); + // we only require auth mechanism to put itself into routing context when there is more than one mechanism registered + if (mechanisms.length > 1) { + HttpAuthenticationMechanism matchingMech = routingContext.get(HttpAuthenticationMechanism.class.getName()); + if (matchingMech != null) { + result = matchingMech.sendChallenge(routingContext); + } } + if (result == null) { result = mechanisms[0].sendChallenge(routingContext); for (int i = 1; i < mechanisms.length; ++i) { @@ -169,9 +173,12 @@ public Uni apply(Boolean authDone) { } public Uni getChallenge(RoutingContext routingContext) { - HttpAuthenticationMechanism matchingMech = routingContext.get(HttpAuthenticationMechanism.class.getName()); - if (matchingMech != null) { - return matchingMech.getChallenge(routingContext); + // we only require auth mechanism to put itself into routing context when there is more than one mechanism registered + if (mechanisms.length > 1) { + HttpAuthenticationMechanism matchingMech = routingContext.get(HttpAuthenticationMechanism.class.getName()); + if (matchingMech != null) { + return matchingMech.getChallenge(routingContext); + } } Uni result = mechanisms[0].getChallenge(routingContext); for (int i = 1; i < mechanisms.length; ++i) { From d19b9e51ab16975f47fb6a5b4d2d8146577fe053 Mon Sep 17 00:00:00 2001 From: Fedor Dudinskiy Date: Wed, 14 Sep 2022 16:40:37 +0200 Subject: [PATCH 14/36] Refactor Qute loop helper to make clear, that parity is one-based See the issue[1] for context. Also add a new test for this functionality. [1] https://github.com/quarkusio/quarkus/issues/27931 --- .../io/quarkus/qute/LoopSectionHelper.java | 9 ++++---- .../qute/generator/SimpleGeneratorTest.java | 22 +++++++++++++++++++ 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java index 81b8502db1072..1f11ea46fe198 100644 --- a/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java +++ b/independent-projects/qute/core/src/main/java/io/quarkus/qute/LoopSectionHelper.java @@ -278,13 +278,14 @@ public CompletionStage getAsync(String key) { } } // Iteration metadata + final int count = index + 1; switch (key) { case "count": - return CompletedStage.of(index + 1); + return CompletedStage.of(count); case "index": return CompletedStage.of(index); case "indexParity": - return index % 2 != 0 ? EVEN : ODD; + return count % 2 == 0 ? EVEN : ODD; case "hasNext": return hasNext ? Results.TRUE : Results.FALSE; case "isLast": @@ -293,10 +294,10 @@ public CompletionStage getAsync(String key) { return index == 0 ? Results.TRUE : Results.FALSE; case "isOdd": case "odd": - return (index % 2 == 0) ? Results.TRUE : Results.FALSE; + return count % 2 != 0 ? Results.TRUE : Results.FALSE; case "isEven": case "even": - return (index % 2 != 0) ? Results.TRUE : Results.FALSE; + return count % 2 == 0 ? Results.TRUE : Results.FALSE; default: return Results.notFound(key); } diff --git a/independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/SimpleGeneratorTest.java b/independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/SimpleGeneratorTest.java index 5784b8a310771..e0a9a012fcc94 100644 --- a/independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/SimpleGeneratorTest.java +++ b/independent-projects/qute/generator/src/test/java/io/quarkus/qute/generator/SimpleGeneratorTest.java @@ -143,6 +143,28 @@ public void testWithEngine() throws Exception { assertEquals("10", engine.parse("{io_quarkus_qute_generator_MyService:getDummy(5)}").render()); } + @Test + public void testArrays() { + Engine engine = Engine.builder().addDefaults().build(); + assertEquals("1,2,3,4,5,6,7,8,9,10,", engine.parse("{#for i in 10}{i_count},{/for}").render()); + assertEquals("0,1,2,3,4,5,6,7,8,9,", engine.parse("{#for i in 10}{i_index},{/for}").render()); + assertEquals("odd,even,odd,even,odd,even,odd,even,odd,even,", + engine.parse("{#for i in 10}{i_indexParity},{/for}").render()); + assertEquals("true,false,true,false,true,", + engine.parse("{#for i in 5}{i_odd},{/for}").render()); + assertEquals("false,true,false,true,false,", + engine.parse("{#for i in 5}{i_even},{/for}").render()); + { //these two are not documented in the guide (https://quarkus.io/guides/qute-reference) + assertEquals("true,false,true,false,true,", + engine.parse("{#for i in 5}{i_isOdd},{/for}").render()); + assertEquals("false,true,false,true,false,", + engine.parse("{#for i in 5}{i_isEven},{/for}").render()); + } + assertEquals("true,true,true,true,false,", engine.parse("{#for i in 5}{i_hasNext},{/for}").render()); + assertEquals("false,false,false,false,true,", engine.parse("{#for i in 5}{i_isLast},{/for}").render()); + assertEquals("true,false,false,false,false,", engine.parse("{#for i in 5}{i_isFirst},{/for}").render()); + } + private Resolver newResolver(String className) throws ClassNotFoundException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException, NoSuchMethodException, SecurityException { From 2c798aad2e9559e9f4a2b12ff5b98b8ddeff3c6d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 14 Sep 2022 21:39:38 +0000 Subject: [PATCH 15/36] Bump nimbus-jose-jwt from 9.24.4 to 9.25 Bumps [nimbus-jose-jwt](https://bitbucket.org/connect2id/nimbus-jose-jwt) from 9.24.4 to 9.25. - [Changelog](https://bitbucket.org/connect2id/nimbus-jose-jwt/src/master/CHANGELOG.txt) - [Commits](https://bitbucket.org/connect2id/nimbus-jose-jwt/branches/compare/9.25..9.24.4) --- updated-dependencies: - dependency-name: com.nimbusds:nimbus-jose-jwt dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- bom/application/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index e1b284c5a8587..0b2914d12e1f5 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -201,7 +201,7 @@ 2.6 0.10.0 - 9.24.4 + 9.25 0.0.6 0.1.1 From 8ad6a4a4b6e5c22638dc6ed90f389ce32405b157 Mon Sep 17 00:00:00 2001 From: hmanwani-rh Date: Tue, 23 Aug 2022 14:28:17 +0530 Subject: [PATCH 16/36] Docs: Optimized introduction of OIDC Bearer Token Authorization document --- docs/src/main/asciidoc/security-openid-connect.adoc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/src/main/asciidoc/security-openid-connect.adoc b/docs/src/main/asciidoc/security-openid-connect.adoc index 0b03187201eb9..ddb149202842e 100644 --- a/docs/src/main/asciidoc/security-openid-connect.adoc +++ b/docs/src/main/asciidoc/security-openid-connect.adoc @@ -8,15 +8,17 @@ https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc include::./attributes.adoc[] :toc: -This guide demonstrates how to use Quarkus OpenID Connect (OIDC) Extension to protect your JAX-RS applications using Bearer Token Authorization where Bearer Tokens are issued by OpenID Connect and OAuth 2.0 compliant Authorization Servers such as https://www.keycloak.org[Keycloak]. +You can use the Quarkus OpenID Connect (OIDC) extension to secure your JAX-RS applications using Bearer Token Authorization. +The Bearer Tokens are issued by OIDC and OAuth 2.0 compliant authorization servers, such as https://www.keycloak.org[Keycloak]. -Bearer Token Authorization is the process of authorizing HTTP requests based on the existence and validity of a Bearer Token which provides valuable information to determine the subject of the call as well as whether an HTTP resource can be accessed. +Bearer Token authorization authorizes HTTP requests based on the existence and validity of a Bearer Token. +The Bearer Token provides information about determining the subject of the call and whether or not an HTTP resource is accessible. -Please read the xref:security-openid-connect-web-authentication.adoc[Using OpenID Connect to Protect Web Applications] guide if you need to authenticate and authorize the users using OpenID Connect Authorization Code Flow. +If you want to authenticate and authorize the users using OpenID Connect Authorization Code Flow, see xref:security-openid-connect-web-authentication.adoc[Using OpenID Connect to Protect Web Applications]. +Also, if you use Keycloak and Bearer Tokens, see xref:security-keycloak-authorization.adoc[Using Keycloak to Centralize Authorization]. -If you use Keycloak and Bearer tokens then also see the xref:security-keycloak-authorization.adoc[Using Keycloak to Centralize Authorization] guide. +For information about how to support multiple tenants, see xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect Multi-Tenancy]. -Please read the xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect Multi-Tenancy] guide how to support multiple tenants. == Quickstart From c218ec86c96e4e07638fb0bdd0c1c573040279f8 Mon Sep 17 00:00:00 2001 From: hmanwani-rh Date: Wed, 7 Sep 2022 14:04:01 +0530 Subject: [PATCH 17/36] Incorporated SME suggestions and added graphics --- ...y-bearer-token-authorization-mechanism-1.png | Bin 0 -> 87898 bytes ...y-bearer-token-authorization-mechanism-2.png | Bin 0 -> 72522 bytes .../main/asciidoc/security-openid-connect.adoc | 12 +++++++++--- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 docs/src/main/asciidoc/images/security-bearer-token-authorization-mechanism-1.png create mode 100644 docs/src/main/asciidoc/images/security-bearer-token-authorization-mechanism-2.png diff --git a/docs/src/main/asciidoc/images/security-bearer-token-authorization-mechanism-1.png b/docs/src/main/asciidoc/images/security-bearer-token-authorization-mechanism-1.png new file mode 100644 index 0000000000000000000000000000000000000000..13271519264dc182954789dbc0ad9f9d410db594 GIT binary patch literal 87898 zcmeFZWmuJM*EMR=NK1-zDBX>8NuzXkv*<=8Bm^Wxy1@eJE@_Y!MY>Bmlos|}_Y?2; zW54_N{_)+1$1TEI*SfCroadZljxi&Q|VTrmmLPBrKfGEU6S6O|308EKMzZ5WSWn@D3(hEgg3q<;Ozi z&W>!R$k(uWJG#KTuU!)n_jWNgx3_esGPAU{brPl7ZD^sPvb7MU(dJd=PL5aQw$ z;^n3K_kT3-fvy%-LK@Pt|Nc1mmne;mySs}JJG+;c7n>J1o3pDmJEx$aAUg*aI~Nx# zyn@xu$I0E)o7KsU_CHsUwsbRhwRLf~b#|gcUeVOd*~49w25$CWPvPkD-^)6={d=Wg z&Dgz7UD!F%f4#7qmXC`iyN0EkvxloW?41=Y z@}pdYq+Bgc-JM;voShy1vyf^w&hE}`HqI_oT-rxvpP-|vOptGm~dO|@6YsEO%c@GsrNaw!k`Ypl|=jJ ze+%OIT6h2TCGy`V#2dqS|Naqm$R79i9NPbRsr7p)x&OYjZAk59%zv&z9g>xE=idKZ zRb1+x(0{Lj8yF_!{@<%mUtoEx|Mx1AC#3)P1OKyb|36z&+CK$KZxb;k!z*n^3m>P@ z`FcCgHu{_%SpKDvq;uP;-(+)-Xi=c%`f0E(hewl=@ZiZu9@}qpaXkigt}9*9BuBnWb0H{%l$gWzs928HUIY=08s1g2Wu*zHU(O{_EoO zP=St1GQ5LJ>H78Sx)oN+H2V?Z;kEv!yz_8xkYsTYemHkgPGvE0=i%2%DO{Ehh%fwpeJe=gcYdHBB7aA1qr{-@ppU&JBsBEL2hDd7-Vxv4C49Rb zClM3=j7}ac?iL~u<8!@QCNixx$5jfszn>p1RPHm@qaAD|=XV;`s1N+>`;DK8nB7Rk zZ54~PK7mnntVoyJyz7;;5l#y3B^oPs0_|}yzw@kglp5V}GyY&s{_*yd*THh{MVhGJ zk!gDvufzD>w*n>fyZVa>VgYCNKR;)b*o0WXa?1W!!IA zrD4S1PZ4 zwZnvDL?M-?sKroblTMkL<<~@JjeInjS#`RNKX9iiSz;NV-m9o&2=Ur|e~*GqhO6@8 z$%77Sc-pz3tG{(_YvC@d{V8!&mx}P%?O#?#`vp9=#v!SAZBKe!oFMMdh|w!1zSsJo zvA@_Eml3k19OL-#w(a&*&F<0mlxn7k&+qk-nz_IR2nQ%XZ6$^Y#{7%9S|4ws-fH|I4arWfHrwimS+(`>@e(!ICKd(}RLN+j zqSW);QFmGOawKmrKD$gv*%(7mN%d61B@Am_o@3x`_LaPzK4A2~XXdl`n-z3p^=Mfc>f`m4`NPtoEM%d{VJTlJH0 zn%z>4d8qxlR{!p$RbNu{Q_TbhWmwZBPIFmJSUw21adf`FU+vCreNtB&nQ8E1H)9Q|kwUzLZT z!hN|X4vVd*i7sUCXHxzZtkD*&PP4zyP^M@E_toY3ZcD)V5vy_Y1)L`Ntupg&BcBzb zJCiizJK609Y3#<$8$UiJEbCR-O?**c-|gF0Nf+R>>Q7ckU@+aF2<7MSh7Deyu5$}U z!QM;RK;`dig0g^3&KD9AviCOVB{q2y9EoMV1pfU^b&Rf+zPI8Rds0{!by;Eoz7Iuq zEY16pSPAougZ}f|Pq=NnwdQ09^&`T3uedf| zM1Rxfg!HT6IAM2|-j_J{XB)hBwAbmdmqG=G`&3(x|0SPDNdRr8ze*>{nQ#M&4yj6*0DEaD92F^CtEBNFFLq6Ly^9 z{+z#(-^>6WxnCE;|M6QkL)mDuP>K^J;u74p*lf>qPV=t$)?mkLj1Me=Lq25gx|+Iu z6pSu;`q{u|{>yXNpd4+A)8C`254GOAY>k(}nd5PqPR4wbB^iD~qgnX)aCKmFw&^?+ zlXy=lrlsa>C12(1vD@!VKh65>Hid_WYa98%U!(8h5W?PnHlFC?g_$TXIQon^xBNYVTa!FUv)m1TKXX|;!fv5ELV?~~?pW@=!cSaIn5;7qZ0jqv>NLbi6 zokfexNyn*bQ?eQObZd<7QW*>veDWgG(H~WsIhD3x?pa;NR@*?7h7`$S5T`Wi)hjA&Ux~(@-0_DgFBW(F@pvE-(I`{i*Oh1~`Kj_Xj>c zKwoRf*0)YSYu&8Vw)!_Sby{PIZYg-pb=c6sa(*-UP-W0#y+N{7@j3-c4d& ztdQaB?c^n!BnsyxO4V$%a|)jndg^!wTDBP3wqK7B(zHUVLKSL*1+VGoPnDgzSE{N+OS`a7~XtzGOwQC+imZ-5Qay;wQMc;CBvO&WRo=Lhg(AUHvD@Eg{}lez}Kadu~sbv|ReWdGO?f{-lYSC{H#z zcW;V4@>Y>SUA&T!-!`v3I|7}pTM;j)j2)^$_5HL%X3EN*Da;r4y24RxQ=XP_H`j8>3@`que+@eQ)5j>5HJ?$+>fzo(-rMoOT!>ISo6^`V|MwaFvB9;Wh{l$pm-uZMZ1_tzDA7}3W6G1;*7UL+n zEYOBZo9YT4t5dwrhP)-B{n^0Z44&YD}`{n+3>>& zd4b5*If#SZ$tuh@^MyUPpNR0g8uJM#+Ddi|S1PCRWrzi&R%*Rhdu|_)Tp0Oy#%)M+ z^qp$=kJlKIR9whR4(ZWR;)xTF^&rI{tY(x)iQI=hC=fIa4!>9aZ2pX}JKmYe@;^}c z{gun^{Al|wi>`W;?;pr4>FJ1?n6Z1o73I@QXr~dyi|p?NNl7fL=f0(O(c=WTm{kk* zBEbVbnbQ*^bz+LO?v-0u)W-WIyan42@MR83^K1yNMdZuq+!Lx9$=-|7=s8_b#ys({ z`|*(%N^hW*V(8y57vm&^nZeG@r(I_(r#^)$8I#QcenleU-v%>;p@7?WCL?QNz@H!5 zj)4r;&4+`+fC^~*n?D-2h^-7TsO=T88`Qc0F44)_+jH?d*t*WDo_p&$;FmZOy^?p^ zCcGwJuBF{nNVFOGd$w7!+j1p#_xTAV$Ej(idW(biBNutPHge(QoXa_kg2;p@IRCFe z-GLVA;Ayd*?jWvDGtH%e5|)n(^EXJ`6>o-ESHp=_eIS0)cx+#gF)Z5^NltSXGenYj zR5U#*jnVo_#f#w)!Ky6xC+y^Vk#53KF!!^&`x3JCp4*C|is&98;pJP1i*_7w|HTN# zlcVFgpc>zMZ-cK(K7Q{$gG%itHhHjJGe_f8YveRGhj1d8$WeKHs@EK32HJ&kGPRBG}&j>!oK z7Y;i6^-fAsvS^5yUCj5lP5-xQ$0;-Ne=?^xZA?%A2mYOD;$jjpe*sp8wzBfaCy<_K z`k~Dg7R99eCfzD`7l6$^AgJw`hM&?z=6hQeL#{bE=P#z@al6+N*^M)g?rCtFw%wAR z3gZfZhnD9_A*i$d>Z#aCtgAA)PV@?!Z6zM_*hJ78;3Nq-&dv7dJuN26)dxI0-|rLx zQWX%Wm5I9h9aGSCximZXw{uNpq1ajujta{g%*2a^-R7z0fCi5_ToWX0L!<}X(kKz{4Vw<-`cFRQG_>c*=8kxD>U@d!VmbQ60-u@I6_G-m%VMfq&_o%9Xd$YX&62o z`_83lrSRhNXv!J75^O5?VUsj$PbcA9OhgH&5N z;lSV2Q;%o3hM7Yx44+$J8}osU8u$LkAnD+ivW&5vOrCE_!2~N3>5gk;Dgyo|d!?rB zW1kJ9Zs)WyH25bN&$%w~73hVBynGQn)FnQ?N0F(6b!Hwb8g}31f$vw$%zVjE49ML< zOVRv~T}~PR_`S;tOvN;}PtvFzBRv20E{jQ;Z)Zjqvt0lhN2kmiVvs1xE&KEY4<=Z2 zuQ220$}KSu!qJGCH7q*9@rWKitwed`kdfIRkE_s#XH7tCW=}`qkt*!PrY9Gw5;gOb zkLTv2Uq(&7yqVWU5mu%RN(Abi2<-b}6hB;(T<+lD?82#i=dnG>%ht!|CcGR)-gJ4k zNw@ap`OTy+5EI#gK;y(t=d>#;L^ZVW+kHoy>1r2{GGx)=I?V8zrB)ZvI99>cit$?r zhfYiGX)PviiUO@WR2l$wth(j-hAU~jf2fhkm|<6oLIizG%38g=+ZOWSMYF2tA3~#% z#5wa{HcF|7i40jZDK`38dks62RZ(-ZtD*`I9u0O9zRaz)$-qDp%X(k26uaL^#gOmF=-rVW0@9sl4_mtr^hA3>F&@A z2?!+LSb5cej*X4X@%ICMkHCX!2C1nlCv%uWbzd9G@-nV!RjRdsu2w4r2&}653A)Zy z(WS2F3dNo?hO?t>Vf!(b$m$QcjGDq4pHBfLtL!=}tEEr31YH5x)3>RX|M-VJk6PLa zSOlH>WQLqPHBJ-r2IUs$T~Q|Jb74XW6!Brsew)QLMcR|7r+--a@fm(d(p}>_w1H1K z%AQQU4bZG|h>}q=n~cw4J)FeT^Xl^N5!o2OLgQ}pImTUFDI?8Ffq+eDQ|62*&iTa@0Yh8RAMBTk;0ruz+qCMzeG=j4P z-NKwR2JfWF*?#KxKj>v7x%Q6zi|DFO!;siH7B?;~?iXYGxCsqoDC@kbq{lJWa^s*U zEL2Vt`moBTUN}h@2*-8j>2g^&WyKo`T5ASdM)4VACRYonTI`K`H+1EkT&S<7E*~_! zW?N3&+L)+V1L#FlLb_G(?;xs1!EKp&ub!7)<0TzYnY>x?d)vJe!I+uMX^x_50ZsW% z^SP7$zWdH}ok#;;Vwg>TmiT>h!c%H7_w`}eZS~Kn_x)d6?~j+6LlbgX$~=}PH3Dg$ ze~18w6KC@#jtGa%Fx8D?#|0G1r-}#g^p*sg3Y~KI145+n#Sl7Gc0|g`x$aN{7(X%U zgrD5V$T?X5R)AT6e!G;n@44Rd`XoKZ&Y-^HH%#ie0GaLyxtFS7?6xzG@60raLt6JI z5%v9@D-%^zxV(F?(&t!Byl}PkLTEh2C&V}Lp>~WO#_jPYKd&M(zVD+@c&mrsqajE# zggkOFu9jlOWn(B5F7Q4J#G{q{{$4$JEOi^L0hk9!zqvPWMWuCb3|BtXuP)-ZVm_E+ zq3P{S4wU|qq)0zbEfw~?#Q45oJcOLBt1ua2mIOVL)k6sXd3g@WG$#kg-WzSBRw4@V zK>zVlQ*u6sFMZTE{F{$w{oIy&7IJXVFlU!e*Rt4kO6TFz1)^!I+t~110~|d{1tj%` zA0MCZ%(cvshsw39pRA`wQZ|WH+hG;=wZFuf())CwUt%Qs8(T>b;3qkWWq{E5V9l&= z-xB}9x74%OFY}qIRj>}bW?Ig7gzfG=xS_8U{;B6RFe*?S4k1YbP=+e%e6TiT49s=f zcRonh(0UNCtQ~S-Q3!DO9c>+%ePQtbAhmY1$VvL`Y$4arpLy!SN@cg`IQl)4C`Mqo z93Ppq-NTAh#>oEeLx_{v0|{Bpp{=bgoKICS z_TEOjbr$h-wTi(chf0V#+w!Y5qIRtYPsdb+4!Kd(kUi2!oF|ij6NKH@)i$*4@qD)K zNX6lxqwBT=iWV+FU>r3UNT07k%_+1sC4Z#{nUvk5M6@@Sr^+s zOXh%WvGuc3U-moRQSM_56C?Ht-v*$8`#Ap5#^rI#)kG?xmY;v&;5j5$hsJ)0ky^~w z&ro)N^;B>Ew)P-D`AmR`sYi+kVFFy34={2Lw-#f?FHe9|I-EGLKKuQP=Y7AAH+xI9 zdlpU|-jtTy>4V4NbZxxEgDusuPuv3|78AIwRlLp#->%#oE_j#eyU~hzzep|Tg`mR? z>FbI~EXaMB+~@-|Ai+u->weh9qMP=->kB{`=ZE)PtRNpE_L`#y;&k!bRLK!WWav)6 zp@(2GZl2z7PqYST!m3yK$jGKiv+I7)#eRmcS8l?|LlwW@BXXk4JivNR7XbA-T?XAA zMaQ3sMrwTlmge#vbAL5=>_=XqHP^38clCY9uD%?EW-a~AD;G`ZSt7Ps039?S{{Z#Z z6h+EymEDV^ON)6u#+r`Aa0V(m$0Fi3vI5U~?^<#7@w?1_0USD6o9VGRhE2jT4}o76 z$`TwLtdz*qs!YtHW8$@dA3srQDy6S9V)Te1L7^(5vF}Yc?aIjQ^)0!+67QX9K}zO# z4jl*qik2PGo;>tQs{yj|7!7OlZ;*DhvmZT&C3xaxDmAG!infr(rG_eU zJni;fRR{I!BqVC!aUF>ol?SRq>C_^C^8-rttl+KDLEtAs>1)_!Kihzk{-{&72q{%r zwCLpxRw37Av?m`b`q(`nnPfN?G0VMp@uJplFkR5-m*2+skkvrUTu`*jwKUpwQ?uJWhUw5}yR8 zhN^lTZE9^?>6AVUPF0kn)5&YQ!0zRWnu0xH_41q-F%MUC|9JasI`G@~)ntkId^`QR zJo=|$Qr)6K# z0K@YG=YdpSL9=tuH0$sZmIIQTBrASgIHAYm8B0mHYe4X)aZRmf0(%jsU08oH1zijr zfv*fD_r&~@FJ6gUt|yK0xTZU&ybQ0y{`Pw|0%-LZ5_tI!wgpB&w>}M?+Z26X0FI{G ztn&wD%_g~|s?Adg_y=Q6!%C2h%(2{8l>JFnGn|_3jV7BBn!w4xzi0bs_3YS0hl6i9 z%~!21PWGXe4Jcr&nuS>Wk>)WI z4xzv(MIV@aG7?OXV4};kCabiNr6M#?dy6r z(hxEq#Q*-*T=t~Oa3+{MK!Ewn>1=@5B*37#2~ZaHno96G+dWx;J0gBl^)2hl1VF z($6nXmSRDZDX3hGudwKi7Z17wWUn4>HcaE(A5R3L39kNX7hhLfbHipn5}YTHkRjq8 zt^k^DzzL52jJFmYH-B@r)jxdO?)xWg_Tw*(b;jouE7NRg5x@MaJ@gxcxYwGl>$e0p zohwHXv+qQ=jRsCv*%LF$krpJfO?&+4#r9jVYG|C&P9DdnS9DnhYWOL$iA_Lhq1ZAs ziLZiA?ZwM(>q9;8>XWR5&bm8eGdE~K|*pyhcYqeT&Mpl z(ZyQ=uFE}SKL6sPuMx4)_-dKU?R2Mg8(W=RtQ%7tdM%DG6(1q*wJXrEl z$w6n?4|z2Ag*_c0Z*pQ#Q&U%_LdTc{LPCG!uTVps_i8x+=>=&UL^3_PQ{@CzUOloJ z%G_?A)qs2q{OMP2xpoKO*qGEQ21IY8V?Ke?3E4ZvPr&EE0+g4o*!!yO?Dfz8j)m7- zHiY&edmtf?4Hmtq7pTsmwy)lAMfS|k zk~!HQ*Q^DS<;s%#F8_O{B8418r7xy4{r9_fuj5*@WJi5~@0iS8pyf+|FMk zBY>M-S|jYma4ll#e&9*#EPjp>Fhn1WTbi-0az4MKH@P|A-rnB*igc+lql-cGWImVz zdR#Z93JdZ$EE0}uni6r{<7$N;wLX|>zAv?g;veDnEnH`FtoSErLLekhJ-fT4Rc@i6 zpDn7l=KHCqj|DwWCt6^6aj`S1`dXx;9}hGH8%}B6%5m%v*|)jBmiYmw^&7RKeD>(Z z&6_W=NX<4s2v1thpz@uBp3RtBZ+dqNDW`{}6%Q&o&D*j3=uwZOkpUu-XeM_A1TyJ* zB86aln|#MKBY-x@@G8!s_#0i+T`A;<-sH{TZyubt^6@(Wy0p2Pw#1mw14 zE#^n^B17 zq{M6nC!NH`YNjmEROr_@oe)KqzR{h!=?z`zffX&1ta?iaDyjj|%tNvm zx0vplTQWTcPClJYp!)K7Y-p_&i#DKV)mu&6fnyjA zDm+RCcSKs-IG{*~7L9t**=}!Jx)ZfxJzjnHFf)}p*+n?J77xP_>96A$8cDm0BUPVG zmxe}fI}*r(60rdIrjj?<0SnLP4QR1UjCKMT;(kXC2LosMC|D%8l$4jt397C{zMY}9 z8DLWw03nsUX|D|vjSD5>OW#{|XSQ$(+IFN~I%!{`Tf%Q0L(1J{m3aUSRywE;DkSYo z!2~xOLbQ@sXr(B9mhm?31uAZSUPNq+m(e~+J9p5H-PcXG-Cnn-IteiQoFm2d6O?Lb zQda9~7?V+BWTC~`MmFq$TCoT-=1X$fsRAWp^0h@46sL!RImAFEW2!B*vK8j)mk}-j`=U zJ+DK)s2 ztSgOGhLk-pBAblg>9HxDnDn>!u^MM%GG}SZls~P%dLakaMhjpNdk6(a$kuMxTKE}i z7wi47?KrifL#qbO`N>BuUEo5#K0*ZUHXY-3p;CH(VGs&9->w1c$OZ7{K=r-3w(Zkl z4Z_M%W^dyFbSThotrT4gfVReL7WAt1^f!5X@)MBM>V`dM>CA!aTZ4wnbs10z7gJ|f;<-lHP9a`=|8YMf{w^QHdt@M^y2dsu3g zA{Tq@`}QM0atQv!Mq~N_Lx^)vNSZss%+|0`#7^YH#Wj%^7nM7hyemC%!K-(JGH!qN zVR`?D`D%<*;ra=jBnLOYz&=1HvVm_5D{a)zJ?egb|DefCnRx3f<+o@4-g6p}q?r{E z97{Es0Y|!iyB*ujXB!WH7eIv%C;PS7JeFp^8XP|jBpZuvIn`r2x`WRiAxPc=A#8lQW-ET-AYZ$!rfo>WoHyDU_HFCE(q9n-_yeTyf69=F}PwDA_5V;G7379&^Un}FXNSRXFxntsE&ER|e{%bTx zKogmlnTMHjSr5mEK%k{q)fRnR!pu?YWwRJJ{ehLPueM6K~l=t-{)!N z5mro7Q0^d$Xb0YLeN_S#i`!{hfPB2?311K&lStSffVjF(9m)|Qy(y0 z0ITFENPUTdf`Ts(T`|ZKP~n{p6L@L{IwyAY#A~T~moM^S#Zn&&xoa@&Y&*`Kz_~b} znoP32pyN{?Hu^rkF?M9GlhQi&?!-BQw0ZHsIx8Tl&3wZoYBodEkB^0=NIg$>_Knk0XmU17vmO_mJ zL_$|RR&1|{6LU{EOt>}Dr;^j5)g^!UDb*4M#?4}?$jI#>xo-`}Hf@H_o(LkXQhDILGT<1d4) z4Xxqs+feR@!5uj-rPGETOiLglvupBu9(MuFWFsL{hg<%3_^6`TPR-fgF;_lZKNN$= zMQ{C0@k?U;f2eX6pvp;BnZP0Qq)3PJA?8?)!h);OZOy{1g*AbrYtAMa`C`fRX?x?J z4TbePp5xa5KQA3?CZpL(Ds!Km>W*}waZ4Z&eVj7%>Y!H>8_fv~n-l$`6iU*@q)tvE zss#{`GvZ1h^LG!YEk(zbX1?&-sk8p_?0UL6`-x+x6h?EgM0@D^5AZpp(iqiH$L6SZ zdyaBD40GZwoq!}R_c24p(d_977;&;c*OGO=nB1b~O4xH;kx*G;SMFnPK6$a^&oE`H z6u8%lnm^f#J+%?lgBz|p1_H<%W=)2${+4@&fn^?Me71L1T7VfXPF*tQUO4($-kIX- z;2Ss>aIDTcsj~glm}Fl^0ocf~Wc6%3UJyPNh3ke?T^Gc?Z=7s9N{FT18YDP{^X{5T zq^`(2w9lC3k559<)9Z=;4%z}c00<&qMQ}aS-@R0F5M*oqXxP9P8;VW78nLjj4krN> zaP`a(KkNK0%!%g6=^64F!Sy~k1&}6jki1H!RpunIk@xc!K5dFn-{m;Dexvaf=)JrP z0U}M(-%Twh>l~ecs?z*5Q&f0_cH1+(PY|^!d)%dMTc^}8T$nQdTElRjTr60W_iMni z@)XbL8xhil83uRV3HBM!1QQ+MxH<{3skcFstqQofCO7g<)pipDg2tfq!5f#)ds=#c_%9{|+}B@|u8gPBKm1uaI_td@4OMHju$&$}8=wNjh7f&RaEweDGf@qX z(#QvzV@amH{f@g0&WQ7kPbJ6>Jsp3}_x?Yjr$QS;@HL6G4;?IWH;bLRc~2eaurNFT z909U@Nx<>RYDyfTnMN(4gdxglPh7!+cri2BT+Ulp)Jl>O3f}l49K)A4@6eG7fU*c=6GNY!-TD((U6 z&>{dmf!3!Ia%s>VN22RPuNiH<%423m(1M;26tN5}-Y zV6Cdgz@kqabvP_!icP~%uOGQ@F~FcSYLSa3oc2OLEhIVH!3L9>N)sM7H#3js3V@PB zZ_WI?iJq6g3>s%&V)Rc@m-%aLZMFt|aW`&bWXptqbR69YxEn`gp#-1nxGiL-`%#A_ z99ZCZav~Y)$7`E ztUT$T1x(}!D;9MucV@sZ)qkG&lcEQk4J!YHE*H)_*k^K|o4KHW4Hk9TA(0}-b`%V{ z6uHx?UI9T#p77;Ke%pj)ui*y~wg`%c8qwsvZ`re|PRur~loy}@?zu(daR={^sW^cZsap0 zjtp2kL3{zyd`z5czV4)U}+o+RjNg|j+UByAn9r9W74R?g%$&;_#EUqoD|$S~?eL0WU2r1@FKvAYCw?l5$y;vb_R#gQj7DYSyftJqS%;>s`7h zZvUu`TDmCp`ec%f7gi&kjB z><}bhVC#S>`wTjka>uXMyQEX5zpHmHwu%3BrF>iDl@#=%XfUbqKdJo4?NPwc59JXN z@rS|*5{+=+#R)iu1d@S+Fk^GbdLjKu(BRYhgwyTn4I@wwL2wO9vl5}-pT@00XN_bD zk+-u_h@v_F5Qn>XM;1AZ=CMwv4r;?224ynqA!6=F)r#qIm*dUq)xAn>SXyO&Ssb`; zM?G=Z$LY|XZXCi!SZCk47j$)suag)ef2|__PKGomehs>R$@V&-k2wqV1sb+90jz*x z;%3@pkHwec@v|$6g?1q0{GqFytg?R#DC~Nws?m-$UMynNu0-+#GlE7@677nnnXl_0 z+WptJZP+X5&w)~c!ubf))ut^K#a}nxR7lAB7SQ3}`)5op$i%sE2mn8D@UX)G4~QRV ztrtdl&HAjtaa&AJ7j(Nr&Q}Dm58XMwf6<^Na3RiHe<*LVzQ z0|=yQ$Y08-JfPpJ_!5JC3*Pe}3F!_;GJ1lpbh9+AT_)zwYCXV{UAF}NrXyElz?*dh zfh3*CgA=&i?g!ae%6nvFslA=h{D4;xIb@+9IR&}p1u7Cx0a@esdmRrCPeSdhYFw>N z2t?M&_3E8nq)5EngQuU;Hi5qgWCWCe8v559chkFCj^ahGn^gr}UBcakhJ+yJH`4Ch z6XJnJmyehQvBVG}&!V*#imQ+gQ8-TK$e;}KDvg%!xSQ>f+8^-U@${H)Z=s-k%+jNa z;CoU22#xyUhQHLKM~|SUkLD{Zd4~g!^_JcEvKdtWfHQY#=?;iRO24f#STXwkYbG>@ zpgkgIPr!l&25lgb!W-%orfbou@oy9PQ%mEc3fhepVmobs8=UD093?m)KG1L_mmsGx zVdoKM7s@o4N0@Pvv@j{zHm8g}yEv*UUU1t3234Swy;^vAzu9}=?1c;!kS07o!rK9v zW=KdW&^bwlTQEUb;c~KU%(5PP>V6(1vOq?3hrf|1p|hTL$$lF_YRnlJR;1Gc7c@k7G^0 z@X27NFH6^mGnJKB3RB}2DQEBDK8Xho4dr-Z$sitvIPT!z(9TJj?HG{C)vV>KTx{#v zJwzY#B%#06OwS}XC4o8WuCH1GqfIPpOMx#mccyPHun|fLxAI+!F;+hi6O;_UW#7>m zby{bsxtVnIh4wK!Z{+C7FsQVUsT1w%Zly$Oc-9WzI5u+mzCqYqAobGv7!HK6=T<3K z?;I==>h0STEh1l`6)3dXe@=^>{xYa74d@K5ynFfgcn%Ey_XS+OD5Cvbg%J@Xr~YKB zDV^ZP8hD?qAX*`UmOd5fR@`MbqWHAn^tYG=p?OG=p7c?#*d4?`n?KSe{<3N5S{EU8k>W@j=&U7}Y>4z| zXYJ1H0)txHQHJAT3Rd2tpo_hQc7R0G{?aTk%>?A}3i}5)r?|0B(2HR7>V;ldXeeH` zOc>JqI;b5Zoh9TEIeO#9jg$F)Nya-M2^lCQetV~?8e^(x7LztWD-6IXUd72x%qeUt z=hUCdH)h%OEeEmHv+xINdkuR^2uu1HtZ(6L;7X z?T>%B)C$);V$HZqyz-Votub7qD4sS%ojO&vya@*q$`+U3m-O!08J1)tuyL` z543t_Nd&d=zC-0I6H3O|(J3`)1(~g*(I!SOgT}{WUCq^VXZm#9Jl0gr+vxOsOy3#E zr6?R`gH5hRutb9rTG)tK4!Sy@gUCKH3HC+mu)xiFg02U8K&h+DsRZu>?a$K?5s)@C zlZvkC^isee6?6v(xBNd}eG)PaC=+Qb0ms7G;gI+U z?J|t(k?LUUqEhde#JU0j`Eda%TrS7^>l!A;IdobzP%d|1*MO0CID2F(e)OG|gRc*7ITdzCC_tA{ zG!*4I4>8#@M6zhb-V7G?GcflcwSVbM;dr~m#($Mz5514mq1@tU>$GXJp;e34so;x% zlA;*GYGxyxas4k!0!0quJgq;ORpJ2b7%f_%l2;uxt^BWv zRj81zRE-DbY$ryM@flkIt|7SS-+f;(9ui1t_9^+9h_ZvuzXOd_<4-1)N1@aAWL#W0 zP&1xVjx+@Rt?wP2ZSsqFa0iIW-|Gul4_>{3?Jtw>|C8=uk{R5+sKFOG>oR7U&7?8Q zvFVLz-GsuH&+#%W?DFDpNLB1~8IE+u*zJ|8mAA$_FfGH%Lp~*ILqe-U$>%_VjYG!a zS*}TlE$jxS9x${;DL*V7APd3u;&|T*<#V#yQP{LC1Wvjp>AQ2B=C(Ywzg}D82K@zw;vl!dy?dxa(8-9~PZro=XV z?s}m_tox&3KewByuObqSp$e|#KXLepzXc=;_>Iy4ilm()H|WckN0X8WLi&bKoL)Rh zIxGZ_I44h4V+|_6s(#@802`PUr~@LgNo>e-8T7J1^)f9tr;Vu_0ZF#rYgY`W9hq*+ z6|2NO7IrtolnJGguvDe)zy&Rk7D^3xSo+>Pe5=fx{{>ixcCcEBXEDnYe!qSb1r*!P zqtLRSjTAMU5}?)^f}h;Jq$%uoZOQe8%1t#~mc&14veMEvSS`3-B1{Tk&A=dJGI1+r z{}#kAq+5TO#F;Ximfcr-xWs4U)bwXe-$rZM5zoKjD|sm% zY@Q!j4q&yU`qe9-@Ef>1mrq5u?$~25E9aee8g|9(=_f5*@du3)H1$cXVbD?fN43&aN+j>%WXS6D-n;BWdgj|TN{e!$AhUz1lJG(NOc?$##kLRbDJ_Un6Hu2E$ zK_u(G`Jn)kh%d@-5GQC;Rd8REDg{=Q1&T=%6N4CZh7AMZH9>f|Uxlym69BM^9Q4p~ zHfkIhC2sxoH$bLJ68{R$5#A~@dmR7a_avSbTtvt*0Y}1EQc4=Dn<=MJo-)-zb{>(^ z)fj<<+L6bwxko!QO6oc3S|vsSz{DkP0DldGH-Ij5LCe}6eHR-L)oBbl6*wGDR6<5Y zKj;)d&XiACIPsbbczJj42ly7bZNKG%FJd;&L313^CyrJ&Ks}&TWN*|)DOY@#qV34Q z5K;pq2!Jh`gfkb2Q*bJ<=~Xh_C7_t@EmhvGA}0QIaUWVl;-2~g_{kDxTVP#&LCamPzXE{U^eg*dQCwV81ky@i|FfF zK&y9NK9{8N0*M)E%e@a?Z-otTgSEA{uMMQtKq=H;#eLb@0)vWF?}4wmgk-zi1rgU` z(Ti_D{GkU>Dp>p+KcU$d%e+gG=e!((2|;3f?JDpx2?&fAHsbl{>z+ojH@^oZ0$LwY zjhEt5>FT?nlE$Y4AjQYAz{H>w^-X-#qY?e&?xjku@cqlX%o@vJGt5U=I>*dpsN5zIn~dg0I0+4bp*eGPuC#8<6IZhx>ARBCV*aJbUByiS9=+=;<=&Gbo(;eS zf5sC>XVR(IHQ%Z5PEZ?;lQ^h?K)6Rzd+)nI{U^XG8^ik;kp&ezT%89{IjZHK0FBKg z*d2R+D48>FKqVjRai$3AcD9NBb-2mOrkOS&2w`(zm<^QhB*~au{XJXoh`%M1igFK{!5;89llph2%sg)%bF{$xwvxye!vHy6CZliE8!Y4E(Gz`+e9 zntydkE0`u6vWPz=18X-6GwZQluy-Cx!z_6j0VO_~nr`3lfyFJ&hPhpgf||-e3?`2)h6g#$d4KR@7gZ_QqQn zXCRE@zwI_JF!oXhPn2y4&^h803{O!L+}A4xdr8%bU+oesx!<3S6-vUlAoRgdUJl$1 zJu8!lHVlXjbsg-b%*o?a)N;$auLTS6I8OeC!p5bZ`$X#I2N3)Wn$0>RA>SW9KV-hF zoG`O_-tjciERAAHRto18F8V8HJ_f~B3CCKI>Z4~Ri!yl{wRe|ba4Nnx=2!2ObIT0q z%TtHH;Y@++?h=r{XoqI83jF>P=Nv77G~wTRP&b1d!D9y%ZXg!SSczc9hw=-(Ud7l? zP%3L2r>rz_nV+Xuo3!2_zm*Iv>33>?b0;V&;{ZgVP5`a67RY{U1)ziTbnQMA+dqkD zasJt7{h(=rKLJ?vQHXL32dVD#bsYI5!)H_;?{`xVB>un;Kp7(Z!3yd~+J_UBPXYku}VFV->*`sR3X(7it!GtPztWj%(x?hrDA!ivbZi_W+=n4c${zz}qgNctxxLFE&Mugz zQ`pQk|3!*1Cle1SVPM}2a{ctXL^s;SFokkIyl9X@Uefw|R!@IxXW6+H|Kf1L8!E@A zC!ea_e`!37PaFVkmJ?DokAqDU71sqA^6jF@c$o%A1_C4BFd-x49W-8??9}Wbwf9b} z%yS7T=C-cmg};%NnR)q`XvCaIyzm3TUB7&<^T zf5=R%u40qOjZ0G}K_0x}*%pEh8CQZ7sFFv@2HB6UPilSds7Y^IYG$SXAx`!L4CRvC z6FEZNiPD)0J5x_;ZW)_tfkl|<+{l42!1K%L~5qx2|x%CLz7HGvQCLsw=6wtH8bT);TzVOA+6yeM%W9z zHJ|<17U%~s7tU4y7&QAeAdilY*4fvicI7Ft2x5AbC_f}b3x-%PpFgp}ahM?6<$X}E zl})|l#YhBcZLx&=H?o$*1yE^!_`&==XNk4?(+UheJnBss4-&H+AM01zzH$E%It64H z`Xl&l233dKl4&=88L2o-mj|o{SCpl{3?4b8mM(vR0Ru%zJis>O}3{+IudP;>=EAmc9_$r zKg~5C?5vo2Ir$u?vHix15`IOu=r2$Q1PDlSg8nBQKt+;ig}^9H;M4;Fn8Sw9ske;% zc$~Ye{ysn{2A8HRq>r4OpaA*Qi%NMSjUjJmIuOJUtAsl5eakT)>+t!VSCnjPa2o|B z5cK;`1`boz(Vjm6dqJz!dm8f0|8a)UpI*kSa^)9n;pw1}m?7?8ve5q( z*+N$MFeKXp0egJ;_Z$>xeuxmQE9fqvjN%8y#Xuf|dFrjC=K}1eSO&KhS9Y8^?#7+p z^KaNl0(radv*b>=zmAR$(Cm&_94}JX428H!)7#esAsoT%K^}+m-VE{yr>uWxX#Pxf zQtisUyo8;eF-8cf80o`Z@H5^AFU_$_we#FgEyYx)=3ibKpLoJ_97ML@Z>W`1Pg_gj zv_%%96CeA|KS#wNdf?t?8PP;c|LCRp8;WFm-acl8;;$=k0Mavk?SsKZxGw;4LmED1*nAmVbzpV8-IQx*Id4b4q|CceC+b##q zG?9W$B;f3K9Ic!{4un6{fXWwg!TE5=0CA47ezm<74?PGu8wbHoMyq7_^8dr$e}{AZ z{{O>x+bp|~$X=0Ew#;N?Ws~fX8ObUY>S>n0 z6b)hffa781w416Oh1F@_p?!66kqtXXxl5Fdjcs1s=1oO$E!_Zgrh{9yPlQY~M8bzc z%_7n8x!%Xq2NfT`*WzS58X$){%K*BVSLlH}qsi%GQBQSmN(nBGj>m00tu!>sa4>A- zoVM}%COe$zTCYK__f)3f&;VE}W>xOe4Q>oo)xCI`_6TaU`=pU#-oHnkR|8=Q8sRVz zZ(sC~s1}0*ic$PIKCSeu!iK86XmIy(^Q-spvwOsNEkCQ)o49XS9%T>P4di;dT_Q<3o`FrFNMWYnAw&m-iD4)l<2JcEJ$Ws@+tWCh>ViYXn z{p+Fi!8{VwsrbX7RtG{F6d?@3z)E|fu5P6Yh`^DK9`2Qm(gTaqZPCi5JlHFVIq3TU zAi>L(83NwM-T5NT+%a;_&tioS(=B&0DA#$Wvl=%UKdUI)ME)A+SUl+dJms4Q-3Klt zrpx*^y{0H~?Z3;CGc7)W$L~o|o{M@5r;&Aq=bmKWU6*1>q`gv4j-$Ir`P5=G-OO9| ztv&Z@+p}lSF!vA8?UrSGQF#l)>z@;666DUfxXhTHIM-Kw=9Cmmm_}4iZ)i^V<^5LW zZ9iiW63~Z2yoE)l4B!n5O5E zIfDZoI1J7)W)aarTWot}8S-ghPmt>hAoqp?>c}VH7C>onilWpL5ele&TEY?EN>sx8 zagt$IECKci3t~i~)?REDl~-GH!mr2xi$rN?H=(rTOE%DANzQmqpfdQn53iJB*uMJ!&;t zZ)w4t49w~p#Vfvh519F0Z$y4a;$5n~Lrw5w=?pnF$c z0k-5Q!#N@&m&uvoEdF^%ppHd^9kZ%)m-( zzXI@fP+{3m^ses~D*JlUz=UnouKk|B$gZyukP0v?*n*nzFX=hve6vdngfOE!!7qe< zV}jv(o3^)3$Hss+m_z?*{EV7(ymjcSfeM-oD-Uv)+xkvgI8RcEw^N061kp6?`VxHo} zigG`~W6G84KV$y*H*|kS_5^4+sTX|VDPts1r;ydDWJb%Vl06l?>;e>k-{yuY_HV^A zj@sm4qQq}-d+yBx{hDHe6%%9EkjBbIMhKzLyVTpN550Q4JkHjyx@8*MB98UI9W>3T zvc40}7l9X&J>~$MbmrprJrk;;B{%E+h0dIaXditeD;?#@bPyOj&s2!}#YhO!Ud^-Y zjRZv?YLIo|Du@P$k~(1g z$R>guyazj)a3Vj^2G~LxW&&xFQgPP9?IDqyr*ng7y6`yRe9BpP_8f<{k?q}RX#oL} z^EMHIKU2bSP(_^krxrVKl7X3-@F7rdcN)rVP82c`(uSDsmnlF=@O!G0OVR^av$Cp-Up67qntpt)Yt{$|kSX76rL05E>(jR7 zYLM??Riz)flXr0IvkuLbyI;YsHr92&@)>&c=1<*(YCH+_`J<0r z;_luLzV9%vJEb!vTi$$7kTmZqCPi@dv9t#tkC$FaSn)TZW}S-lf2-`WyV-tyabx?^ ztCMjKN8NKO(;X!DrmY@hcboUD-M84$pW)AUF(9}2phoL8m*6uW7CyA=`DPdm3DrkF z9eK5Sk>vlr6{FKyi5h>AEXS>tcWb+yETT0HqfAbRR)%~T&r0qAE+UD&v_p336fxve)C~SC!FmN+o)F@cD24w&sCMJaSfbf`#x7g$G6O!39Lm)TUE)&ODn(+ z9>*cNN1g~-@G`CcHvWJA9nfwdr%+BK#`}zM*hU)(Z zVrgSuw`}^mysmB0N|l&8j#NrWH(s4U%P9n!JBocWrJGKXp zu+)?`5ph{hlhv33lRiKg7`^N<69QrwK*_{tv?2tg1U*Jn@Cs~fdR)IZHH14Zqkn@w z9iaJ<>wK}_m{K32*9Awf0JynR;hQduR8aiu?odB_zV>=c#4oz@iRgnX-1H2&53xM{x|E!f7>vGe_ERLal>Nx*VRIFgoo+=hvWn#CYO- zJqB(>L)0iu_Q2c!ED^te6Q3hr!-RCOYZ8?mF;kO=DHLjXCDa{IegT7icmWV;_W`#= z#bEygT_v=SAJO5C!DkJ&zSOy0yA5Bnd0L<|e$4zWDTT;Bz@x_ByP?BY>pfl%Z_*xm z`bQXkfQm!&A~Nc=XNsF{=gws~R~C!uSaP!Mxx#6iJJw3kFT4wjWx_q9bs1VAjANdk{ix92qC7PV@R)55atl z?g)%^<`z?+v}hE{CG_yY&5)l2!NJ5D+B`H_twFh1475|L3$8bh6(IbDL(KLWYU~#{ zTp=*I!ok}ZdhFR9(|d?7{fWRkU<5n^TuKLBT0aC9rQp#^sQ(x9td&gUTwa2#Xsj4?5tg972mD{+oj3y@M$v6c5`Kdo zAff(lG5!;?JI1b{$3F#{=sAH0Y%7F9@gm;IO<#Xop!4(d?JJwEGAo24X|iRFS5HH_vVf};X6*pIA$qx z&1_G(N5P8nN(U_yRpx_z<7MZYuP9e*Zs$<9lza? z8Hb&te<~3fjg0qM^uyh?d=HJ)Sw(dYzcMACaGZFTL7^uqTYOZrL*R+_7cQizlAG(! zKWRw2Uh34^pRGnF>T}s3@O@0_=H%c%mD35|M5e8jG*cZ$ZL4n9ALwdcrE^#j*%zAg z%PGBUrG;HKJ}ak99}nZd;O!$%y4*+64-&x$$3>8o>DhC;8S83f+605U`>U&*@!+b$ zXq1lZ9;*ATA1n_z2E#k(K3N1wfD%U%;4yd~JFkeXCIRbbL?8{xnr)R&|C|N%V zA*@~qt@2*(eDwJ;AZZ^8ark>Lqow{ej`(bko*vRD=5z8l8JmmNRcittyNx_z_xqOi`3iE zU8k!n0~T_At?ISi6F!N482##`jbB_W8)Lw^sYT-jZ zoF}fy9_)>s{~0-gy}5kJW-sTKP1)*lT(ojMxAtmZ%lFf`nsdpB@x<1x z>H)@A7)fO^ZfFP}fh4K{3OT;u2G2lc{o#es^CF zhN+6Kn!6(t8yZ8Mu9?N*lS;qlOJV$PudkngmDIgAKUVyLS#clUvbx$#Gm)X@KTk^G z=o7W6U4baBxw(18w8$7oJ?(eU94CtV?5QY2zqLyPaXWqb^iS|bpis);Rx6W%W5Jk- zo?ZfJj|~kCK={B~DJUpd`!RV$O3NYSws*jUeEaqTH%>fqjGU3xwqNdQfhYOv#wl@; zlyGR<0P4Q&?PVAtr=l7HkBEgUrZeFxSY_>b@+6VbDrM*45qmLrLzAbgt6)aZ3!eG0 zrzak~$-N9(D=0WPcQZ37EbIk3?w%}_&D6BpBP&i2v@LV=5%%!`z)3(E`^LsNJR<$* z2Q@kw=;_5|fwZI^IMt7%uHl>src%|K^XJbyg~U)5e1McZxSCx}b?`;%xRIWty~+o4ZZwY~ooTHa0g?^x6-a^e%F3W@2JG zfLaKg!rrC7zu({Ae|C0uhJp`egbH|W>6I&*j*fuQg$)TG#l-!znVA_ZXL!Fi`aS~c zZ5+X;ps3j1-hRx;s7soPipsrv)^8>ufUU@&*ZvPi%*Wp2s%;{NjbZihqdyAu)JK%X z6M!i$Y}_m%DH%Iu_yK2jig_D%;{N>V_mfwpT1(GY$Qa4KRAXlZGo0KKScYV0jK#$QHa zK}{YjaUg4f9BeeefsyWi-r|1V>&^7|VPK%+si~<64hmw;EUOm{??-_-{M9Rn-2OZn z*%fRoVa6yldN4#&FDPvK0_Az%Ju7Kp;Zu0T1I*0Km58dqyM8-3$ThDyj5qtD6xG z4{D7q_s?NKy=6}GZrO>HwlRP@G-$fNp%o$PO-V?2gu(z2JIic&c{w#Tl~4vqI7KBV z?{X`~HgTSZxDrt9OA`DfL>xF2EB4m#kI_(30TZ{z!2!U-V8hW#b#p7LXya(tIiVy< z-%I_5R!GKDg6E$=su>yI-L`cI2~qe!*4JNX_@b<0e0)5Gt1N;J($cmxS%&MEX#h3u zkv;7wNU_J~3k_vF$Du!9+Ukd++~a1(xjVPM<9hzSp{eN)xLc?#R9TkTj@Ji1Z@e&0 zBnk8eV2xh5u)+Euq-#y#SvoG8-i*UfATAk^vBJ&8#pj13ne->G;ZcpPo!v#|p@3hT zRp-zLqFZL%9m}#Yyq9zz0Zw0G*Zue%z0jM2f`Z`SU|`~Rani7`u#jF%x20acaibg@ zH+VKe7lVdFLRwmLYQmYr*xt+Q%f!SbW~H%bWs;=6J#1_cYWtvJeD&%TwvI1kB=!sM z@Yv@3f==mPkU-VA35kA*4) zzfG>i;hoHzyc7JD)SpM|2$ea?7Q)9)IQojE&U{06*m7F5wzl>mommGI17Vjg4OV!? zD`i~=;M2q`z8QKb6`IM3i3Toh$a)z9rF6Z3x*^`XK=NJZJ~}!EIUKs`j(j$;JVxvm z78W?InwBsC@xDfZ)>=O~u=xOQb=olM2BriXdLPy&v&cyZ8SywV?)Fei6 z@7@jfY;&O3jxA!KaYNTp3e{HA8`;R0xj1&j`BSzp-GLGag&>E1!G)Q%@nTc+t_RPB zid@q!;dj_seqDuE6t(`Swsw?@@PPx5@qSU+UY0>~ceqxpRcgOO$SR{hgd5v^NpVRT z0(9+&P~2Ghac-^#6Al(s^mBU`r$f7D1|SYJdS{&f*U(|Is?XqZA~AoO#4_V#6C z7>&Ro*j8ew#t0|T(9qDx$exjRpJdh5b!TV+4|R_fk)1qw(#-4)W&~6M%(FB!Gz+hp zay%P)GptPRjHG%_-Ba}&z(Yj}_LU+mnK3K<@G`Cs@H`9rO$ z1-+|3fByXSE7!GzTnBFfGvx+;QN!JMdjGb~IERiaBe&q%QU z;Q3+y#FL{#}j!{1qK3ya*IuvJ#Y>U zl&b2bS)RlxYxHl>Y^|=Wyh{U8y7oMbY-SpN=_WgS_6NGS@z;~y)YFLVw7}RHb^Pi1 zJx6cdx;07PSTZ2G7`E2T%N69A(#456>rPCATmyOuMZ|;G&ti#jn(m5U+q>h9ixh|s z+J&b{Nqx{!sLN$x%y+(Ab8tiV0j^J2kI_yEM};-LML!z_4vT8#^*{;8CgZ)Amv;&u zlk^ql_{3Ycq6d)#v{mZ1X>qrQ-?r>Om@e?Vs75L;9-nzA@J*MWc8{3k`^E-elg4$y zeaS?iKSunk&5q2fIAO*3;o+0_{h%vrB->;`|1F*dZEnQPgG#NJcWR{av+-?1NZ^Zr zFq95>0<0Crd6#c2SlhnV)6@r;-fjGA;w3a1yGxwwKX|9eKamx9jWNI8h${Hs?9oF29+@Xb7u zlE-)i8-8=d~(ZW$MkY>PaTxNvXuS($9F z(>a}l>(_hHl)|rp%;<4+)=ElBVEl}Pk`Oy&T--OIl?i4%TJssO_95t%%gUs>rJmfx zBR!eiAfedlx7@WoL}PBo4Uk2Q{_bW+i6rd^uLVvB(9s<>N<9HpNE9N!$n!YOD+nF5 z)`8@Vx#u%t>v3^$K0ZD!mUngcNeHGC!bzWNC^i21v!|EW`uljnS-)zKL+@(gW)weo z&>vO_?1fW3w<7~0Oy72HRXRJ@?}08uAWU*@em*TBA*b5~$)~4kg&Lyt@G3wF$?jbBy_sTmmySGv+G6?&$9d&v#BOqR zjeI~EVGjc?P8hX@xDCO;3NV{@HaGj6KK*gHN^;^W?ogn*@@ndI$j-BF4ULVBFJDH7 z$CYsUWRl-h6EE3NIy)$uZx9(9dlVicCkYF4^8r98k1a*y-eRb}Ca&`1>)ZDB?L#F$ z@X89cZ(sMG2b|D#-{<$ulbU`n`udnMvGRgprLeNHiqYAZUkYrcEybuvS6iFDl?iUA z9b3D^*+?wjXV31x=|w+FEvaToWm2>~D*u_m<`3%-maC3uW$i7)j25w2xbs*EVm(U4 zj-spgmdj{S$U20VoCbrwo}ZuJ5{7JOAG^8otLLXTaj>G_zOBU&q6BaVS`Wj!K=e@2 zq(9VmjSp3L@$>M!3#&A?EOWti8_XrQr7|n9P&)#y?7(?wI2B?36F^NZj%h3|+HfNG z3KN(zqsZv9b7)vW#^1ercX)UhYkvoalC=VB*gLi{b~!(Cm0#$v>B3 zJNb7&Q^rWUoNQ;bH`!J89V8r~M@KKRW5*7cynz}KgFx(y{s$*AQ&IxHjH1MiA)VCB z%*-cv$8ta1Ffh4b>AeM(1DoB&KfhL2@vd?Y5jiPP%7U?7sBHbAAbO2TP3=U;0zo5H zivxS$;K&>p7=VcvfBH{s4uD3xDw=IKg@Um}X&Sepo<luN?7;Z|CQ4 z+p>kQpEFAkTgme7XLrgZ+if5#=~-B+z`vzEL9dmyjXD&oV#W&m5MpjFQ0!ltSY3Qx z^@0|EV)E6i7gTWdi(LZV|J=lcQIjuc#^eiSoAawvz&!14G)$hst7|yy<1Ef8Gvc!K z`h`1|v!) z$CPk1`Nr>EHgFi|V>ZL|gU`5(Zh#G|49{vaM-eE*`A6N zGK|xqx2Fdk0N75_u?tk&zA2!a)6Agi-ctRh#6c6~|NVPMW75Jzz1Zm4u=|LTLGlIl zH`pEn(H*qW-+V#suQJC@M9GufxH?v1M(Bsm03N9PMf-zyQKzYA%0RG#QpAUS3|Julg;1 z++f>g##3XqwKyESfV<34?4Y2cstH|xa{W5-OntHjY}^A&Y-y_vK^wHS=~5rdHjt5# z$$AFArnJYP)Y?!%pBs*A=taL{gpP@cL6(^PjnVVJJeK(3;_2?D%SEcreV46aZaudt zqOv;$oLyZl0Phkh@GpSysRF#9HEB9|8uS2s@&aB-N8pzCOmW zpg~DWHedbq%VFONip8Y|XuOoQ{BoCk?R0NcRaRQp1u>o6*}h#$Ovx*v4)6g^;-=cj zGS)(4p|)mJ1sP~*+(Y{HdrbD}?&%2TosButY?5#yLEYFcbU)61pPi&XQ(vcd{mBq^ ztqPx}4po`(nh0G!5w5;;gp@`Xko@a_n!ed(6({mET3s#~{i8=?$GtamR8A+OJ_QO6=?Gr<(6C2s1_=qH7B)A0?b3=tdSJk#3dV6a2z^wqjs~N$ zRi$x*hCu&roE$07LYJS8aJ`A_HPiYT7JX-CXpTfe5e7c^_pb#Ke10+4xp&GoJe!icYV*2PYdmypQ%zgjC2xB9U0bV|p z+ONd}o0@)vM(W+@i7IUIQ_rJmkSXC!f6$|8*+RX`>e-T|NLk%&Urvbwtg(DzV%~t9 z0RdvZ2dCt#B|XBtU)85*ZTFu(XCRhBoX7yfOl6$3H~jo?lD7KJ$)Y#bH^kVTDC^kj z=JC6K_*6t6xBs5@=tpUyX_TqSPi1!5k|}0G!L8aw_Xd19{{mpR;3XB10xIsp-~;Fi z1(UQFK7T7XP50;4)XX;@z1NgaPNa}B@YRWdob;#8QNqI1v>z*ts9?UnzUg{I_8(4> z(^@qJQ_gA2?MKfZn)&?GO;WyN8=a!YzuANCcQKkSP0g3sT2xfg=m^_x9?&T=USv-z zI9O*?s&iJ7>_P2FanZ=y`Ooc&2hte0f`tC{lUzT4^vr$7c)Y&a8T#naqxt!Hj0sKr zcQWMiRPTtqI^@l{+Ih>BVNkyPrM&%e%24iSEG4bHHYVpwT-@m`e_-1l)LGSE} z+oh9|tMP)yhTG~MRG8@I>n31+4y zE-Sp^$6C1<&Rmxs{k|@-^z5;*9tB;}x-@0Mg})DFX7!p*TVju|-E3X*7;CMNg3RSP zeFnoovA-_}VB@ghZD*tA2hHX z6JttITWi&5?4!hp)6`URw;iWMhFN~MM!cs9s*DS0R^gkfms2<9`}bf?ORwS8i_p; zi<*loM_bcF^zAvt;D*arLdP;*gzqrqHGIO9yTI-K=SQzeyuk90J<18!pSvFS%zyeP z;l)`sMUMj#da`s$m;U?j`N!JYEP3xuxo7ORQ@v71FCcdP2(OWApy}Seshz|6{Yiqm z5>H%Mw`OBJ|Jlu#C3G=zhVH*f!SD3aRu4C!O=G@O#?vmGZ;B{Zh0(H6ScAjnnGC?|(ROHS|HWuKIdNbx6qUNZa}I zhkiI8kV>QB65{^1XoZS4JsFEC>{%1L@oT!IY_l&_+tTXxIuWJMpOh-A_DrYZP5i?r z)YUu9&RF4iGBC~{&St@W1Z&Bj6K)V(+|yze?T5=BCX-- z|GsJKnQ!fj?Q*LYou7`~JGV!tgN#m*;=jE^$|$MbRGPLnm~k^csH$!uAuLfRgQ1(j z;V=IH2Esy6K&!|Evv>#?JU#D zFjB^UKpBaYk#za$&e5r3x1W zAC;9E3orsf)1b0dDxrHhQL4*#y>+V5RFweO@MEIqq%|Xu)B>QIxJ%Kf2;=b+T+atF zVn<8M&w`Gc!?_eYb|9GhJM`M9tc#0_JoJ%ub#)aLKVSnxbm}1t6lrN`Yil7%+TXi1 zIdD^`7S9D;y9cGEzQ8`=f4pxXp#Hgu+-fJHMlnFA-nun3ICvg-1i%DzYPB=Csh0S; zQ{9r#B`z(!4$**qx?Enji=W(a8=Fy(`+({WEvj%nwyXnZgUgP@Fw!47#4*+~wTOue z_8*ua)1a!rjXL1eIFPb{NyAvE4x<)GS#e1`05?1!V3qQBi{svXkhzfgiS0zh%iYa<#J) z4_WaBq}*qYhf=RAJ=*!(1!6CjQ7+&|l+ z8Hk06pWme>QQ>h$MVR;AgIQywYy@ZnjW-hq2L#Z60O>MX+1%x&HY$=jbm-8VESC(` zeQaS5oG|trC+C}?bIrN0}h6fKG#0q#` zcvNzqOEHwA=oXI^8EbseQ`K^B;B2x3Hd%uC5h@?Fs?TkAOS#xOn|rPTk)@`jRQ14? z!e)|#_vUal-W5pC#hm}X|DBIQO4+%_LSXqIzTSWB2!N`&$w|;#1yI>EH8rtA{R#tB zKE)nJJ#Fou@a1CF2=~+9Xm$jx30hS`ySl1s_IuDZyXPhFP5#D2f$u^^Nh!s8^xuVe zBe=e)WVEHp=UX|eWtOl>q@T^;yEdluG`iU8_AgdeF zMv+4iV80tHnzqn3J%Zin^_Thi7{SIj074PJ0E%c!siTKSd(yuS5{H76Ys3#_(PRLL zGJ=9yx!p_iZzcKp3ji4ag+~(ryrb>aE5PunES@(aTw~*D4PPQd!l?xZUKjA*Gm46e zhO!@16#`EKlMgsA1R{{Y?cO|iGwTP^Q}rcQTvA#Zlw2R6S<2pEsmX8H2|PeiK>OM4oR|?Ab6Yz*ahX;;mdH6+F##R`Wyw($78YK5*>?4=;0JfD zJP8J%2}x@>5A9PO+b*Z%f%3o#n2?m4{PfAg%PT%((yHuI+_-?vkALU6x{2l1v25<- zR#4`+m(l<~;O{^tJ32bTTvP+v5;yTN`aviR01O*Ohlf5@7j~Q+JWs)-7T7mtky}Vy zI$~thLnH_xIu0k|;NT$eQ81IPVB#$A^*?`ZhsOsj|Dflool^qP)1(-eIc)91qW}o3IW=iJFHIbTIC`c!f87Ms5VJT8u6B@Z{&upJ!(+ zKs_(f)?qx;h}Q_bcX8#QtZX2#l^Gr-XajILEAv)kAtoW5jY`#5WS^V))c`>eh;&5- z@xokRB_;9iTs2HGeNM3_&nOVY-ouAtsk;5RT>#4@CWiX4ylZ&47LOOZ;&DX<0M#Nq9T<7BRKj9&IzTk#J4Z z&_uV#g9Isg`*uKJAYfkVq3*)Nxv5B53<~;5Nc%qxNkg*11_~+n&D_1acSAx#Kmi*t zRd*AvgLL`n4Mn z#KobmZ?o^ag%xYJZ>BagGt)f(Wt+EGoL+(2<9vOG@d2R58O8_LMhUnRrw+Di)Stq>j}Rq#0fSOfvbpOyIX8F8&hFZ^YvSM;@#x+F z-)?=@*tm5>IT~j+j6H;65W*-#$q2Fg&=>PTEp%h4Kb< zo-*f5&EjbwM3Fzf zz4trnn(gaCmePxcwyNF%s;c#n{h$;)3nxHj{22C`T%*_%p$a+TPFmV5qar`K5l}ge zLp3<98-g49+|EFi0h24DF`~S_j6IFd#jD4W<5X=V;U8IW9V52Ay}hkfR zVjacvf^4t~0ZL`|Y`NB=p}1--rg)zN73<>ivS)Wr1c)2+Uo5|08bXM`8v6nIR$K}o zUQUYC7!#vF+r}>GOs$l>WPj#g8^Cqvy^usU+N<+8AMuafq*xdHy?g8M=qIL*K{JEr zcvUqUl$L$Xt6z*({4FF$1HreHRU3uCj#NE;`gFEVh<-DGb0D9L%DiwQxeS$mLeR}l zR@NX8t$;XvL2k^%be1#3y%cnu5~l@S#NL01{L4p3Nl4ivz?pv<@c zhMgZf+uIM^kL2D-@evFNb}Awlx?bQJu+WI}`K_(3#hjYUsy*Ty$tQLN1Si&hjd`TS zzSMnu+V6w9X#GbjeKwy{Q{x>`{-14>WF5ilhxdu27oy;=Z=XLON9t=?MTNAp9d?(e z=V$m^z^madR9ZH+VVn%`4?%r_!eLW&IuG*T{J`x5SLH)0EtPbOj*q9Iq}0~Zf_A7W z!rh>$%{y~POV+)V6lNWrR-%jUd>2}vCc3M6Z{5WK1ozgUCQJ^swV|HA0`!Skyh#As z@I1jms#=4C0MDqGg@=zX<51{%n2HF24K$EMB8l8luO1bz$CjU9kc7Zu8vVnK6>-V; z)(xj#b)Q_gcf>Z8zUo#&#tkC@Mx>4N2a!6t563VcbWfRm)M8)qo=~Jp61mgJ5qfa` zf7`%%8>{=qLd2{1_Tgvv0qk}g~L%I733EVe)P6Hr`0f745VDY?)z)m zLsCtGJZ$mnS7s=nk8GgGmu46*H5MWSzG$G)aYX;g5~5b|xbXAXv14G5G<~icf?;e? z$}F@f=-s0bK$~7sR)$LAox6_ncnmU;j~_lYbwtZXU^O=z3wuer)MsX=!O|`wn6( z2+LSFIH2zcj!;E^6pJ>0yAuS30iY$Zf-zX3*PANsRDuazA78}n&HY#jtmI$l0^#6` zmIKey+uK{>1i3KwvMcz>@*{aY&;92S}{Y8#XE(%{MAS`pJ#`C*nR}ryM|VxKC?> zk5YFa85x<>S#Xfl{jIHY;8>)NM*->f*4kAkcvtn^$^Mn|(p*2^&{M4^FNJ^i<@2n+ z$CJ*P6LsmKMv<{;LIu1ECMsgdElw+8&&>+3V?%kWDUHa(J*IRT?i@GOp(|x28%wz=Q--pz5wEzEj zyQap*Y%$6*gfu8A{Xsil=m-fmn%A@b{)hLmLT8bY5$AI>RU)q|tE((MeFTy{$l;z~ z%)Wj5HsQ7(t+QGi+G`}nOy%|m_wI;@h&1+^*vIT;?-1HqT?VyyLxXzA3~F=s*6dga z2fyRU%1T1>vmLV7u1VYfR;@9waoZj_`^U*pn+?5vOI*u_h$d10S{j0-kkwojjagz2!RM&90V%}BEDt$MaWmLm|1sBtSmLKteK9BoBqj45CH2GXhsRU$eIBO`jbS&Ty;KJbn9n6!)) z6|&sBotKw=W}#ki$slHHx>S29^GlN%4ciEN+iN%ITbB~0@&=ODPZ(|fLwz-s^Z7|H zuM=7~OzPtm&L?WGK2}y=-?p-ga=pPUA+(@j{k7dY^`-sm4qA6xEx?<6-NLy!bU)qi zRugkoO-J9hKfnA7UW)L2cB7l62oG)S-)pp+YkRnafJIq~E6e1dVqPNM{v4s7DGBED zhu20dj1+lecaTMi-;mll$&>ge;B~La;q~&ThCqySv?BoF+;I=xM`(DHjz-j#*Dfh^ za7RZ5>#_&lofU4*Jj2r47|(Hvp1n$9fuX}>HR@kSMrWAU$nQy-(Mo@MYxVg9L&3M! z@&6F28ChV~qzq$Az0sUchWkaR->#Y2D|xN|D_23w#INP&y{_G6{ODH6TW-@UXOHjn zSJ;>C`wwng(j`8Meq@7FHxU=Q0Tbe`qRRS+iG3^)pB%m8vWl7kyF4PKu4#z z`O(u|{Z*`~2UZSM1fTbv8y?XG2|@gH_i{aWzS@-lEU-O)6Fk$>a>a&+`|<`7i#Z9h_#rF(qs&eHl7i zscxU&mt1WS_x=n2!$;p&TWK0T*QO|x*37Le>#b~m=K2rT5UPmMnkjsrkZUI!9e3M} z>QCIZ$in(&M=hhG+wT?_XS~BzTQqt!DOG& ze_x^{+n!$T?axcsMD|(s8NAHxvRDN^G!kaq}ScM zQl@s5EkC|F^s>%s^C>;~zYou_%0yR{|LL@{Nggw&A{KX?0cgHzu?HUUpd8F=h46DA6%puGu#|o_hN$cG~O&SwZ~d6c7tM zXxnM3w$tBky?&JT=1tuR2P<$0|K4*WCJaTB%TuS*(i0`CtMpu^UGgS^@BV#)qzom{ z3^RhO!BuGrzDe%JX33z70_BBdc>n$8PI6+INv+jc$65Sn^7s8!U1!{y@J;3KKmBhV z3+;dUtoY{7-iv)#DjrR{|F?$8h&6oOZ9ZF8vf@cJQ)uRYf1jWF8Mc(H`B&-x7NRfj z|KS~oH}(G(dhQs)QVm!$kaA!1JzMiE|^SMSf}7z;@X2vRpK z_fQ#sABVQW+`>Y!LidVMQCnwc@@|FBYZKHt8d9-4*0>)yT4#c_8ysZip6Wb+Z{K|= zfmq{?5Ht!)9N?WgwYWV-+&5{*3WS_ZMg|6Z%l9rqRzRSY*fWqpKqGRmcQ(V7BT;(_ zwGDSfg?)eq-Rb1XJTH%&p(ZU6Ei!o^mI_l zrAkS}YBt^&`rU_;MB0+1{g}Wkw!v7nQtomQ9d-03%nibpS=s^u0?M7B*-eWN9L)ma zjBXb8Pvr9~qFeg#;X~(gCP$GJa6!yO$eP0?#>AaV89zgANErb{0uBt9#~TE z)eY(RL8f?$%K?6gdLXFi+7#xFX%QcWiJ(by*8iv`YD2v3I}FKZ-i(9h06hoCeoA!> z4U=vrbAbHBEjxfQq{l*FN}(TPJqzA?%evcR%OYdeFDC;J8yvEP5Td!YwX~#UVR`xd z$BOAhsUGz6__*>iGH5&fp7r$f1ia}x6lpE`@+vUV|9KkXpMl6U%C2Vo2( z~A2K?n&s_A;u^YaQ$EsXB^Iywx!ynF=%fyx_uTVj2U{5o_v zTr-{%cMD<4Sz203OJnQZ$#GWQ#n4!^+N^2VpxZRcM$Bn8a<*CG#y-WQV^5;55{IYI zZi1oBV7wH#WfWsQkRoXIFnA%C#@ok7B_!kebq7moL#e0{0PB*HrD%!quDV^MFe&C4 z$89220Gq{K+R!4!lSX=bu~=@pJ(iQltvS=AZ-yHhA9lkYl=524vqDUHDI+BXVx4ZT z<(vTw50J)`S&A$V4h`KZFE%PNSSQ4{g)r`5H1oF2zU9)OrdU!^!Yt#~^z2+n2*hVc z-`rJED=I1~q~t)plYs{=W@)-aSyx-@ofr_%-rsNfKI5TAK`+x$t*iZJydBuCs zb+heeVM#^zd=pIH@#DvR?!J1HC}E_dqf=+Y0nEy!sYROWwLc(JOcImI)OXNY%FN~! z98nie833LVZA!2(^YeG51%mZ;_tP(tyhPGgu^53RmpJKZGh3VSfb)2~K48d1uEcqje3LwxKKfhr({{W8=`R8mhP?UzG07CMI7@a_;U=f@d z0K$gpzNp(JCXkKV!tW78aWIW=SMPzS68i?2JPb6B&dyP?7}wZkya@{_TK3%BTpf;C z3_DRB1Cw#DnwwoQfnxS0f?DsWay)bb4v&HKaC3s`eHCF>S665}$8hcK&&XpjrkFbr z7kp9A{zjLTZh%D)mni+x^s8&!0Is)FQo`EMWCK_Qml84^r>bJx#YxmCqHkTI;Tfe- z#IxHn3KW&NO*5R0fgwzp(7pnceP-r!ilY8>css_h!zrHtT!@egq4M`fnj4~5VB^V} z5uu+DWqSP;(Il4U=6hdjre%VHy?K)W1YkB=LwGV!@Ld)(ewz<4293U~Jg#tuyI0PS`DH+<#K~@UzYMjYw3NXk@;Hi8iuSV#=3E)t-HoE@dRJN= zTi%-yX^Jy7c=q;L09h|mz{F%T0bOxxTKYAypH)v`2x+_z_&v(=-B%23pf4t*>W~T9 zIQs39DUkXC-BjW_wdi45OO<==i)(InU&{E;!$KRnNCgi<9hi=@symuXtETpVw>11I$N+hoSqjlgO z%>ZN$z78ybDDLwCAs;DFhsA~sLC+g&kd&7<1^gUm!OtIG;w2z7ak_WL8U#H)o{XJeiaI%z|1t* zRD?c*m%jLrgIi;|@w5VSH=Y*MyEy+2VDXM2YARVR6p2vx+d`X#bv=HeMBD9 zN5LicL>bBg>RwVE?m=j5d^bCr3c4Dg1~7lZtr9Hr#c7^`^4;PuFm>Xh);uZKL8`k* zv8Fu%r2#myH9(O>GgV3ccgP@$_nq4@UgQbdc_8#PsbdJTqzRq?(pNe|Lf)+yw5049 zcVxHW53CmubQf&d4W;Tqfx3tP1Wl>wq6keu!$;CIkY_8Y8;84a_wzK`638M;0`3(T z3v1g{`%#PsLE3s#*!6LuYD14Caewi{hss`!Jyoc}5JuQr?3{cAtWp++G#H9^2)$g! zhX$OZny*df?ve3bU z%Y=>54A)PiN}RZH?Izy`?3;-B<@4^{UYp}Q zN!oNekTh6WT9zZ}NkRg-g(U!5U~KBixe%aXn7y;{m@24pkQnw0w(^@7_R`;>e_*)Z zK~J;Rs%0Q*#kApTe*83=MCM&H8U#D+B1;^AtN9tC`!s8C@AQyrkie?;%8DrkE#o69et{N+89y zu&9T|&^%Hc*EmA-ti;q|;s)wuLJjcIEU!jaGE< zI_Jc^L(2IP4r=QSFilpvi;aR~beQM*?y%2##!~3Vzb~J}KxqBeHSa!*&XPt(ychRnWUzwis#^idrVO4K)pd55zU`Ln0!#DS@oiK zbJ31Fcx5ZIYI2fuU~A)j1C4_bBGMMYfY?S!EcWFA(Ui7LPC#Xtztdzi4v5 z+Xd7RaWg@iBjzN+mfBWcncL9zb*T%DOyh(KxUH1HbiG|T{CDlTBD?PggIcGbdVYbG zvAMQ=@x0SP|gp!_VD!PT{00e0Y{7Z zt1gsSS5!O+BWO;PJ*qg9~8k|JEMv!6;Fnsix z;bSg(cL{w+v^KO}+0(VO7rlncMG!R(PX~Z%oUjaAJ;$CX;+cV!wAZw|=|}s(U&ysq z|8$r>zNR=0CLP2l;unbL8;TLP)!4esT&}1%E9wIe zDAf%oq>?!YA&4$K>Ty9d>0C~iACC9NU#*099EzzW%_3v_R@6Fd4!)oB)6+jHf3%X= z8ej^C01pJ&MGCa#PXhvFJ2pMZc|ioMYH6u7gLA8_sfm6Wft3WL{dkXvNIctY>2AYp zQ*YjPl+UNJXmN7v`d(}-M7>>c_-W`Np_k1<4*K5CP~^a0b#V|1lWLsXRGVYym=0@d z7Ubu%`pV_ZE*=jj&J<(Wg&o8o9Fel~`B!+ji1O9vMmUs+&Kj*!iIn{$I$WD4Ar`|K zVG=7*BumlcmoG7VA7VHBQD~TZ6Iu}&7L^rnk4^3z>T=K z@&w8rh~+=aLtcnX3H(RjpLAYSj=fe;z(4T`pg-r}-&X&mqvJaPcX2+xrJy>@Tf~kq zGNK*8{p2Sh#3G+3hGS&pFGMY<_7US5i>W+Tza&@4Yi#Stj%3AeCQr}*hGhO8E;Yeu z4?vr5gkTBHGgy47ahPvQAsss?BPzOM1UGfW zU%$@ay!*hHp^69SqHyvuJD?wEitaTO=rn1%rc^`6=h z%-fm~@qYjv*<`Pqz`h;(Z|)Nti3n_ZZ5Q>hdw5uZhi40efQ?+-gPHeK`)j)(Z^9V7 z9Zp@St3YJ~7IDV;RWgS)h*TNFNn4InxAmRW^I~47n5SX|T3Zj}XhiqZ#RC82=vstb zRIw*TdQB4_j142g@ar^MxT!(IDR=e`}_)ZXzU$K*5XCz2y z%-tK;aCy`c-Xqv!I?c!$FLmjzme4=nU|(b|gW(31<2L+-nBI=pz8^}lSGFBPJBQH> zdr%G(=umg)?%lgfn6E-5qMH4zqG{2dcSLol+v;@{;!OyF56Xa?94q%D3_<|4FxJCk zXdALQG2s7V@4er-?)(4ohL9*^g~$xqk&%*_SvDETUYQX|sYFId5u#*^kPw-Xkx@~0 z8BszZQY7PZd+NHb@9{m3&mZvp;oEUs$NN0r=XvsaJ)e(pzu(q9_|USAgToDft%G}2 zd^a;|XltvO1$%KOQ2F}$-ehDaLTgJ1S`r7SRaK5HjFUMIfa;bW8|5_?MN7Wh&#ueQ zCHZ;@b^QY7+d~U%Ys(!HlgUsTU;+)z5=X)$Ic~*Vd*fm~+iB&)A73Wce;Rc)Zz|@EQPbfNIL_Wq`PR9=uP^k2Eb~3%c|;=4eZ(#vUhrV@37+*rJ=0 z^r5#$DQF^W8861f&T`srxV+Yi8u0N94+VS<56x+iA8epvJX!XnTR z7sm+Bq;qW4rR^Vg1r;n4!3`VK&mMoNCkSD5RmCfyBTe;xE_lVA<82I5lWW|#H~Y6T zoY+jG%YlCdP5}BCAKCj5+o8-%{KWG`AWoyOmu1*XXR~Jek-tqr6opOK9F96QlO;OeKU+P;(ZQlr%70A4d31 z%7H1NRPjVXC`(ZVfKPDk%IQRx+)>slbB7{$6qah@H;0Gc!@zk!95=&QVtDoQx-~NA z{|u+;y<<(=KT!;^2Ts2H{Co&T{s_9g<+uCKr`Yfots^)rD>D<<<(IEt|1K{>l(^mV z=urZ9pdy{vpqsr;V zP+UM9`6xTv0Y!)ByOf_L8=TycCd+5ZTR1s47gd9)fsj#38`MogQj&wc{oOD3k4qa> zA&(EbL7lM4QNcdpq5HNKZ`}H&n3F`Mjy1#jOh*w6rsUJR!}?fmus6y?S?`jVX%v1h z!{6old^4|pz7Yz*&XWd)-;an`o98H0X+Q2ONOQMV<}*Jlru(A(32#(+d~TntW{Og* zw~x;yl)pb@goN@t)B`_1KI*kaGm+uFb<>-O;KZVM&FPH~O}r$Ytb>S?Lj474KeghR z8OaKY2$(;~pWuON${3}0KI}v64ALiPE}}a9@baZns!piG1sy}ECQf9(WUx7s_8d~) z>x%#~*Q2ZdMn&g3e%O?J_HEYUkecF-)CTS?XG5vtJ}A4Cnkms;O@uNK_raL zC`yEH`jPLb%X=qGIcP?@Ue(fr#3nRqB-Bq###%K8UKGd1hn8t;&|5uzEC}-hf=jH9 zQQmDrenZjE6HrZ&HlvHauV}dRc*o<43e(4ViowwF<MW41^}q%zns?>&|*DJHm2QBjF}9-(05TglM6A^@yY!t%Kav0I78zkBQM zNt76uFcfAR+(T$Uo4YN|nwumBO`J7Ii?2pSF6(0fE|U7`R)QKy@_HY2e7 z$;!>$)wTNE*AuZ_!Cyapx;hp~s_)L&u>e>LsG!ei!}d9AkvaQ0{5^)gw}GqSeZ`FdzC*smo5XXW$H%#8cuJ91&JdA%!dK2Q@N#DyY{I{ zb8*=~kxWI^2Jqk0ms&^>5uoZQeI1$Qg%Fs4c!0;%)kl0k5Ro&SmjY@@X*$^-QH9;D zE8~5ZTbFSOc_Imk_%M|@Ij?$p81z@63nW6_iWgDd<#jvLRgOg+7GNTe0wEM8h#fYvi`|5x5{C$$bp=_u&Jy}k5J?xO9%WO%fNhS(%G!Dq z1Bq#V!d2FA-l_Aj4o+*!dK^!L0UcT;k=9tl5J=Lg4;Y9#E^T>#7+c*#W;CyLOR=(V&0H#z7z^I zkeA}XB!)GE?$bHQ+{i=lS2$Sk)ov!Wkri%S*r-rAcLY_1z}Ez{$KPMpesHO$wH3Xz zQNo#sz2~TUGD3+g*2k~FgM;!%${t8X4NXl?Q6yag_wc%m`&oy@EK|iII^XUHT_8 zGcwQ=&Y`w>7a2DxOYDT|8<~2O`1W4s&dt3P#rlKPH^NX2J_wvBiacQFUwk2d*tt2j z?F*i>56B@_Dvm0mjf*-w!zV1PDuoEVAQ!~{d_&2T-X{H@2dy^31v%3CuAk2Bcf~ju zcV7(1fA?;0=l(k_z_mnenjN%eiH{C-#v?-m0|}%9VA}NYiHSXJBWr}vp zVjX#YA`FGowE=&`qsBf=OgL)&!aeX6NfF8CJA<<|=O*g!Xk^-xa-Fvqo2fJxHs+&4 zfwn!oFNJ+ky-UKyH*SDn2fbLk+S=bK&31bsh-V z-qMU>07PdDC`R0?4<6uc8^U#qt&*0(_b}zGxJ7oDm_$t@({2fXmSO=w8CTK03je;` z!~`$K=%L_{kf}rVha5JsFf%0A^FT#8p@xJGoZ-OxY-wS}WstDZ<{ZK-mbRQB;HMQaOODQhC_W_0vPR00r^LyC=3%E@UG z+zH5bGD;>7oc2IFFc^sR3pT8dEBqgkHCY2IXYgw8-@i}wRD~}(2cl-e77-gu2Q5Eh zrm-i|(m0D|Z~o~|X3;Y<%Ny3WFsLBWhI!QQwhIX1<(7RhtaV?QPKs5H_g+v+zlSUn z9i1!VKvZm8T`lzV(hld<>W!b=`LO&F92QI46{yOCNVh3#@NsZ_;vFO>ZWre$f=F@Z zEb7;=u()4$S!%h#mTmVu!Z<-0<6HwBigW18`1p`duLbZX3OH-AZ*eaG^ozN7Zwdlg zB<$>_U;A^0awp8YXsjVS2LSm7E+7Gc*2YF2At53G3kQGG#e?hl>&i}+tWkwn&ZzM^ z-%hG=(^eJtzTg)jPKDx=t$%M}~0`QN`z=_x|eH0UF8ygx} z5nQ&-BNr!gc?NEK;Q9CfMEuWvDoQ`577G@o1;y6M4=% zs_v0^O|{{eoQSS{DMO;HtbL`wbOT%@$@+qt1z2P;^uijMsA!%(d$tDF2Nbm@F{*_k zIPJu@H-~2%zM_hR#sr(_IFv>P1|^93K&TBrzY{6|>mBq(PHsxm;knA291+ElrW;$E zdWLce3%R-O%9Sf1ZcQ9hOmDZ?lD9)CA#HrkA776sAntwflDXI6D1ke@sfDIVy9{&8Z=i7GO64vEYRNJXPc>4buz zH?7UNlsSHc4J&2_XYAFIursFM#z6kNC*=R_En-AeY%>5b3q!+h@Yx9ZL-7*s6o8!$8B>F1jE8`< zba_?P$Cod+HUp{(KXqjMCu+&8Vj1lFglWypcffL25sifo8g^93$22nEt(>7;T3$|b ziZLL@#8Imw1vwW?^dYr6Si?PW??^9U+ULHT*tMwkQ9%>9tUz4rLsNJ%cT68BffFKP zlZJ){>?xh64&Go)eFlpbFgieNG*j$wGJ#n3Ala*YcH%Fh;Mp#Gj$yepl$$EnfJs}< zLG@Vb=(vgdgZVyegotKsI?j6s7lAWoM)Ey@Kn|Vl+m+2{9Zf`^5C9=W0l2@F43U(- zFGK=27gg#DxK^P3NvdMlKq1yH5qtls=z}H~3FfhugHa>pGpjwQ9?)u~;B_Ed^#MG0 zn4t+6kCpKoKb#kk1dI_v^1|znDr;-y?bpyQck}p)CNjP~jedS-X{rb66fMDqW8Y9m z;flg=ST${J6!3z=&4wtA_7;d^d%fAYN8A=458W=J3vsBFm6g>Z$Qpj!;6uAIBgkle zf9rL}I3S>|&HWGvA*+^%Uq(F+QU%CMRmT@c)zyLJ)LRUneVjYdaDhef(~ z=0N-bHU(s3o>3mLEzfy-)0y=pu-~l7&CMl7+rfM4V|wb;eVComF_8a4l{bl0K+?LF z7InE3!B^nhMTsYY7&%JNr;s_|&@+4PWoKf-6#nVqOkF0VYmj*m;lnYZUvEVfPe>4u!&SJ&|07O^rUHF{P|5nEM$~z&qr&@ z0i|6>O29nhkQ|u_od*oP1fr&P@1e6My1FrmTOF-UIvCt6@j*K~Fh_0*Jz!0`=u}-D z-0q;NSTB7Id_X+o44cH5m^$5k6|b$t!(_fvO}l&jJahR$e0=ZqNZqyi&KJtemSXOV zEW=Zwr7Q`_W94gi#Q^lYCscu`)E^my_tyYIOQ>FdD`KvxDW(aE*@E4Z^_(aYvA1dW;CP_mr(riBac0~Pkm9kjwRu<*%{@|T z8VD!FANJ8|#iqhdj2}LlIL3cqQsx_~6>bQv8)>m&9ST?I{z!Pvo*;-7Z{MR!gMX$$G#?% zA-xTEe2F!o%|?#W+}v=HJ47v%W`C7vL2_>(W(0TxxNwBnA#)J7$l>?1Ez50TaSA@B zz-|iN4sjrb_m|h5K8TV9nIrJcozT<-)QhefqEJ4rr<-!9*zr(0@50vYrTj#s>N!XZ=zh=5IrTr~*~v^2HuEqa zMgU|uUdS8FkXj(EwTNzQOc1V^v&9YqQkF>6P9e)@W2+FXK$Dd-RG{rdZ~$!$^gNL5g@%<$*I?E`dlUSmlaPL@U$nCN zgkh*p@oz!frd1_IzlaoRWXGTdhf2jK5e!5riUDGzAfSS@v@OT0s;hPFLCxV3G&D3U zr@r$CvQDH!KuYF}MhE{UG7o@sts=ey6kVSDpXM*<5}B8xU@kTKG z)!fHNQb6jyQO@E3%87@QXp=iSJ9VXQM@Eqryl!pv2S%c&udfSWL9&S~n;JIYe#lFg zA^VuBJ+>FqdqipxKFpEZDE)8+D3^{r5kT?ybTzZD2KF00J!SaYAgZ1T#}d9N^Sn;klvRYEzpjL}*e&2NjQtQfTD>8xmUeb=SfL3Cp@|pZ zr-e>gup~koQ7I^dBew^j!9lYCwIS~cz^^2%T96gGOpKKUjvvFv7>lYOMCED38WgJAyy|Ou)=rE ziF0j34{aOX_ova%#$t60%nl%2l)xp$#Yw=82@V_?5hf;n06Xz>v?06P{j0TABTfUT zbQmNf*39gfGWsu`!sbCO;wS)A5&CChtSLU0N)M?d!f7$V7F`5Z_aWrUpSuXEIOrD_ zB+F<#Y?^K=ya)P=dGkJAUSPae@$?$D5ED``?H>Jbs4P82BVHT)GDoZ%$dOBh_O5LB zGJupnps4U=5KZiO;lgv&gh(yD;oez@ECe*;P@F$Jj7E12&q-U(hv$Ks_@#U|?$@kT zWMpKlF)g+`7zFz&d=Av0kh3CmC%cbGzH%G+fX-=g^8J2c;Ta(HC|LY3TABCc1Hsd$ zqg=$E0U2QUl-dUb91Q^gnWtz$&^HlHKqvF#*?VxFMm@V$vF3OYLO zsRmorV{1^cx`SoMv%)F8yD7pOcN0c)uD;m13yns^-t)k1;HZF>11j!P$Q=v{aNhGr zgYzH=#2jkFUpQTIaweckJ@y4K0;h}*Y61NJ+P)wz;0N{xtP5d-*N~MKRSaptSx0J* z-$0lNFF=9HY|Zl2fgqR8OC(Nn!@(-!H%Z^XL_8)yxve5(2U1BIcbi$ibqZRzi|>Im zjW4XQoFZ7|8tEnFUQ7TLxYI&BGC;Ok`9?NsI5}CEnPp*5C#?R{JDDZOt-d*&>sW@` z16x|p#6%jaCFXUVPP(O(x03d*qcx*wIJ^T85u}1DzZDCcNQD7$lU5t}HE)zSb#nS+ zJWy)5k!K;Yq7Ii~LK6Wa5>yL%Fwn80-Npc1wJB=sZEcg~1KHkV*n$V>D(L2+3MeTl z0VM&gJUfqAVzZ%m|N8zOu|#4TJ9;S0nZfNOTJ7$*PB%xJ1CD^OoE&HcOo64$A!^RY zFgQU0ZY|9|h6UM7O2pRZd(`I7Du1~Oa5H;TH2duMxRHT@0q*o-zF7FGAsfV}~W|hqr!Qgw*k=z%zqwt@bNRNhTgvCL3oyw;!x$bz*5-XyPtceQ>$skCu z_CyK*(v&6kvkh)kXyR87pt^F?f&ejV0qyz<7!@Rk6SJ|=Lbrc~Fd5tQD3X6Lc>yRN z3INpS$>^MPfXGk@&?Lq~!?*f5B6!d24uGXVSR9$Vt>>fLaC#%@1prJ#-cu?836qJ` zJj+Evdh4A0e3f<}6w7ZV_EhCmo<=#q7vh4E%{HT^Bt0daOLvrev1LH?EwEcEp2f~$ zi161@^<}lWWZ; z*)ddDE53_~Xi)()u8u{%8T#TyTw6FyP>Ae7=Yo5lk&&?kzZ(^$>1rIW z)Q`7tuIaI8`=KKqP~~#3cU;-FUhl-%7W%haXV2O{QVc`E4LBLd zR=-$aiNUZp2GJJ8l%Xv5;2(*P-*2=^s9Ql);w&<`a6x800uLDd!TQH9)YLSSCnpw=%3pvjYm5I%7xdI7aSDKg@+y8_F~(a9+>F3tzK zBQU;`qYAf{gLDcpAtxYz(RB4YZAE`cr|4-9+j|OD-c)hm^C6U!;pSF0mRSxKYbXy} zby=7_>b6h`_5%ZV#*PY~dfI@44uK5lEOH(_dcH@>AiZuG+ovJQBJ@!3^^S6V- zW|%c#hO#yles+{!Sh%TyjSRD3h0T3X+u>vE%my}$QrN@w(cOggVMjj*>~r>uivGAt z#&+z>(oH5X!7fM^m=!*l_31XMqrU!rjs!^Vh*T{$m_-jAWQg`oEdPxxyz@Xcdb2Vz zMohimqqyK=U}rC^e6~HE<0+FgC%QRPK%S{;-Yy&VGja!FCxSS5k5tRW2t8(11L_JAkwr~x3uVP6H$5Ln!fYFP>g zAd~yG5=0?(hIeL<9q>P(w2;y38yH-N{6N9lO&({_#xSB$gaN(*gJ}mq7fFL+7)S)s zy`8;1b1i$dF1Re(Fm9;ov?C;W6aEl6bJEtFUpjAL6Je8J{D@k5Ckj-8CdFqU_7Q+1 z$Y-JUk+mp{G&C{_2?@bWC=>I;P_N^wA)m$r$1w(vwxf#33!ILO3V{x_2J{MN2ucfF z=8a8FJ%`PiNN{+e`$$$P!ugTLT~GMaAl<77$wDa-KJ)$K^Pdn85tJ%&Wf0aQX)f*c zSuM!-B@x3F7YCz~M%8iFY-lw9;H*gvt6d3c{o>Un2c8r8YEV<87Ux_*Fcr3w!db9s zoSjYU{|w2e2_e=*G^GlzdeKkv$Y;Yjowb_K1QHc!b_KQ+d|$f{20|;V%F;AsXTOjgo76!TkMmFfIxlK;JbHVzr3K! z98G-WoUa<+_ptpIhZ1_B>p#F~Gttmkxw{8~agvDC(1|MmlO%x-G68dAc@7@@iFO1} zTnik&X};=pFxGRBQT8|VR3Q|rwD)u+IvL!Xc)J982>YjZ2LjbV34vsO_m(yBAhUHF zllSKb7ia!pdfnUwK)O(Mqur|u269R#@>M)^bJ;`O(14UcP!Ode%rkC~r8k`Fl)R1+ z8wKFF(I$u8Nc!ycM@?w(^cyBJG4YABfT>3jx6=uz6mpdPJ*R(7zW;i1NN07-I^NEk zDqR9U3wsX!lF)ySlvq(mvJl9SM4WBLHPG+kX9R#rKT ztHYw6{Cx!WSyam~1Zd*?QBc^tj21DNSJrn1K9(-1s>8aEf%^y2Tlc*hu1VOeMCbZW zAdbGW^6y^Xk5^S`tdj3SbOk+cTsDk$9SD_!>P?ubm;X-7%@2x{pcMv{YtgH#6r|$G zVLu=k^vI=Ui8s)9Ql~_2-Q7#feyyw9ytkor3C5rTl))a}-pWc#zu~d-fu4c?oT^f< zwuA#%@h`3PF;+BEoZ{k7hlFl)igYF4yBBUnql?c-g*_#g7yN^ol9Xgot%`$f`K2#d;<9KF2;7$u1qBjg5JQl zgit+0agNI-^%?RkZ_3wKfkQ9O`^QeokK0rNPb3(I;g-!Ite2P4hI*OXA?+^<+4zkjB-w=qjIJ79h(5v-tdSB zUCL~ous*5m7Ab@E@};-Xp|gbu1FiMl{rUm|-v0g_8*zQ`x~OAD?0O21r^uQok12Y) z4FuIvbpt_VTZsq>e7gB&GCNh@Q?l$Vj(rp`@j=f;y`r%O)OX*E3^Z39W%%7$}BV(ccYk*%?b=307NPBoF;3+U zZ`sU;&`zKn35rT$qq@{|Vb6OT>(RJRq!xLLUO#W-IZ2fajWxa809tK#5_e-x*yoLs zj`(@^?h$i#q+M{Afnzr`Hmr;Y{fw-BEb&&hp_|27-)t+Qd@}{WhPZgd;c>l#Q3#gr)X<;J4$#x{|MU%3>%04 zzDrr@9A%SL_SRf2YB+*;6r-2)P#bo6qvHnPm*4||H&Q1*iD(YKsTu!%o0z8nqLqNC zX;x1)B}r_gGIwh8d_qO|?~kO&|C7lfdGP=C*MoxHr2)SCX$I;3`%ec2Yb^>V%9aLF zU3ge2IG0@r^EL6uyV%6k^lXIiM9s+j^-^{IKZdN-oXdFx#Jcg1ck>(GzBe_%Qz<^1 z>@)XrFp<7HfIK2}ZOi|D^7pX#%-}eQBbGl~?`2flTYfxWb4y84Iq>%)#f|?ACz#z= zT%x0%zFXt(C2yY>4xZU}+N8OayKMjaGgc$%Xm>pNa(C?c;+tBzKYvRW6sc}p550GV zcppSHSc_i~tYK=ZTYSdFv{!~PesBAuM`KNRtTwi+TR1rt1d5)Z;Ty5kS1#KW)}_Z; zRHbq)MpMX+wrx02p1N~7w_s+`a+`LtXX27;h-~J&hR3;{ZM%)wTM~aiF;3Ar9Ce5$ z@$a`_C4`2gz8*JloNCQ=7LvB(s@SF`_-%@=`|fQsYkp5BCvB%wR8WhS6+_qew1U~J z?H;drlCjaC+IE@C)h29JCMz#W4@4iAseb=9FY-pX%?ag5!b&l*xbAaT-}Gn0RC0t9 z^V(_d3KbL6)3tWIUbTUr_Hv|U@tk-0CMb~iO_15bCnt79>h-dc%!jv(QPs^ps`4kz z;$rcWvh2l(@49lqNZ&=b;L+bplAG4qJWH1L!FX_%Kc07uR@CRy?B8!) z{^bJuFj*ZXz~1@n`L{4+BG-q}PGToS_+gwjY$wvz`dd%L_b=~sp_Xz+^naIUQqI%)MJW-v?m?dcr3d*RH};U`|MpOESC{F6 zql;8#^zm6NHBy?@VB*CPe=1H5_~)Ll?b2(Ny1l#8GV*^vQH7)U^FHd%*DsXC{hAyzDrq;pgRVS=HwP1Q`RE0JIb7x zsZdV{Q_z*}BqzSY>R)1&EXXU@kXo{|_D;~svgRd$`j-%L3E2Ul7!AcG*y_=o0kTJV z>M2KDRTIO%$qwRhRnrlO1Julx=Yysoz{Pw7Swv|szzp2n(UAhMBO9#S*|R@VQ+@QZ zTE5KVghT?S9s2wICz4;3u^JL9aCz|)p=2TAfm*C>Y&PNr3GGH12tv|rh{9053TkO= z%feUu1oUIm$S4sLn**p1+q2WaGV{m1I5;x(T1pLwt+G(qK-_QZyNG@$wTJfYUnWLm z>84#V7k0=RvBw7n(zXr*?QcvIhdSo7Mdb6nM1l{^YNcl|fL2R{23*rt(8w`?~A-b+eqR zd%C#>a=V>#viU;h5y7gVNy2a4QQMll^ewp6$86uEz}bbYi2~YcrOHt;NXqqRpKRQ$ zt5h3IU9sp(pk9DZ(GviWg03BCE$omGzbr@Zzyu+>Hpmvhn~R7ng34c9Y%T>J!z)mR zmbs*aVAH=KlZQ5}0Tn=7+YWY0wk1VFNTfj2|N8pn3kPMFJ6vT<3&RT;dXGTLVpQY< zOxbeZx@8MMGEX7JckXn?sf`3WN~9Yja(A@rt!2V{msS7IUNW4%=m```?2>yB$gOn5 zQ4yFti3Tt_Br8;expdxrRlM(VtilR9G|tNa_yQak=A>@QUsDjF=D5cAVL`%QpE|6Z zB&FLK^@rq4Jwy#OdTMH+>FHc=yz&_~-oxs~bI=zpLM*2R`Zmy1w_;*+_4U*B9>Hu) zL~opVaRsP_*ati*JR``5V)%@p4m8RGUqQ@zd-Y0f)iyi|Zj*4L#sAk=t7~i1NAEjj zW;S-4NS+Q3o}AxFp8oXdJVZW2<(`NkN>fc&h(H;Q?(Nk-DR2ZAphP4%#0!~)PymYd zY`}Df(j$>{ei_-3zb@^vV3+B1;``t8BnzQNL;N`5Jfi%6$-S%(2u+Wm=JNsV+F(! zO2H!+=DDYo4`4AYLQso#mXVfLZ&s9ooL59-77{S|i@zTud;*#Mi0HpI7CA&`E{)Fx z$@WMt9yI&m^OX4;bRbrgBO_OkPU8EGTY$L2?oov)V=iabiTf1*OWIek9Dqs#4uIN? zdhgy#=u#o*3i}#_Y(QWEAD~1fA=!h*p7;r16*KfE;q&cCN4^z+8KCCgqTr8UI~TeP6L=1ZuH=9V>mM8v zKqw>p0mLI~;1uXXaUe8XNjJwVPISIJ<3!7Od=Dj^>;a>RhdY+Sh10pCkh+c>GU9Z3 zi|F2Dkmb_ExN&3zi`@hhQXoYbBO=1XPha|FNF<}mf~N!qgL;090c*R1Buz1b=`#@D zHGmjQ3^5NX{eKBeHOzrWVi`V4oOc`JirjR8kK9>D_Uf727+Bne!}Zgd%a_*(u^%Sw zS=E!!0kXWMkqK~T5E)0|ksa_S6J#e?Ek<^BdEf?sjV&!LgL-99wE!Rn46B!_gG8cz zf`aW(oZ@lyKefXmYrH|wXZIv`7^l7k@5N3t+7F(t&s5i>domqCtszEPX) zQ_x5!f#*m`F?Js%<_JSMx8*RtWW42N?D8;8owtDKF!FC2I3E^r4RAgP@C1Z~i7!ly zL4!ZoKoTDm1*+=BNO8EF>8%l@`wga9c5r-Cj`X+p4%Qd4nqqIV&+2#^*b16 zpScgujEsn4(L#{*FarT40&;T zCMK-EDE~odrm%w4)wf3_ozoXw`|EKE!bXJ1%JMW}#61s=e~c<-ss@xDA;-8LL5!B& zcqjKZhbdoEzielh`ESOL=sM0H1YS|@1Y+nX`UYeRJ~U|KvjZjS0&yUj9AWOZ{yGRH zm$ZybB-;^L*w-L})6dcpyDaZ};ldw?W|z=KhQ*2Fv;#R#PEKA9rvfa}Fe0?Iw(6{- z-w$D?qfh~ZMWj|UG4cJrKZ%b9tdE$n@Rpo38+%X(LA))1VU>e@e0<0cBOcEI!OJH| zegNA>KMCXtyYxpv<%|G7{~RWMJ19TJib50zhAPul9~^Y)x;c*%8BHx-km{;63o{gX z0SvqN_Q<7kGcZ6CKG4_q&~(AT`~!48MP$?zk4=g=(NGQyEQ9X4hz-{;zlS7DlR?k~ z@qWPsYzTy`V_1~j2H!>wCTIc`<>E{V;u;^Oi^-y?oPKD6Qx`i0dk>&zSfvN}HCTNY zN?HnyH=DXd4DzW2pnC$)FHqQ2 z3|%7gV2DTL2g4j40|Q>^!whp_b|mim1?t<|j{~UEb#Qc?K&%I~Xn{(4&Ox|{(f{S< zQrHnr{wp9>K`aH4dopyUJ%h~wp^*5@P*y**%(`fyuWy5?C@@pxB1P^9a6Y^Oq%sI_ zalKTmj6PW%ajJu>$f?Nfn_WP}(eV8Ux_+v&_2@^>=DkoHDxHyzS`vG5Qf^KeIRYLs zO$zJUUqeY6G3Nw<)aNl56u52J*%Q0nCdiyZ@>um zLI?tG?cv2U$1~V&PL5A=@?ZntX2qP`4jctIUuUtK>yj<=jsE`qyZF8T9PskAda!PD z%hL!NI4vK~5u?`drUn~+v>H(wR+!`v0}LBCFJ{Q%Am083xmpPM3d_?;^1Cl1d{RrR zflOjAGjndO7}Wsn#a6^g!YKe=j}DmPeaI#I9;i~El!v`Z7nWOMMgoF~DjwhMdrek^ zI*%CfTP0IX3?|3Xi98wkKc9CetEA(m1NPexj3roT!frc$Nx?yECllw*O#~#AZ$f>^C~VI!w_h_HKWR^nk`?=AaTq7%J6b2@+iW==eG|{C7-k!7_s{48_uE0|Rg9 z9f%kR=t*&)Kr_Y1^`Kut3}xu*!se+4e%Q)m#Z1aLNxVnHtl9kYDER1NSBZ>kgvQxMqUb*f8V}if@)%eE;nJP=mKX)=q*j}o16E8r715bVpNDVmF->0m~=sZ zX&I{+-JT@TjoPMal0oaH`rQq;_0ZP`7f<9HeLJK|v%{o;CFaL-)Y#U!masi)vR(XM zbe!!1GPbzo<=2t1fR{N~2op>lP8SSlO;7OKEZDTlMnso z(o9CuT~5H=8Wk#z7YjuiB+iLzAIg1BvTa(=e8#Fz`KI29((tOFW%2a>bEFqYk&W#UXg}jhy!ua5MCt= z>v{1I=8je(qQKgDKzpMys!;LR1qf)z;7TnX3og2PTehD>KBbK~s_6YgLlD?Y;!47; zId&G`1}ZR>A!UI$D7@nC-i0+C8cUc+faPna932FT%za9y1^f>tQ5hH+J+p10oilZd z;U$KBXvT*;kgq_s5QU+=2$DZm)ozw=#HEPEWRa%OHA}>J3C6)Z;Yf4SM9CXYq&bgU z2yFp<;_(@#I6_z6x=#^wF>1Ma zc_8If+RrzgXKc`Q$pP_Es-oaoRnSM+;;RcJm)k1WK z>)-jsm){4m_hB0bTZNklcYIh>l(2vR5Na6GCO{uv8DrwnTEai*hv^R6plEvL9}apM z@=`p9hGE#m7U9BIV|CWx>VZnaL55 zyY2OJr|G_;bZ9*;DN}}$tXhCU#MyzCywMpl!(e?_!--cOk^^FAaX2s#8(!=vWH?LA zh+yU7ylD4ETS1PHX<)UAARHitOadA!$*9PK=Q3NYW#lNIH*BZ-@Us?M~_S3FPuoYI; z(Qk2-5bYwSUiht%2RcE<{}i?B5Q0ctL`N`(!o4f#8B`x5Q&YL? zHLg3RY2#2N2O_ctZ!Zw)o{IC+;dzkKc}>3KnKN=9{HgJeNW>#h5=Fy=q0G-pOI23T z01`90Jm;G7Kmxx`mf2n1_WJcIgewqV@#`HdXWa`$$aC0%u{($H3t-ygGL1EdcMAhL zda)N?_5)Q$*=(xKNNgn~PTN%1rx!8(PXF;S|f-QzfUm+tk0 zBXBOO-K<6I3pOv5Vs3DKwD36}Bri=JBY+I$z^w&H-XHc1j=IcD^Z^i)px07SQLz>| zt+j^xA10G!Sez*Lh?Bg(JZPf;))+!1G*+-t;29zu_+>E8PKcxgFV}_dy>Kkwh!`^7 z)WplhwY8gpkr9>`=oe5DoxtG)xIkKD-#lF?N%v?jqGGXE2m1S~|BNRAi?GgSF^}Ta zp_L?_AC@Q<-{@7|L@k`+h^)-nbI@M;@L>Qx=_aVhpbkKjNX^}}hYZf*8F*2N`#W%b z4L7~?H3nVYlRG?d0HlRMB?f8cDwsz4T~>)RrG6I;AaR8}(vQmzF@6*J}Mq>54GfPTv6fLVKyNSr2Ei2+O^`P+Z=G(UYIC{>)PKXFZqmJb?cUI+p`4 zcY9tN&+IRW4RKUd#n;P=_VC!+7*+~$Bi8z>HGh1i8DGb@K}`)dI^pw}dpUT*_8)Wc z=Frn!_QPb0Og&rxNQ_GtwZ8=I7G4aFCTl5c3-v^;5Cw#H-i(MifCTQ{e7tuHCZ35M z3W0pp4ht2J7S3hzm(X~m(9jDY%(Z(}Z2wgABiOD%P2(ZcF%&BH23pb!DBn^yU3v!j zK+`Ji;BjAIWx#U=HvlOMl+HIi9`sL3T5p;gwQ3F_Ofw!#s09#y>HT4kHK#vy-w9nwMN zFQF7%SLQ3?gS#&#uL&@jfS($RzqiVzQ6lZhS@C+^1f%Mfnr9fo_9GQ*{lwfNEzNFO zlv;D{bVxZ)IT^n9T*HT_jTG@AL#@~A`NHN$)%kS$${9z$4D<1`N$2)BmlRjD`bd5| z-mSCBTi;>GM_M&>d2*3_n{pA$bg4q1%8{($vze7TSv;ob<(7>lnQI-5l{487?F=Wm zy4E4-uis$EdHGz_d&^(!`v9VEkY8Z5@NzMjM0d3zU2oaNOpCFndgS9mN)V@|M|upC z!H^D>eUJNzl0fyutY60Fj!&NCY@cRkFZ69oQVlxmn9l0AoFQV6T{(h~`ybtt1#S|?%2g=7J-?v@bn z6g9KCAl634{eVl4{6_f9!Ynb8t#kdB8aIN}(>tLT{5Tr@sjIsiw^xfT`Va8{H{o4W zR@WhBwSO&p{yS3*26+09CpvTl2XZrI>OF^~nb^K)=dr_Z21&(sIH~^Ur;8AbK<9e< zTU3BjXZt*0$c1w22reCLjX|~_TVT9@y!{eWvOgRc@bp4E+_E6IE3#%IeF`c!P1gjFL^*z(nxwN9H$~;vE z_S+)omuGG;>(cJ_3qU@g<1;VY-G%PrU~67Kl>R)x#XOWH0oFh1R1` z;k&s7qIi4&>%~1gTzR*7PGxid5K2REW2}>8mp>#l*!>zvOWn7LfYJ2jz1~k#I5J9x z)&o%EI}gVOr7j_e*2xfU;G6s^`6EB{l*CnLQ96bJAJyf(&1FmeKrY3qaQiTw4%*bd z{aw2mQeEw;EORjks$81wgzZs#%N033VAY=EWrqu_%=$ zOhc_108JNsBIO-#-_AlN3|@Q%Nj~du#4@1{aq`|bP_5!X z#wK9MT7bg`naT;tHcunn(ZG5tr+1EoCR~J0A4=|849&(tVeJWHJiPSpV2=_@Tu1mP zQeUUE`Ci#RMd3qGI(Xg{r7ZZKc!=f*P51}!f`K&=xaJfH+l(f_Y@pzuLU9BrroxJV z#yH(E6C%)1GGh+YqN;6HBeVfRP&#iS)jZLlqz+@OS><`5Rv;942D#8vCnO|514YR5 z0c-LF?b9y%fbpe8TehWIlR)7#_Y0un$1?X6pgjXBpV81P5&-c8`X8iF`tZ}zLA~Ju zO)j21f?Kca3c;BF>eUM}i)JxiD0UOj%p-0i>P*R;BpwI{$L3}@d5%Dp?A?&O?{)1q z^nb3fEs-kwj)^M6bAx5bDE0tX`{>urk=8Fd`(X(RaNSthTAv^MZ*hxjs;D#qb|Pfs z04)KYTF)OM@E;1s~DOe33p7-{y0QNkRfy{HV8EnwsnpyTEtW!NK>R zMI*cqIQZn$)G`Kft8?EbEC)O^Y*X%?Y1{m-_EA2NL*)VmVn0GY1(Go>c=t)Ut4}(Y zQJB-b02P7YD^Krhe8S%DWJXCv!OHrMoKsg#2XMI{%cIdN z4#;%>zyL5+QC{A0VCI1^*i+HU3dBvpo{zV4-&}YWQMLkeKY#8{8eG8EgTT?O+Fx?2 zOXQ|MxQQ#9x8iKse|`n-?;KDvc$N5_KthGDp>tHOWR6rdh$x0UpNJsO)(JAnMA6ud z)(f);FeC35EZzWpc5g-2`}x?pg@pjv^hFbzDpy8cLH7CX{d-uhY;ho2r;MR676Fc| z>ofUo8%N@v3$SnS9b1@q{1UzrUB6wsK4GjT=y< zj|pQKra40AJ>dBM-iNy}H;*A~yvW8OsfS_PQDOx+}(b-w|A2R~F zeFFpjm~x*Z2WsLAa5zk-?*v@}<^v{!hZYjoHN2BJBi!r!E>R0B901|@`0-h+C_t$L zSb(T^JHiRuwq@L(=_&v0PRAK+ND#||Iu9vFiGx>HsJ;(2k|@+>sI<|R&<|Dt8*4D7 zA3M71esJUE;t7|!%a4+9@U^l!t>zm;MLDA|VfJ`6qGc14)LKUlA0!fjdU+so zPWAB<>V6U=Uj^Y!@Xews!HAfPlp4)%-|lkI6+{&UkHTW|P64qKyHZ7+;lv2|-S2=+ zTVR%#*2b4=L2{-ed{Qph&*3*PE2O{bn44o526Wcl1Q&wy4X*|v36`F%?dK!o>Gpsb zr2epJ9j1gFISC26YKF?8z7HSLfBC@DN7}iORuGe=h{gYt1BI z9Jqh0st`M8{xi(eIE#@RNqB%XnlqjuvgpFjnE=y_+we{Q%puvdX_E)LX3gf2KNc>gHs6Sf>+oF!h8l3?ynbTFJQF%Y=lS+!ZdVPxZ7f!+myI(*N)$F zK?p$RZJ=B@WLjPL$H&IFcno=8mHvPg2NHSix^3e#*t}UnL z3tH+NC3;w6{XNQ)g$Y{(f{rDDM8eAGfAL~lC3Y2+YuQdcLqESIEGTc;LD+Ry5U^`4@1=%>fTXxM9udTb(X+7R3Am%E0g~%H9`@#sQ;cTELk&EeO|-I`ez+9N z(0Zx06 zng>t?tiBCg;~EoB)I!A~h*@Gfl|FZ3qTogV-66}%g;@}|up+|39*r4sRSsT8Ryh*f z)e?cN5n~3#jm4Ak>5kc-MXc$3zWM#1-!caJ9bm4RqQ%$`Pk02prfmHOwf_9I!IXKn=rRXHYwM>L`V5K&mmj6Z!ZAz{MQWLXrGDJEaQWAYc2E#$;ve0(?v z1i84Bju_!ih&{V6aX9f74J`pEAD7bz{?_oieQgnSU1 z)AaVO2u8o5tkBE9IfvbKm+{>m-vJ-qqqaH)W_{(k5D&wpMX)48+0*TMc;4`ITQD3!PZl!pd}=UX zuMDrgz{M)V1my!x1)f=rZ^)a1_2P9~Tjl55nrrj%wQ_R}yH(RWXGTs`k_K^LZNtkp zZ)&eZ>hs1&iuaDX23|9!MnEajO@QUC2|2!*jfklHggZ-I2zM>zjvd7^=ARxu_=(<% zQ&RIPk!_E5wenYwncbC{lW$~1u4+Y99axRe&6r2AU-hGYhxkEaZwVtlK!dl{x%Br$ zr&5JbF}imU2qfQ|7q{5!>U}rZI``m#iYan zr3DEWZ6V#Ev?8?>q)P-n?K)ZKU_M!^DJ27KwsY@@_aYGaEOR3;t9E8=UiVFUl=~^ z`8(p-ub?f>_EiXEb(pC*0rmn+b;I_S2ev{HI=Lv9pm`{70_vRwy3+Zw`P!H$iV_|~ zP^7HsV1NIKeUg%rl{Z?ZIi|DOx$*2`?7e)QYn{@YY%5{{t~D^R_Jx}GI-uEB{Te$s zRXRJ3pCcoj{)Z#v6cmDPVd?@StXsENgbP>YqDLfQ4~8lzL6njNom;I=+&xt41} zJgA4s3BZ74LlpA6IlGEtS<+@kM02;-KJ^I-sV%ZSsfYYHb*b5Y=R_l9lFi<<-c)&! z9rlc3tj_1%e3V*#y@mc;kZ?O&u4a=frR<+f(8Je}Ih)|+}5 zSPnxJSY2R4;EYDQI3W;1^;yyN2p}PfW23B0-_wf9V^Pm7>|;mI=5?jWY|K+tWc)Pv z>#1<#P+w23&8ySJ8X1!*OPX~TZq;9=Q`?N-P=6S|^83H1>Adi0U5Ao$AOHXzdk}y* za$bwYg!uU1v$Jk+<0Xa~S;TL{Ye@YU&TS%bohj%T?d|19s~{W+7{y&h5Xix=ITv+a zOGhl_hbAch#{>HEKH?>oO!!m=J0rS06yh>2bKrO<_O!HgLQ)bOUvQ*9^7O=DNtUp6 zO^d!)cQTT^2A?s{)UT=knug!yy3@WuckMa&6oxyQGD4T&UwLC@2C zSu*?#`=0Vb*umUTW8x3zH9rCFY&44UXdgk zuQiz!+O~V|-Qn2%Ct@qjj@UpRW)kVez!6*sDU=~u(y&3@j4#TfBHy^y*Q8j?U-i9f z4}L)U?+=Vdkvr!XX@-XD6m^#xetZ(nXw}$+caJH?yOTn>%1VXGY6HA)i(jn|E6a&I zc?}1r#2y2KEhGt^!K{sMzDabyXf(6S-?$<%XxG-}_TDa(ttS7D6TSD|r(=vlccdo2 zykzO4C6OGtu)0#yKY;PPV9@at=AHq~`Hk;m9p$&4V778n( z{CJR|KUNnel-9aV@ZLg5MK|tW-L|W^ji&~);wF;L@4p^ilTDeb-c)}^Ek^Lq-Xe`| zb$u0$*(%1%SLOm0Ii>Z|SSLL;CtKt;1y^h&kq$k?qBf;c`Sx?`jvZ8e6QvMm;<=;# zpy~FF9@$64y@irzN(LJKrO{pzUaw)sr%_PRb>@YtgxsS8Hrq)gk==M2KIJKF_1eNK zitl9_N@v<$q>Tnw68SV1dd*!-wmmuMba9tIR<;OT`c~2+ z)9t`+$!#Pr_eGAj5%v6yqbhpp>I=^t?u#C+p88tS=Wp@Pg zgKsBoGw!&zTob$9)1Zt2i|D$~>N-=4(N&EZXlFK<;q-0E>Q&bG?{&bS9T?lt`SN~m z$V*>C=k+&QNANm6^6DDfbFIZx#{BMCy=UJJiWy1RxSekAC4Q49ZYat0s0==Ca$oJx z2jgv?rd{8v@8NqaPyc>zK6CPpvDC*jt~GKS@Us)Em&o$7*;iQ(&ofub(vVDSNZg;* z<%s|LewFwc;>+9>1XAeKsrr_d z*+T7r`IsZGKbvN0#VEAATTA+GwfdC4uOL>_T?&l_f0WJZyvgZmmez>s3;(f zRs?-)(M)YdJ%YB!2r^?vE1}v(i3S~qtXs5u;g0Rw1MTVC9^0za3ZdcxBM-fC3i|Si zqR_q6WA$QPukwb#+NQv|S0-7b9jG6QALJ7e8JAsL79kf=f_xwHgxjNeWyTp1vsBs$ z1x-D@Wl+XAi!SOteDHuUzKM>$(0w_KFnAFCU9zshi)|B#s#kX~gg1*xN&mKIybpUJ2dm)<~4s10*dy39@{q~V&h(l2uq zS43PRkrwa%=l21M*VM5hluKamqr$@rQJ+DOs44sF;I;>#_*P)-vi}MEVT^&Kg4imK zRW9hT;G;vX+5y1~2nHx(YbaNI6ym@vpMe;BuPFpuFuaU;USrJ8$tgN^#}^fsUaH9u z1I`fQ!;(nF@l+}=ofpRST|s?UV+WYf%Xw%hJQSsaR7H23Dzwjy>wfqa^ z+LT)nl>R|mq2|XvEe%0!332!y&fnDH<@|71tRCMK!{+f$3CegqJ+3(TEdZ*8lREXp z)r1j$e(&o_SuEkV5;O)6r3R?g)%m^ql0F#MX{F8{J2&>_T(gi;a{I?4i@&O zdB6M8&eKwbs_M%wA@%bA1B9BKrmwN)@D zViHevB6&(lM#h|NtZ(_LbZYcv)MqXw(aH(Y(1}88(!k!Y@l8d762$7~{8aMwln#rG zLO|VSGe**3B1v#x!LEf=1MV^{X^j`i z2d&4U4q1*3s&$(5m2RdLLJm&2bYO&$v{Y0?1nxUXgRVG5rU6L^9*GJD*d=`YLH?{@ z^V|gFIOriWDic1QnB`5m{_?CL|I#lj*}LzAD#&oYs$mDW8p)Q$&?w@?{e zBnZrcVcx*!N_AqvqtX$0YPYaL%%ACMG8fPF(VHpH8{qhmg*Xykb&WtbTHYARs=o~D z_InS2CCCPOSol%Z(t0Se$ARSD$n*3^TFU50*_nn{>mJzrZE^< zgWbhO=`my(IRq{?WdV_YVS31+wm)?Z$~rt1okHw)LI;N92wfthn8GV2whWt#fInB> zPoq-;^n(no=LhC^4Oud}3I5=W1mc0RWgOzC!1v(q2!0SNGYhV1DD{bz&S^XjiR>c*+J32mFo73VPMEaiD4^*svc$G#>Q1rtFw*e}j6-qcl zj=w`jym#mp@5AOd))w}1`BMOIFdgBto(?<=aq9XAH8wVCqR{}yI-0r6l#P!aIdT=0 z4QOPnU|P6dgVN>L1mY|@5)Lmg;AdrF3Ut17gwa0(l`+dw6d1~=l|_@lL1JkETVPGD zx+irXtw%mX4agV3Ofw4!X^GJ^m?T`_vVdyZtykF>EG@1wQBriwEM&Mg^fX_mlN zaqu;q*>6rw zGV|n7bw=)hR2!2?O4)x!tv%)<^RZ|bNc6yvN2ANX@~@q^lnrGv7{$E4Q*R(TQN)2i z;)qYp{ketJKO|%hy*RRWfvgd9aEgP1`mtNO4jpy?q2MGEu*XZX8+XJ7af*P8fpWpn z?$9X%_e$0#VJ;_4XFJ^ZN?T+r2_v_OO*ya#s0q6m%?P?$T7xbrK{9$U;<<`W1`4%V zvbcDTVT*&1u|SymD~!cu7Z9+As2q%?Puq0`DC3UH%Wk*!v=izmUY<@RhI=r;V$~G^ zMLox;vnS(a%TVv+;nM@PCwmNSAod=xmy#+6e;T9A`UNd$P;hiP^+%`_?+x;n-Wl^5 ziCm>!@cRZgvT{mjp?%7Mb)?nmn3U`HB45AQ6-@@Z*o>$R5g%E1 zFd!B5=QFYS_)ph-?^Zd|^KlKlgtDfIa9hJHsh^fOjNwvQp{-IJ{;QBlCUggmdAgqa z44TgjiY07*DU?N*pl1b#yo%Rb}I$}e=Zy)nW_-o=_n z&)sXpCeepgcEh177M;UM^-I}wLJvo6<(&O|d=7Kt(BlH>(_$pwU;#3&G@Vy=TIn$u z>WeZk7tK?MxDkTgFE7Q^(>;ZDNBGvyqXBID=FPqt*Oza>ateRa)syY&2^~d~Ff%jJ zvSV$9=nWhbtOwVIo++Uq{r`&D!w#&X#d-fV?gR~5oT|0PGc4e?xry;kesF| zlD*8ulXoNN?0ajEcZcHyZ*_i~{*&q^rVMctTt7j`MFj*vhyM;x?gdFni}q}J3L>xr zC>VOW-&LN}oRoz94yDW)070kWsu*AN5DX;d6mu-ifxAOx1baowjg-E3UG_VAIRu4c zN1Lg0+x}4Qs6Km~x*+{~^yxYC4~sZOrqH5;_yjT-3?mkXuPmm6mm&os8W)T+=0Hg; z4QJHt=hkxpLym(0DmnD;=6xX&0K^1~w{ynRN}QzJ@>SqT!j8t3h(Bm&Xv;8p)+Xry zuVtDTSvfJl&i-rte%CY!w`wfI0&^QZA>5^4N3q(TTVx=qV;2VgB zBbDS#SKK^ghax;Y>793VUXTM4EHex#H{jR32;dhd;iuNeZSGeTd{Ow`TfcE5_|{5} zz<_}AVRFu}vf#h`wtI~;u#-LA<{P7MR-#1`W?Y!G6on>18=(9|mlE&wN8wx%P9s?@ za0QOJWo_2;hakH&VLmj6WX0qJBdI>^AptbhhM4}dWs6_EmY!_6C=@jKuB;psTP<*W zr9Sm+rg%MlT8Me!y;&dIA;&=LwWi=ie7|~v!Ey_CMS`?YJ=BOh7=_A*h~qw5OZ0oM18JxASNKuFzf+ohJ6h?7Jvi1;Gicr18ye}y!K@bB4 zmTP0udOz&EgAj|qB`%%nWhRCh*dc4wF~Cn-bSpxl?Q7?g9bd7maU$&5wab7vB>FZ~ zevl;NfX7`wdh~sbvQ!PgbF5e63^E8euAewfrr&SnzRa0qPuF+i5C`t<`+EVx z(}kAe<-n*YeH7qGk_iW2jB~&Mp0~TwbF^q)X;2tMo%zJX^t5O(>Cn{Fw8(5P%;1Tt ze5(FD){(q|LMO5(bZ^e1okS`EwzV3H<<@RG4X>t-h>8+}<2QPoHTNfqV!#yr0;P5Z ze`9SRWN$90hye}YxXnjhfy}`NrqTxv2qLQmNlDZkN?WfIN_2O366*~13L)`FwoXQm z9_+W!3@eELgW5qR0=q7?rRoW~VhDl8tD>AW35T8hY}6I@b0 z#GaCS4UGXbD@20*;?TZ*MyC$)G9KTH7a50|?53qN{I6~MBjouN)3uV3 zoq!VD8M9LboxV4*g=pql!bE-FL)^5oJX?&6YWJZ-=u%SyA*T7uMgQkqFC7({_=Dd;M827TQ1#Xl75-4LU-T39S8b34-LA8U<+1m-8=?y z$X5`Ulv%!taj3eaMYydTgU%}`cr!p=*A1$G`9h1Zlp6Qd!tnoiBIj@Hk$gQ)l&3gi zKsX?~gXb96cyYK2ppCemzCPq<8s3^ue#FQ@rNBHaZuRrt)YTIAHJ~w54QCkwb$ke? z0IaS#^!|dBSi}WW((5qRN(hg_OiDDnqQ*)(1iTGVkFc^zQ`-t0Zb9@>{S|X8q8Cwd zaOd^V4CHN7(n=+lc3A=vumvQ*$GB_P;f%PT$-P45W7o)c(0;{T`1>4mF-?Zll)?#_ zftSvqfdRhyUna^vsB7xtR7+~ND3BNG?t6MsQc`9JM7K(y?HE2aGW@{JzEy(gHP2{G zI#kYl0v&78{W0!){pW8_{Tj#ms5(*a8lTp1*t z9%SS_w_4i{m5vLm%BFIEe6W=y^5|m_R$)r}m*u;U8Mtaoq$N4ezMi*HUN#Df*LJpbzE5$&=+-G){u(c*O{z6h|m> z^2t*CoN?uzDozL;HTHNc@O=0T)tgjmAJhq^^@pdUN7_nX%^z-_pPSRQu3(-Rk`&?) z#lTWZ>zkO;^vkhlyH!3#%b~{}y*?2A_RN~Wc-=)59c}@>fApPG?BZ8%bzMb-H(7Nb zVoj&td#DYqU&dHMkCZu7?VE6@a#yq*@Gu55&|MG`_pp^`Cf4SjHgqo1t1Q1s^hq8+ zF0}+!8mBcx4ayD%36WK&P=6Dq&$Mr*jm>77?gwdB;vh)NMv>xd`!ThuJ17kD_C*dNZbLRR$sN8=|k|IcY?B&SQvrtj(Ag5Dr)6uw)wZl&i7 z$`}6j;u?QdQz6q}xZ&dV{@;bdeMyB4Kh9N`It5r|h?6XNjuDs?scx>j*#J$T#C#!$ zg`gfv*0_ONhE4cs`34CW@Qf3nrTfE*sw&8VS_W((bwVnm35{<@;fBqiAUyUFILYsW zg9&o*J4DWenK5JNT~$)rRb@jW9XPUzqx?RIJ}K;ToNTtE!?FUKNS0OvS3@G@9Q2a( zuehGmjy|)MlCs@0jm3&ImlB(u$q1#@9-8e~c~1Z#?j> z%S9*x27j$L#l`~nviO^!3?l^00K!6ogX3C;!e*Y*ZhL^+Nemy6h$Ta$MAO$!- zTY^5951%uSoOhYm0TSInaj6_wM2W`O#sQ$x5R)=Ot+{894e$ljFX5L&H4@c-T8fK{ zC;YzlJO{6*eS_NO)3v-8Tcw{$8Uk5I@Wp`nIb^Jvh}VFWHY<9c@Wy-py>s&6AL|I7 zb~wEZ`4+;Y#oWvyjgU~p=(TpX-dSng5V=^OOn;F015}GVb@ku4o~qh{s6#?}B-5L# zFfE^vR*1x62P_)~{>*L7A?~#l@OOSVgcO>99O)XIHB%wMWGxO!Xzj$Dwx+`Avr#c}S#x zDsnP+Xt&dm?m!Qu2ZQYoYD=9I6*_`rJI6J`_`D3wo;Q845GE;YCtw`+Sh=HDJxh8M zx4du8Ve9z5t%^BZAgpFp9>fX}3VOJLb*w!x78-G(NIBD{=$T7k3nlA*n_6#@$Hx&& z@I(ou5_V&>WKN6DFIP7dqD%`6Rf0;Q`DOP0n9T{QTi`l?A;{7>s7iV{$iLdInk-QZ z_ljBfJeq>oxb#|jlZ!%-j9tZV)QsmC)XqhB4%kI}0Qz6$V>A;jJDBVocTRVAi-l%^ zM6F+Q!%6;U!q22$Q14ksRt)()#ug+jPDke-0 z(2?(F5$~mm%5)h=BPJzyqge-M0XFI!fW&AsW5jvWOv`PoPP7ctfVNQL0((`X?b$Bc~dYEL!oGBv5~Dwq3^cNV3k zDW!Ppyaq5{!BCk;2zd&F^rY9&_5pkYVh}q&94=I(~Q<|RkrD>074eg5Cy!4 zP=i5thX%}~g$!k{Fn`st7~i!xdy@?dd3OzzLdD+uAq{mA&cbO4Tg-7p@-KTA8?fr( zy{H?1ar*j?3F`Y00{wk1bH4GB5P~5m=4@Uw3#(z_$@|!Vx_$xqY>)QlWahQOsu>O;c0PR?kQ^vESb{JJ*0VffaKe1sJp}aNe{t#ie{29g;A*hY=g4A$KdQ)JkPB?M5nkW)w;+^+E)m{bXF`nqzKozHhv-nog^q0r`*qb;-8t(0NMQA6U6?)54_6Qk_|pK%-%L@dvmK_=6gyrBJ`87u;8aYiCSG2NEAb`^S3Ck&{hV)6LFog z$5fSP9o667LIRHF2|tHs!?bAshB;($WuHEA>J^?rYdT}V8bTBU?Zb&9v%t1c5r_Es zO(6V2SqiTm1BmHSUa|PILS7WtSOf4E@igxgrkmmg8a`(93bM;O{z4L4gdxb>GKAui9k|_ju41}C0~sb zi-VOW6I0X8lwZvQ1K7qWG=*8OzKzVoZcu&#wr%v9*Cl7jq6o{0J{{+iua$;WufOWKrmnrCTVzx&A^w=k2kfcnb@-^p$j&k` zvQ*>Bm4UvSYT91R(*@$>X+65U7ZVzc-GNn{d#r(LSLIB+%>VhA9j)B}zIq!^Ms1&y zka-RG>^M$yLdQS~Jrh>*1;b#V>|#7)DPeAs_^-KVcym5I`7OM7JZB2*h6uCciUOLT zE|f#i)?)bdO>{e9y<6lDux!rIO$_wWdo>Pm>n|W0NLbZd8OWcnrAtjkhKADZ-#-HZ zCJdF+kgc+>qSW9pKgzx%Tl9z%%t{A4gOqI|C!7y3k>HQlDsi7#PG}XI>WX ze-u59s3MM>7l>w-%Ae31DG)sQScKz3u;KtIXxwi$E=dLQ0Pt7m^5v7gF`DQT1{AF* zElq~56svd2c?q65y;-d{iIG=vav?`eT1V{3z4>Tp#}SR5_QA;OW^nW&W~LI{Pw4e5 zk5pmR&uFd&(mMC&57C}!{VLoQxfI~hD=eyyn{{5Oac41QWFBp%y7;)LPv@SdZx%=} ztM6&LZ3dX*cyO%uxJ-YqKWn2kaCFF$p{AyO1`r z`p4&HW;W$bKDzKZW(Und+1$5=WjpL{JX_z72#&3o2U|U>pnzk@%S%CG zqw7W)ljisCZ9=05nX9YPb8zF)=6*s^$#PbR<9>|RTpLxLomGGA{EKMF0!pC8?ac~j z=9k)u&W(l@vrN9=@(EL1pb}yUY(q_5yFeX{gbN#g4!ad)D4RDLwQL>i zBW`KW#g6X4wY(RRW_;Q~wZtW$@F`@rt2}$iW(R3yb|*if@38zV#UokkY@0 zLpSC_$<0g`T(W>D3dl;cj5#RCz!lIZBm&^RUAnylk2eGzqbL&YYclmI78Mxl96V(B zys2`xXK9Q;%g4J~bw8`m#22Kio$nt`%TG52>VY=Pi!LsOd@X?TBSmk6%t!MPxv0&P zs?gFGJ{^DQhHMo4i+ZqZ^f^Tx0`2LFJ?Ar^U`E!=l zFCBNa?|5@z-^}c6u#&i-paYf(aRlFMt<^SY;v}QZkcWpSC2t*TBc9UcsxbZ=_O3S+ z_hz&%;sC4;C!dDkgo7gq+!@j!&?tm1I_FzLUSEgMpx$xX7N@Rqo7y|Cxu?`}#Y^;6 zzb;kGO$V16oVspeg81k`BpuK65s9iXY;+O8H^=@b!NCW_lOZ2pxycJ{6Y0P+%2ozQ z!R>@}VZ()~FaR$iwBx1D+`;&QebvZ(av#jnXcA+_2><|m4_X+0jg5&TheAJaSj~R) z3&DuZ*Y`6RC!}NO`K8h|Y1X*yVd`J%F?;>l5yv;8XB@^MWBuSngUeG|`^dxyo(w?2 z*AFy)4coxG3QsB8ke9yp{)zsgLl-E2YU0i%D_Xg-ANX}HRo%+_eMH5aJeQYCCaAOQ zRm$y!Nu-l8kk~`6BP?+* zgJZMDF%wFv^RbuL60`)vV`JQb>o;s5cs0;G2d?q&AvyA`7IX697s@+3JJ|x$kfNc( z2ixTk+4?x-LXZ(4Z46LyLbC#xW-pjdRaXc6TEYQMgqe@+5cc23^3vHOMDyXmzJ0pT zd?Ss0uw^(3Gt~et&d<-w%t30je$%G@zCJ=4ft;Ek`fbp+d!h@I=QH|)nXLTgVIqSy zR=A9grI>xR0UsB6!=XlRDZEi#Q-R*va5Z-SQ-F0~uOn~F!q0C9O$Df?v}(z${`&>` z<{q)&KZ(dA4OzBFN6ES zy90j^@tfIj;;g#aCDv@4eg*Q+{L_LYi4UBbSPAlx7rIg2K%kAdj80iVPm~`YP>t@z zJOdb3;=Gk=Dgt5;kn~Ge7j*q-Pe9R(5w|_aA{Fqt_VQ(4b2}dNr8M*wRL2-d*dYgOFH$a(;HK7-O~+ z#h3Vw9C?ZKx?#_N3sAy}>oC7d$cdzsp&I~$tT5(ailC+E=YVUY#!;N-*hesE95Hcm zE$P(wY>j9whuBvjq|_VGwrZvs*I0za3D3p%Y()V){LH>2wW5b7<~GosP68b7xv!H7 zf~RFzn^D!_P)5g#3bS7^Iad$!TYPg>Fa+`h9K+XzS@kuCJyskUhg`WsRBWNIH(Y1|0xw59Afa6D+Bm zVZXNwWS6iz65Cpl=l|s17nNabm*)cJp&;bYuSCqP!@UE|4B(X*v4M<4;NUizuS{g? z3-9gB2uvLSeCBCUwf6H*1|6UK=CAw;F^NRC!R_eJ$T4Jg5|(QTgq$N~hJm@IGoR{I zhz?4SE8q|TXC*!b9w&%e3;_dJf*wRR&OP?#Hrm?kxQ9(O2|fo1FBhQJAfI|3hl|}2 zf?-Z;4$wAnNCE*eH~0e}L&qaD36=*`EO+0p^b&@sV-opNkFJx8Q9eW)yQKQp& zh|`Q+Sa<<}CP>EnkSH6|CnRqbm!512T352NNDn`QTB1Q=?idHASo?~IoLlb0>HWj) z-I@dB{)hu!FHc|GA^-hq1&vpgcp3rWf4`!a0}2!h7s`nPSsv2X))Tm}7&&041UwH- z*5>R8`rz_y<=VLO&P8G(B0A>pUR$pLogxq>|7skaqJa)R^XLmWzBgEixfb+L-ufN{ z5M=)k z$>w_pbEnAS;`+uCZ!5nVx%L>q)ByIAh0OPF*vDqK>1=Hh-B;_U;-T{|$^uzwGS}%= z4BH8jBVRQBdY!kz5h^Vf=H7<#`|Mm?1n5@CZiHHm&zqk&OJ0}pbxOfX%4u-5{ z)a#7h>So7ileS&j*Z_-1vT~art-q5`_OcI?xwa;Ezx>`i+ympKLPYEdw5`LfhyvqpZwF}Rf@gKU5E?EK)!ThgJKM~WDms;nNbz(Mnz4w(mLRuaBn5aWdx2RaN=`G9*ZwfnIC6h$w zulIp*RnEN%pNeQp-(UJ*-pZXSE$pusn_YEPfw-ni#5FmlHH-I)FEoefmObGU8ga|1 zA`5bVaN=s?q0iFr&^Xmz!jvNOIw4J6J)L#`U+V7=+p^k#Y+_^3r(V+}^DP=YN*CzR zuvjoEVDP6;*w5XezyHLItnEA3W_dHO(K_V6VR5`CPU%*%v$JKr|I*T9_CF!(Ez|#g zo~OZj?JU(zY)wNS2UNU@WL`TQd#M^@MoXDEJw#V1ap`q>tJ}MIhVMIPWv!bv_VvYO z`B(oH%9V6G(q_8laZM>|ZYWyPE$u264vWxMUNKB&8=fr9{0hS&t}Nze%(VYeNQ$VsUlBJn{m3Z#Z`49x^$GDlwZQhFPU*6rF{kP6`ak=8 ziEh+@p8%uH4!(^V^sNk}KN|=f@%y`LALO5mnc1~8AE`@~Yj=+S9&DmZTUQiNYX4Ty z?DB}ZMuB7^^MmyylGi^gsc3<5EWFFORO~j~uwgcn!f4SD(8_l+4NEXoh)7Icu%h>2 zR`0-k_i4sRx0o>M<@>2}NuXwt>(-^!Vz_ z6Y@kV7oJF)zjZz{^Z1KcsC7YUr54d-()71KppjE|ZMJCh@`x4ZYmXv6BY~M_{BHTp zig|bs`Jf!-S*7CN${ilYNoULjOhZt~{a0s!&UN?k51V;7KCk|Kjt)8=p8TsM$y-Fc?!P!n&6rwR0EtFb-FONlSp^dO@zm{&w&>FHaV#u=_@l~RT2)}f! zcELV1#s$!{6Ej37Camr1Epl+6&%sqA_!#jik%S=499!m;5{gKHHW9EG9M-R0PP9%3 z$wK;mlnC?n@vS!@2#AOMGo%KI$H0JK03Uub;I&xh6+d%Sz`HgHRfCB#l`5tnv=qKiCfS7}8)TGmWCY5*90s?YV-lezR6zs5Vgxw$z3dQg<%1TNGe01tUXal(g1rQkd09917xPxr~ z%QcZ~vpN?o1A|f#gJOA06)O@N=t9k|TnYJfZJ?td2W1fQVHAxJ`?w5^jivh0S-)Zk zes6ujTb5lb`OB3zz?iWlL0Z9p`y9M4H|}77jp&J6fO$HAwW)kZ|3uLH33I4BV$w3T zXKO#IxG5`t2FUr0f&8j*0B{-Tdwp`E77u^V`5g^8m|Td_mBf67aCP4thBp#r-KSfOG^WUk7DB!V zAS8u_O}i6JfcnCbyA}{Z^mRbx@yf58v_XYKAfOFnHkjD(d4J-Sd~Jml+OP&jSl|oGj0n17(0W`ZS?29CK;Z)p5QLHkiau;*0FwV~-Ky%K z<|KS(OxAkl><$^?L)gvw3mxtGh;{=2EXmMtT05f%PSQwZ$hpkXkO&J#Exo7of`or6 z(FDN7r3YhP)Hrw;bs_aUVKpjplD=>lV0;AIP)|<)_P!a>hd^43$kvo%2^v{L-V8OQ zCBW6gCkQ63uR4TaKm!tP7gDLt>_0l$ks3)pLTdEFYULI0!6vVB6n2JW4eiU9b8%Tj zb7~IoGy)Xtun6`b{2SoB4si*O!>EqkGZihZHdV}OAaur;)4n@DL<>ZReY-jC%3@>G zG88nH80cCAmY5Pp7}93-l-&U%#m-KIL)3Z2#P#t9kmFtZ`uZRVjsVvn2h^^aLiip` zVm@Wn6BySiV$S#RlmT$)}Evqe-Jc)pLPffe7!j3&J6+ND7Q0 zibKsYD0#%Idm}sAw}6#iL?Z?UMzp{an9OA~Gupv^i_mC6LyQTF5EX#}a3o~g==)$| zT0z$b5djwTsRAvxI^2He`B$!if$<$QSU_DokqoZd8Ry2P_3Pon04sx&+}w(BjqrS( zfXGQJ&)Nt=G$4kKj*eOPywOGkiU(X*b4nc`6+}|F&SYNZAj}X@gz4WN;cuC+o`)1H z*+Nh?VhkQH*gmk8=}87Afg-v|SkHm&$N7J-%K!Ph$^j0Dh!mpsM@w#8V{cDSe1{(H z)(o6iF&sKSI{M?a^x>{z{T&!?2l^ZQh1!TvlEqU$56bQDa3$~0h*#@XDVc76;MWMm z1v@HcC%@*b)GZuVgPv{UCm@!Ku&eFy#o1NhkQ#pU19-}(g-Q!8<(MoU!`A<^5@0(FN|31|+Hy(Bo2&&w#PH#{%p0E1fp;s~)(E%Y4kcWzySwhA2c}R-T?5+h{!ic1pBDjCn8q*0&xs{-}(2nN|5(m zT3XuvK;PA58s_@g|7ap<1v3TRCQ49p^9G;{6Xzll8wccA zRAH$aeo{ZNm$j#Z;z@5Otb!Ujz*bCC*1UC6_-Q7TW(`re(b(+xr>6DlYmbcQ!NJ6Q zdx2Y>9NfeqPcTk)mrKPE-%mt3zRI3QP1%oTjz1!7NMs}oUl<~rJ`|mqIR)2=gCdU5 z8=zj-dh3#h9tngmm|$R#k&#g^&MR!z{jWdAJCxVe>1P1hp zkbiJKe$Stu!R#COW7=W(M;-L#`#6sunP^}7xzK%m6`ZpF9q*>e_%m9HQ4WStq zWM94eL@`i*&@4+UtOmxjNmj#8hMf&D*t``Ywq{%y2=rm3A#mgl=P{HtGaEqkg{cD~ zM;3BF!A{2ySDt;kMjRh9O@s7_&2*S`U|Rnor;`X$L<3pCF~E&E9roHdW)a2+>o0=G zmLXul&cFk38CJxN0v<<4L-Q(VPAW^~B_Eq;(u_}1TPL-9?+K?_!X0KU-03J57NanD zRIebE-Xm?(h<8*(GcpV=hHtqoU^YY}A}5?AbAcFX5=9T6q(&k?2gfiBPa4lO(5BSYX|M)DXOM6Jc?~QG_dkjG3&5#30yxKM415#8 z0T?WFszgU7pLZ4;^#N*FXjSh$c!1%~Z{y?34NgH_r%X}W5Q$Wcd;>00NVbqv5UC$z z#$roTstMw}yegcNzRObQfiNOgQ^bv1BBLggg%qq9L$_Ue5ZfRKpf<`FuK+aS1KHi3QZ4$s7cRqb(Q#%DBD%Y+`Fdab3e ze;VhRGk?x?Dg1Oj$o-D2`1NjE#U~;$cQ}YDB$sQSuS4%1G+-uMMVmF9JM;<2mM6j= z;_SrHQ>T==FE^=j-hI_FRAZc>rmdzH9r+4#bYE8fvK30WLKm0=-E^;R0_G`%s%xw7 zXb>cyj8oILQf=3^3s+fk$k1Rnr`@)Zl|?@$s$tDQpw`vnckH>seUI__ynlZ#L5JBh z`XTkXja#^L*^n!9@d;X@d(cLIP+Y@z-IGA~CyP0Li}YM8QUZRyCUHw!fXF7!l5Ot660Ew#uEvoOOenkz{0cj_2!Fj0xBi za8xssEFvMF03ZjF>A&CP$;AKkgUVs6FdQugFH!#-rcUd+{H9{SR&5 zcw5)oz1h?cW=4eR1iH}(KaIsw0VeLjg$Z9EFg*K+zqY*l`>QpnIK)4sH#JFGKbOBs z!_>=b${4s64vDn@$G!_HDH-4HUwA6`C8DM#q5iq;M3hEV67l_#`{&MWDoMpu&(W>y zjXRF-HG3-LmD6dJ^wUZl|@&5U%_t}C1|K#O=io`NdJc@1JdzfN;t)% zSnvD@%QUhg(`lAKCvh}c^m%CK! zu6yfWy6^CG-`lW3`S4mssr7XC*3!Ik-?I6$@&(%fi4eEL_x{zEerRf{vg=}H^`B9j zA5WfL_Y&y_G3={9GP(E5pFg5qbKXTaYL;gTdYpI;Zj-HI`yh6bqA1=r-;->w;mA!Q z%{-CV^mTMPr*p1zY*|%dQY%OO#>j8|=}psuD^4?g`dvk3L;UjH|1OvCM!*_@Qe>b1Qw)wor3 zdF!dFi45r@gw@2?7e6>jqm{BzgDZ1cCa#ksagmNaF9a%X-0wm8{oRA5e$A*>oVwfe zlV{5#aSpD3n#*=-d$nbz^}uVRWRvuZb>Ci>EsTIi@XT>ttd@-`x>8ly_U%;yRsz$m zyE}fn&3#kt5On?RDek(`UDq-Ch2_)25HnW&Z}-i%?hD^RAyY8{ zZ$n*9{!UwYd%P9PiuC9c-T#i~5ZlX%k3)J@KItM5w(law!{e?LbfkC(8F4Ehr?)*9yepf8)80Fhi8rPB-CID91+%5R=$T49b@Kypd-93(1f_Kok zOzX3$Z9#`dq8(_=)}HPQqA(`6btVubcK~#P2u|+i1qW z6mJ#LdNW8*#%O>?eg8!(YipGla@MDSM06H6HJS3Y#q7Ki6jP~}8cdgz@5^Q7;_?ku ztH;@9FI!K1o-O%sk4lNnXvj+-K9bGd)xG-9i)M;0;|0~4i&{C6a*_Sr<9~krir4Bk z7K_q1kJd|#`?r@W6c+QPVIaX3d}Vv`{(=O~wGq*eeYj*zVr*b+@12v8C$YNyFYrgz z0bR|yLDRB-PS)8hBsr<-4w~yIPd*EVr3NY}@@uwmrf^EmA8)2s)31%SwV<}9g^%~O z=jj|?4I}4572+o676%h@=Mb=p`ftF?71*QGV5A$&RQE?dNu= zm7#tX($tPmt_u|Cs+IZsc^;jpdh^bpe#Aa;oL|#R@-;*%a)Ub`9*cS%R~io1$u znw^W@(oCtnHV(QSlG

7sSq#8XwehC4{qCD_6(AR=0JDGBlFJp7wx<(Ys3L=A4{i zw680;kib#(*|cfEHl@RRkoxK*O#P@`M*Z<>L7YhpFp?U`*W9$;Jh|0bcxcP&Q6NET zz8pA}dt_M(KN{ITH1k-i^(vbvdQ#!fI?Qd+pzF(0fT@CkDsqB0sVi-cNSsO{4{{~-umtwL6RGQhU{)c{W@KBe(S-(!oJ8HyxJEN` zo@JvUm&6##ou6$-za`Lx$gw26Ybt+(hHjWD^u|Zcog2KK+S0ycFL2=UdEyxQ=S660 zz#&az5#7kLfPSaMW-URHxNNDbv0Dd{BwzbR#{Sf0(dOh2t8pzBr}GN(!Zr>Jv{GRT zVn3>xIEfy(q@=WrX}sOR)m8}+9qOs;WuoPRd@37+cFvz`p;RY4u;*3{Kek9J|^d&Csf9H8Q7e!h}ijcf8wcELZ RD2qta7Zom~oHO|M{{T7X5Ox3n literal 0 HcmV?d00001 diff --git a/docs/src/main/asciidoc/images/security-bearer-token-authorization-mechanism-2.png b/docs/src/main/asciidoc/images/security-bearer-token-authorization-mechanism-2.png new file mode 100644 index 0000000000000000000000000000000000000000..ea8cfdeed41d4cd7cde4b5d383395ecf8b1b14bc GIT binary patch literal 72522 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 literal 0 HcmV?d00001 diff --git a/docs/src/main/asciidoc/security-openid-connect.adoc b/docs/src/main/asciidoc/security-openid-connect.adoc index ddb149202842e..da213cac4dece 100644 --- a/docs/src/main/asciidoc/security-openid-connect.adoc +++ b/docs/src/main/asciidoc/security-openid-connect.adoc @@ -11,10 +11,16 @@ include::./attributes.adoc[] You can use the Quarkus OpenID Connect (OIDC) extension to secure your JAX-RS applications using Bearer Token Authorization. The Bearer Tokens are issued by OIDC and OAuth 2.0 compliant authorization servers, such as https://www.keycloak.org[Keycloak]. -Bearer Token authorization authorizes HTTP requests based on the existence and validity of a Bearer Token. -The Bearer Token provides information about determining the subject of the call and whether or not an HTTP resource is accessible. +Bearer Token authorization is the process of authorizing HTTP requests based on the existence and validity of a Bearer Token. +The Bearer Token provides information about the subject of the call which is used to determine whether or not an HTTP resource can be accessed. -If you want to authenticate and authorize the users using OpenID Connect Authorization Code Flow, see xref:security-openid-connect-web-authentication.adoc[Using OpenID Connect to Protect Web Applications]. +.Bearer Token Authorization mechanism in Quarkus +image::security-bearer-token-authorization-mechanism-1.png[alt=Bearer Token Authorization, align=center] + +.Bearer Token Authorization mechanism with Client in Quarkus +image::security-bearer-token-authorization-mechanism-2.png[alt=Bearer Token Authorization, align=center] + +If you need to authenticate and authorize the users using OpenID Connect Authorization Code Flow, see xref:security-openid-connect-web-authentication.adoc[Using OpenID Connect to Protect Web Applications]. Also, if you use Keycloak and Bearer Tokens, see xref:security-keycloak-authorization.adoc[Using Keycloak to Centralize Authorization]. For information about how to support multiple tenants, see xref:security-openid-connect-multitenancy.adoc[Using OpenID Connect Multi-Tenancy]. From 8223153226a0c646bec655e006a0f26f64013416 Mon Sep 17 00:00:00 2001 From: hmanwani-rh Date: Fri, 9 Sep 2022 20:16:10 +0530 Subject: [PATCH 18/36] Added descriptions for diagrams --- .../asciidoc/security-openid-connect.adoc | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/docs/src/main/asciidoc/security-openid-connect.adoc b/docs/src/main/asciidoc/security-openid-connect.adoc index da213cac4dece..1d4f1adbca9e1 100644 --- a/docs/src/main/asciidoc/security-openid-connect.adoc +++ b/docs/src/main/asciidoc/security-openid-connect.adoc @@ -11,15 +11,33 @@ include::./attributes.adoc[] You can use the Quarkus OpenID Connect (OIDC) extension to secure your JAX-RS applications using Bearer Token Authorization. The Bearer Tokens are issued by OIDC and OAuth 2.0 compliant authorization servers, such as https://www.keycloak.org[Keycloak]. -Bearer Token authorization is the process of authorizing HTTP requests based on the existence and validity of a Bearer Token. +Bearer Token Authorization is the process of authorizing HTTP requests based on the existence and validity of a Bearer Token. The Bearer Token provides information about the subject of the call which is used to determine whether or not an HTTP resource can be accessed. +The following figures outline the Bearer Token Authorization mechanism in Quarkus: + .Bearer Token Authorization mechanism in Quarkus image::security-bearer-token-authorization-mechanism-1.png[alt=Bearer Token Authorization, align=center] +In the previous figure: + +. First, the Quarkus service retrieves verification keys from the OpenID Connect provider. +. The Quarkus user accesses the single-page application. +. The single-page application uses Authorization Code Flow to authenticate the user and retrieve tokens from the OpenID Connect provider. +. The single-page application uses the access token to retrieve the service data from the Quarkus service. +. The Quarkus service verifies the bearer access token and returns data to the single-page application. +. The single-page application returns the same data to the Quarkus user. + .Bearer Token Authorization mechanism with Client in Quarkus image::security-bearer-token-authorization-mechanism-2.png[alt=Bearer Token Authorization, align=center] +In the previous figure: + +. First, the Quarkus service retrieves verification keys from the OpenID Connect provider. +. The Client uses provided credentials to retrieve the access token from the OpenID Connect provider. +. The Client uses the access token to retrieve the service data from the Quarkus service. +. The Quarkus service verifies the bearer access token and returns data to the Client. + If you need to authenticate and authorize the users using OpenID Connect Authorization Code Flow, see xref:security-openid-connect-web-authentication.adoc[Using OpenID Connect to Protect Web Applications]. Also, if you use Keycloak and Bearer Tokens, see xref:security-keycloak-authorization.adoc[Using Keycloak to Centralize Authorization]. From fa7319e56e31482c16fa12d5ffb6e5662423c188 Mon Sep 17 00:00:00 2001 From: hmanwani-rh Date: Wed, 14 Sep 2022 18:46:19 +0530 Subject: [PATCH 19/36] Incorporated SME suggestions --- .../asciidoc/security-openid-connect.adoc | 30 +++++++++---------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/docs/src/main/asciidoc/security-openid-connect.adoc b/docs/src/main/asciidoc/security-openid-connect.adoc index 1d4f1adbca9e1..2f82bf23fb744 100644 --- a/docs/src/main/asciidoc/security-openid-connect.adoc +++ b/docs/src/main/asciidoc/security-openid-connect.adoc @@ -14,29 +14,29 @@ The Bearer Tokens are issued by OIDC and OAuth 2.0 compliant authorization serve Bearer Token Authorization is the process of authorizing HTTP requests based on the existence and validity of a Bearer Token. The Bearer Token provides information about the subject of the call which is used to determine whether or not an HTTP resource can be accessed. -The following figures outline the Bearer Token Authorization mechanism in Quarkus: +The following diagrams outline the Bearer Token Authorization mechanism in Quarkus: -.Bearer Token Authorization mechanism in Quarkus +.Bearer Token Authorization mechanism in Quarkus with Single-page application image::security-bearer-token-authorization-mechanism-1.png[alt=Bearer Token Authorization, align=center] -In the previous figure: +In the previous diagram: -. First, the Quarkus service retrieves verification keys from the OpenID Connect provider. -. The Quarkus user accesses the single-page application. -. The single-page application uses Authorization Code Flow to authenticate the user and retrieve tokens from the OpenID Connect provider. -. The single-page application uses the access token to retrieve the service data from the Quarkus service. -. The Quarkus service verifies the bearer access token and returns data to the single-page application. -. The single-page application returns the same data to the Quarkus user. +1. The Quarkus service retrieves verification keys from the OpenID Connect provider. The verification keys are used to verify the bearer access token signatures. +2. The Quarkus user accesses the Single-page application. +3. The Single-page application uses Authorization Code Flow to authenticate the user and retrieve tokens from the OpenID Connect provider. +4. The Single-page application uses the access token to retrieve the service data from the Quarkus service. +5. The Quarkus service verifies the bearer access token signature using the verification keys, checks the token expiry date and other claims, allows the request to proceed if the token is valid, and returns the service response to the Single-page application. +6. The Single-page application returns the same data to the Quarkus user. -.Bearer Token Authorization mechanism with Client in Quarkus +.Bearer Token Authorization mechanism in Quarkus with Java or command line client image::security-bearer-token-authorization-mechanism-2.png[alt=Bearer Token Authorization, align=center] -In the previous figure: +In the previous diagram: -. First, the Quarkus service retrieves verification keys from the OpenID Connect provider. -. The Client uses provided credentials to retrieve the access token from the OpenID Connect provider. -. The Client uses the access token to retrieve the service data from the Quarkus service. -. The Quarkus service verifies the bearer access token and returns data to the Client. +1. The Quarkus service retrieves verification keys from the OpenID Connect provider. The verification keys are used to verify the bearer access token signatures. +2. The Client uses `client_credentials` that requires client ID and secret or password grant, which also requires client ID, secret, user name, and password to retrieve the access token from the OpenID Connect provider. +3. The Client uses the access token to retrieve the service data from the Quarkus service. +4. The Quarkus service verifies the bearer access token signature using the verification keys, checks the token expiry date and other claims, allows the request to proceed if the token is valid, and returns the service response to the Client. If you need to authenticate and authorize the users using OpenID Connect Authorization Code Flow, see xref:security-openid-connect-web-authentication.adoc[Using OpenID Connect to Protect Web Applications]. Also, if you use Keycloak and Bearer Tokens, see xref:security-keycloak-authorization.adoc[Using Keycloak to Centralize Authorization]. From 20bbc7f011b117efda2f3f795b17fd396d723f75 Mon Sep 17 00:00:00 2001 From: hmanwani-rh Date: Thu, 15 Sep 2022 15:08:07 +0530 Subject: [PATCH 20/36] graphic size changes --- docs/src/main/asciidoc/security-openid-connect.adoc | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/docs/src/main/asciidoc/security-openid-connect.adoc b/docs/src/main/asciidoc/security-openid-connect.adoc index 2f82bf23fb744..af0f39765b706 100644 --- a/docs/src/main/asciidoc/security-openid-connect.adoc +++ b/docs/src/main/asciidoc/security-openid-connect.adoc @@ -17,9 +17,7 @@ The Bearer Token provides information about the subject of the call which is use The following diagrams outline the Bearer Token Authorization mechanism in Quarkus: .Bearer Token Authorization mechanism in Quarkus with Single-page application -image::security-bearer-token-authorization-mechanism-1.png[alt=Bearer Token Authorization, align=center] - -In the previous diagram: +image::security-bearer-token-authorization-mechanism-1.png[alt=Bearer Token Authorization, width="60%", align=center] 1. The Quarkus service retrieves verification keys from the OpenID Connect provider. The verification keys are used to verify the bearer access token signatures. 2. The Quarkus user accesses the Single-page application. @@ -29,9 +27,7 @@ In the previous diagram: 6. The Single-page application returns the same data to the Quarkus user. .Bearer Token Authorization mechanism in Quarkus with Java or command line client -image::security-bearer-token-authorization-mechanism-2.png[alt=Bearer Token Authorization, align=center] - -In the previous diagram: +image::security-bearer-token-authorization-mechanism-2.png[alt=Bearer Token Authorization, width="60%", align=center] 1. The Quarkus service retrieves verification keys from the OpenID Connect provider. The verification keys are used to verify the bearer access token signatures. 2. The Client uses `client_credentials` that requires client ID and secret or password grant, which also requires client ID, secret, user name, and password to retrieve the access token from the OpenID Connect provider. From 6382413496aa207f1c8859584716a20e9f02b172 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Wed, 14 Sep 2022 10:45:56 +0200 Subject: [PATCH 21/36] Jakarta - Bump wildfly-elytron to 2.0.0.Final --- jakarta/rewrite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jakarta/rewrite.yml b/jakarta/rewrite.yml index 8b37a399dfdc6..4aa04a0531e1d 100644 --- a/jakarta/rewrite.yml +++ b/jakarta/rewrite.yml @@ -183,7 +183,7 @@ recipeList: # WildFly Security - org.openrewrite.maven.ChangePropertyValue: key: wildfly-elytron.version - newValue: 2.0.0.Beta3 + newValue: 2.0.0.Final --- type: specs.openrewrite.org/v1beta/recipe name: io.quarkus.jakarta-jaxrs-jaxb From bee662a70147cd9a0d7ab4003203327b9685daa3 Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 15 Sep 2022 12:32:34 +0200 Subject: [PATCH 22/36] Jakarta - Fix the Parsson situation --- build-parent/pom.xml | 1 + .../deployment/pom.xml | 15 ----------- .../resteasy-reactive/server/jsonb/pom.xml | 1 + .../reactive-messaging-amqp/pom.xml | 18 ------------- jakarta/rewrite.yml | 25 +++++++++++++++++++ jakarta/transform.sh | 5 ++-- 6 files changed, 30 insertions(+), 35 deletions(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 2a9d7f36089c1..ea7c0e5e4a45e 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -1274,6 +1274,7 @@ io.quarkus.build-parent + io.quarkus.jakarta-json-cleanup diff --git a/extensions/smallrye-reactive-messaging-amqp/deployment/pom.xml b/extensions/smallrye-reactive-messaging-amqp/deployment/pom.xml index 60bd5b514e3af..ecd53ab12ed09 100644 --- a/extensions/smallrye-reactive-messaging-amqp/deployment/pom.xml +++ b/extensions/smallrye-reactive-messaging-amqp/deployment/pom.xml @@ -47,20 +47,6 @@ org.apache.activemq artemis-server test - - - org.jboss.logmanager - jboss-logmanager - - - commons-logging - commons-logging - - - jakarta.json - jakarta.json-api - - org.testcontainers @@ -123,5 +109,4 @@ - diff --git a/independent-projects/resteasy-reactive/server/jsonb/pom.xml b/independent-projects/resteasy-reactive/server/jsonb/pom.xml index 5213908b714b5..61859d2038764 100644 --- a/independent-projects/resteasy-reactive/server/jsonb/pom.xml +++ b/independent-projects/resteasy-reactive/server/jsonb/pom.xml @@ -111,6 +111,7 @@ io.quarkus.jakarta-jaxb-switch + io.quarkus.jakarta-json-cleanup diff --git a/integration-tests/reactive-messaging-amqp/pom.xml b/integration-tests/reactive-messaging-amqp/pom.xml index da8bdcb487de4..2ccd4e67e7cf8 100644 --- a/integration-tests/reactive-messaging-amqp/pom.xml +++ b/integration-tests/reactive-messaging-amqp/pom.xml @@ -59,24 +59,6 @@ org.apache.activemq artemis-server test - - - org.jboss.logmanager - jboss-logmanager - - - commons-logging - commons-logging - - - org.apache.johnzon - johnzon-core - - - jakarta.json - jakarta.json-api - - org.apache.activemq diff --git a/jakarta/rewrite.yml b/jakarta/rewrite.yml index 4aa04a0531e1d..98c4e0c465eac 100644 --- a/jakarta/rewrite.yml +++ b/jakarta/rewrite.yml @@ -316,6 +316,31 @@ recipeList: artifactId: resteasy-json-p-provider exclusionGroupId: jakarta.json exclusionArtifactId: jakarta.json-api + - org.openrewrite.maven.RemoveExclusion: + groupId: org.apache.activemq + artifactId: artemis-server + exclusionGroupId: jakarta.json + exclusionArtifactId: jakarta.json-api + - org.openrewrite.maven.RemoveExclusion: + groupId: org.apache.activemq + artifactId: artemis-amqp-protocol + exclusionGroupId: jakarta.json + exclusionArtifactId: jakarta.json-api + - org.openrewrite.maven.RemoveExclusion: + groupId: org.eclipse + artifactId: yasson + exclusionGroupId: jakarta.json + exclusionArtifactId: jakarta.json-api + - org.openrewrite.maven.RemoveExclusion: + groupId: org.eclipse + artifactId: yasson + exclusionGroupId: org.glassfish + exclusionArtifactId: jakarta.json + - org.openrewrite.maven.RemoveExclusion: + groupId: jakarta.json.bind + artifactId: jakarta.json.bind-api + exclusionGroupId: jakarta.json + exclusionArtifactId: jakarta.json-api --- type: specs.openrewrite.org/v1beta/recipe name: io.quarkus.jakarta-json diff --git a/jakarta/transform.sh b/jakarta/transform.sh index cfad1b3fbd6bc..af6ba984ead8e 100755 --- a/jakarta/transform.sh +++ b/jakarta/transform.sh @@ -230,8 +230,9 @@ sed -i 's@com.sun.xml.bind.v2.ContextFactory@org.glassfish.jaxb.runtime.v2.Conte sed -i '/com.sun.xml.internal.bind.v2.ContextFactory/d' extensions/jaxb/deployment/src/main/java/io/quarkus/jaxb/deployment/JaxbProcessor.java ## JSON-P implementation switch -sed -i 's@org.glassfish:jakarta.json@org.eclipse.parsson:jakarta.json@g' extensions/logging-json/runtime/pom.xml -sed -i 's@org.glassfish:jakarta.json@org.eclipse.parsson:jakarta.json@g' extensions/jsonp/runtime/pom.xml +sed -i 's@org.glassfish:jakarta.json@org.eclipse.parsson:parsson\n jakarta.json:jakarta.json-api@g' extensions/logging-json/runtime/pom.xml +sed -i 's@org.glassfish:jakarta.json@org.eclipse.parsson:parsson@g' extensions/jsonp/runtime/pom.xml +sed -i 's@org.glassfish:javax.json@org.glassfish:javax.json\n org.glassfish:jakarta.json\n org.eclipse.parsson:jakarta.json@g' extensions/jsonp/runtime/pom.xml sed -i 's@import org.glassfish.json.JsonProviderImpl;@import org.eclipse.parsson.JsonProviderImpl;@g' extensions/jsonp/deployment/src/main/java/io/quarkus/jsonp/deployment/JsonpProcessor.java ## cleanup phase - needs to be done once everything has been rewritten From 7270dc661fdf834fc3dcb0e4a2dad79b0c3fb8dc Mon Sep 17 00:00:00 2001 From: anavarr Date: Thu, 8 Sep 2022 18:34:13 +0200 Subject: [PATCH 23/36] wrote the first version of the guide for writing REST microservices using virtual threads MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Georgios Andrianakis and Clément Escoffier --- docs/src/main/asciidoc/virtual-threads.adoc | 521 ++++++++++++++++++++ 1 file changed, 521 insertions(+) create mode 100644 docs/src/main/asciidoc/virtual-threads.adoc diff --git a/docs/src/main/asciidoc/virtual-threads.adoc b/docs/src/main/asciidoc/virtual-threads.adoc new file mode 100644 index 0000000000000..e55f24476308e --- /dev/null +++ b/docs/src/main/asciidoc/virtual-threads.adoc @@ -0,0 +1,521 @@ +//// +This guide is maintained in the main Quarkus repository +and pull requests should be submitted there: +https://github.com/quarkusio/quarkus/tree/main/docs/src/main/asciidoc +//// += Writing simpler reactive REST services with Quarkus Virtual Thread support + +include::./attributes.adoc[] +:resteasy-reactive-api: https://javadoc.io/doc/io.quarkus.resteasy.reactive/resteasy-reactive/{quarkus-version} +:resteasy-reactive-common-api: https://javadoc.io/doc/io.quarkus.resteasy.reactive/resteasy-reactive-common/{quarkus-version} +:runonvthread: https://javadoc.io/doc/io.smallrye.common/smallrye-common-annotation/latest/io/smallrye/common/annotation/RunOnVirtualThread.html +:blockingannotation: https://javadoc.io/doc/io.smallrye.common/smallrye-common-annotation/latest/io/smallrye/common/annotation/Blocking.html +:vthreadjep: https://openjdk.org/jeps/425 +:thread: https://docs.oracle.com/en/java/javase/18/docs/api/java.base/java/lang/Thread.html +:mutiny-vertx-sql: https://smallrye.io/smallrye-mutiny-vertx-bindings/2.26.0/apidocs/io/vertx/mutiny/sqlclient/package-summary.html +:pgsql-driver: https://javadoc.io/doc/org.postgresql/postgresql/latest/index.html + +This guide explains how to benefit from Java 19 virtual threads when writing REST services in Quarkus. + +[TIP] +==== +This is the reference guide for using virtual threads to write reactive REST services. +Please refer to the xref:rest-json.adoc[Writing JSON REST services guides] for a lightweight introduction to reactive REST +services and to the xref:resteasy-reactive.adoc[Writing REST Services with RESTEasy Reactive] guide for a detailed presentation. +==== + +== What are virtual threads ? + +=== Terminology +OS thread:: +A "thread-like" data-structure managed by the Operating System. + +Platform thread:: +Up until Java 19, every instance of the link:{thread}[Thread] class was a platform thread, that is, a wrapper around an OS thread. +Creating a platform threads creates an OS thread, blocking a platform thread blocks an OS thread. + +Virtual thread:: +Lightweight, JVM-managed threads. They extend the link:{thread}[Thread] class but are not tied to one specific OS thread. +Thus, scheduling virtual threads is the responsibility of the JVM. + +Carrier thread:: +A platform thread used to execute a virtual thread is called a carrier. +This isn't a class distinct from link:{Thread}[Thread] or VirtualThread but rather a functional denomination. + +=== Differences between virtual threads and platform threads +We will give a brief overview of the topic here, please refer to the link:{vthreadjep}[JEP 425] for more information. + +Virtual threads are a feature available since Java 19 aiming at providing a cheap alternative to platform threads for I/O-bound workloads. + +Until now, platform threads were the concurrency unit of the JVM. +They are a wrapper over OS structures. +This means that creating a Java platform thread actually results in creating a "thread-like" structure in your operating system. + +Virtual threads on the other hand are managed by the JVM. In order to be executed, they need to be mounted on a platform thread +(which acts as a carrier to that virtual thread). +As such, they have been designed to offer the following characteristics: + +Lightweight :: Virtual threads occupy less space than platform threads in memory. +Hence, it becomes possible to use more virtual threads than platform threads simultaneously without blowing up the heap. +By default, platform threads are created with a stack of about 1 MB where virtual threads stack is "pay-as-you-go". +You can find these numbers along with other motivations for virtual threads in this presentation given by the lead developer of project Loom: https://youtu.be/lIq-x_iI-kc?t=543. + +Cheap to create:: Creating a platform thread in Java takes time. +Currently, techniques such as pooling where threads are created once then reused are strongly encouraged to minimize the +time lost in starting them (as well as limiting the maximum number of threads to keep memory consumption low). +Virtual threads are supposed to be disposable entities that we create when we need them, +it is discouraged to pool them or to reuse them for different tasks. + +Cheap to block:: When performing blocking I/O, the underlying OS thread wrapped by the Java platform thread is put in a +wait queue and a context switch occurs to load a new thread context onto the CPU core. This operation takes time. +Since virtual threads are managed by the JVM, no underlying OS thread is blocked when they perform a blocking operation. +Their state is simply stored in the heap and another Virtual thread is executed on the same Java platform thread. + +=== Virtual threads are useful for I/O-bound workloads only +We now know that we can create way more virtual threads than platform threads. One could be tempted to use virtual threads +to perform long computations (CPU-bound workload). +This is useless if not counterproductive. +CPU-bound doesn't consist in quickly swapping threads while they need to wait for the completion of an I/O but in leaving +them attached to a CPU-core to actually compute something. +In this scenario, it is useless to have thousands of threads if we have tens of CPU-cores, virtual threads won't enhance +the performance of CPU-bound workloads. + + +== Bringing virtual threads to reactive REST services +Since virtual threads are disposable entities, the fundamental idea of quarkus-loom is to offload the execution of an +endpoint handler on a new virtual thread instead of running it on an event-loop (in the case of RESTeasy-reactive) or a +platform worker thread. + +To do so, it suffices to add the link:{runonvthread}[@RunOnVirtualThread] annotation to the endpoint. +If the JDK is compatible (Java 19 or later versions) then the endpoint will be offloaded to a virtual thread. +It will then be possible to perform blocking operations without blocking the platform thread upon which the virtual +thread is mounted. + +This annotation can only be used in conjunction with endpoints annotated with link:{blockingannotation}[@Blocking] or +considered blocking because of their signature. +You can visit xref:resteasy-reactive.adoc#execution-model-blocking-non-blocking[Execution model, blocking, non-blocking] +for more information. + +=== Getting started + +Add the following import to your build file: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.quarkus + quarkus-resteasy-reactive + +---- + +[source,gradle,role="secondary asciidoc-tabs-target-sync-gradle"] +.build.gradle +---- +implementation("io.quarkus:quarkus-resteasy-reactive") +---- + +You also need to make sure that you are using the version 19 of Java, this can be enforced in your pom.xml file with the following: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + 19 + 19 + +---- + +Virtual threads are still an experimental feature, you need to start your application with the `--enable-preview` flag: + +[source, bash] +---- +java --enable-preview -jar target/quarkus-app/quarkus-run.jar +---- + +The example below shows the differences between three endpoints, all of them querying a fortune in the database then +returning it to the client. + +- the first one uses the traditional blocking style, it is considered blocking due to its signature. +- the second one uses Mutiny reactive streams in a declarative style, it is considered non-blocking due to its signature. +- the third one uses Mutiny reactive streams in a synchronous way, since it doesn't return a "reactive type" it is +considered blocking and the link:{runonvthread}[@RunOnVirtualThread] annotation can be used. + +When using Mutiny, alternative "xAndAwait" methods are provided to be used with virtual threads. +They ensure that waiting for the completion of the I/O will not "pin" the carrier thread and deteriorate performance. +Pinning is a phenomenon that we describe in xref:Pinning cases[this section]. + + +In other words, the mutiny environment is a safe environment for virtual threads. +The guarantees offered by Mutiny are detailed later. + +[source,java] +---- +package org.acme.rest; + +import org.acme.fortune.model.Fortune; +import org.acme.fortune.repository.FortuneRepository; +import io.smallrye.common.annotation.RunOnVirtualThread; +import io.smallrye.mutiny.Uni; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import java.util.List; +import java.util.Random; + + +@Path("") +public class FortuneResource { + + @GET + @Path("/blocking") + public Fortune blocking() { + var list = repository.findAllBlocking(); + return pickOne(list); + } + + @GET + @Path("/reactive") + public Uni reactive() { + return repository.findAllAsync() + .map(this::pickOne); + } + + @GET + @Path("/virtual") + @RunOnVirtualThread + public Fortune virtualThread() { + var list = repository.findAllAsyncAndAwait(); + return pickOne(list); + } + +} +---- + +=== Simplifying complex logic +The previous example is trivial and doesn't capture how imperative style can simplify complex reactive operations. +Below is a more complex example. +The endpoints must now fetch all the fortunes in the database, then append a quote to each fortune before finally returning +the result to the client. + + + +[source,java] +---- +package org.acme.rest; + +import org.acme.fortune.model.Fortune; +import org.acme.fortune.repository.FortuneRepository; +import io.smallrye.common.annotation.RunOnVirtualThread; +import io.smallrye.mutiny.Uni; + +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import java.util.List; +import java.util.Random; + + +@Path("") +public class FortuneResource { + + private final FortuneRepository repository; + + public Uni> getQuotesAsync(int size){ + //... + //asynchronously returns a list of quotes from an arbitrary source + } + + @GET + @Path("/quoted-blocking") + public List getAllQuotedBlocking() { + // we get the list of fortunes + var fortunes = repository.findAllBlocking(); + + // we get the list of quotes + var quotes = getQuotes(fortunes.size()).await().indefinitely(); + + // we append each quote to each fortune + for(int i=0; i < fortunes.size();i ++){ + fortunes.get(i).title+= " - "+quotes.get(i); + } + return todos; + } + + @GET + @Path("/quoted-reactive") + public Uni> getAllQuoted() { + // we first fetch the list of resource and we memoize it + // to avoid fetching it again everytime need it + var fortunes = repository.findAllAsync().memoize().indefinitely(); + + // once we get a result for fortunes, + // we know its size and can thus query the right number of quotes + var quotes = fortunes.onItem().transformToUni(list -> getQuotes(list.size())); + + // we now need to combine the two reactive streams + // before returning the result to the user + return Uni.combine().all().unis(fortunes,quotes).asTuple().onItem().transform(tuple -> { + var todoList=tuple.getItem1(); + //can await it since it is already resolved + var quotesList = tuple.getItem2(); + for(int i=0; i < todoList.size();i ++){ + todoList.get(i).title+= " - "+quotesList.get(i); + } + return todoList; + }); + } + + @GET + @RunOnVirtualThread + @Path("/quoted-virtual-thread") + public List getAllQuotedBlocking() { + //we get the list of fortunes + var fortunes = repository.findAllAsyncAndAwait(); + + //we get the list of quotes + var quotes = getQuotes(fortunes.size()).await().indefinitely(); + + //we append each quote to each fortune + for(int i=0; i < fortunes.size();i ++){ + fortunes.get(i).title+= " - "+quotes.get(i); + } + return todos; + } + +} +---- + +== Pinning cases +The notion of "cheap blocking" might not always be true: in certain occasions a virtual thread might "pin" its carrier +(the platform thread it is mounted upon). +In this situation, the platform thread is blocked exactly as it would have been in a typical blocking scenario. + +According to link:{vthreadjep}[JEP 425] this can happen in two situations: + +- when a virtual thread executes performs a blocking operation inside a `synchronized` block or method +- when it executes a blocking operation inside a native method or a foreign function + +It can be fairly easy to avoid these situations in our own code, but it is hard to verify every dependency we use. +Typically, while experimenting with virtual-threads, we realized that using the link:{pgsql-driver}[postgresql-JDBC driver] +results in frequent pinning. + +=== The JDBC problem +Our experiments so far show that when a virtual thread queries a database using the JDBC driver, it will pin its carrier +thread during the entire operation. + +Let's show the code of the `findAllBlocking()` method we used in the first example + +[source, java] +---- +//import ... + +@ApplicationScoped +public class FortuneRepository { + // ... + + public List findAllBlocking() { + List fortunes = new ArrayList<>(); + Connection conn = null; + try { + conn = db.getJdbcConnection(); + var preparedStatement = conn.prepareStatement(SELECT_ALL); + ResultSet rs = preparedStatement.executeQuery(); + while (rs.next()) { + fortunes.add(create(rs)); + } + rs.close(); + preparedStatement.close(); + } catch (SQLException e) { + logger.warn("Unable to retrieve fortunes from the database", e); + } finally { + close(conn); + } + return fortunes; + } + + //... +} +---- + +The actual query happens at `ResultSet rs = preparedStatement.executeQuery();`, here is how it is implemented in the +postgresql-jdbc driver 42.5.0: + +[source, java] +---- +class PgPreparedStatement extends PgStatement implements PreparedStatement { + // ... + + /* + * A Prepared SQL query is executed and its ResultSet is returned + * + * @return a ResultSet that contains the data produced by the * query - never null + * + * @exception SQLException if a database access error occurs + */ + @Override + public ResultSet executeQuery() throws SQLException { + synchronized (this) { + if (!executeWithFlags(0)) { + throw new PSQLException(GT.tr("No results were returned by the query."), PSQLState.NO_DATA); + } + return getSingleResultSet(); + } + } + + // ... +} +---- + +This `synchronized` block is the culprit. +Replacing it with a lock is a good solution, but it won't be enough: `synchronized` blocks are also used in `executeWithFlags(int flag)`. +A systematic review of the postgresql-jdbc driver is necessary to make sure that it is compliant with virtual threads. + +=== Reactive drivers at the rescue +The vertx-sql-client is a reactive client, hence it is not supposed to block while waiting for the completion of a +transaction with the database. +However, when using the link:{mutiny-vertx-sql}[smallrye-mutiny-vertx-sqlclient] it is possible to use a variant method +that will await for the completion of the transaction, mimicking a blocking behaviour. + +Below is the `FortuneRepository` except the blocking we've seen earlier has been replaced by reactive methods. + +[source, java] +---- +//import ... + +@ApplicationScoped +public class FortuneRepository { + // ... + + public Uni> findAllAsync() { + return db.getPool() + .preparedQuery(SELECT_ALL).execute() + .map(this::createListOfFortunes); + + } + + public List findAllAsyncAndAwait() { + var rows = db.getPool().preparedQuery(SELECT_ALL) + .executeAndAwait(); + return createListOfFortunes(rows); + } + + //... +} +---- + +Contrary to the link:{pgsql-driver}[postgresql-jdbc driver], no `synchronized` block is used where it shouldn't be, and +the `await` behaviour is implemented using locks and latches that won't cause pinning. + +Using the synchronous methods of the link:{mutiny-vertx-sql}[smallrye-mutiny-vertx-sqlclient] along with virtual threads +will allow you to use the synchronous blocking style, avoid pinning the carrier thread, and get performance close to a pure +reactive implementation. + +== A point about performance + +Our experiments seem to indicate that Quarkus with virtual threads will scale better than Quarkus blocking (offloading +the computation on a pool of platform worker threads) but not as well Quarkus reactive. +The memory consumption especially might be an issue: if your system needs to keep its memory footprint low we would +advise you stick to using reactive constructs. + +This degradation of performance doesn't seem to come from virtual threads themselves but from the interactions between +Vert.x/Netty (Quarkus underlying reactive engine) and the virtual threads. +This was illustrated in the issue that we will now describe. + +=== The Netty problem +For JSON serialization, Netty uses their custom implementation of thread locals, `FastThreadLocal` to store buffers. +When using virtual threads in quarkus, then number of virtual threads simultaneously living in the service is directly +related to the incoming traffic. +It is possible to get hundreds of thousands, if not millions, of them. + +If they need to serialize some data to JSON they will end up creating as many instances of `FastThreadLocal`, resulting +on a massive memory consumption as well as exacerbated pressure on the garbage collector. +This will eventually affect the performance of the application and inhibit its scalability. + +This is a perfect example of the mismatch between the reactive stack and the virtual threads. +The fundamental hypothesis are completely different and result in different optimizations. +Netty expects a system using few event-loops (as many event-loops as CPU cores by default in Quarkus), but it gets hundreds +of thousands of threads. +You can refer to link:https://mail.openjdk.org/pipermail/loom-dev/2022-July/004844.html[this mail] to get more information +on how we envision our future with virtual threads. + +=== Our solution to the Netty problem +In order to avoid this wasting of resource without modifying Netty upstream, we wrote an extension that modifies the +bytecode of the class responsible for creating the thread locals at build time. +Using this extension, performance of virtual threads in Quarkus for the Json Serialization test of the Techempower suite +increased by nearly 80%, making it almost as good as reactive endpoints. + +To use it, it needs to be added as a dependency: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.quarkus + quarkus-netty-loom-adaptor + +---- + +Furthermore, some operations undertaken by this extension need special access, it is necessary to + +- compile the application with the flag `-Dnet.bytebuddy.experimental` +- open the `java.base.lang` module at runtime with the flag `--add-opens java.base/java.lang=ALL-UNNAMED` + +This extension is only intended to improve performance, it is perfectly fine not to use it. + +=== Concerning dev mode +If you want to use quarkus with the dev mode, it won't be possible to manually specify the flags we mentioned along this guide. +Instead, you want to specify them all in the configuration of the `quarkus-maven-plugin` as presented below. + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + build + + + + + + 19 + 19 + + --enable-preview + -Dnet.bytebuddy.experimental + + --enable-preview --add-opens java.base/java.lang=ALL-UNNAMED + + + +---- + +If you don't want to put specify the opening the `java.lang` module in your pom.xml file, you can also specify it as an argument +when you start the dev mode. + +The configuration of the quarkus-maven-plugin will be simpler: + +[source,xml,role="primary asciidoc-tabs-target-sync-cli asciidoc-tabs-target-sync-maven"] +.pom.xml +---- + + 19 + 19 + + --enable-preview + -Dnet.bytebuddy.experimental + + --enable-preview + +---- + +And the command will become: + +[source, bash] +---- +mvn quarkus:dev -Dopen-lang-package +---- From dd0173349ac3058d37b483f1c56b656fda2e9bbf Mon Sep 17 00:00:00 2001 From: Fedor Dudinskiy Date: Thu, 15 Sep 2022 14:37:43 +0200 Subject: [PATCH 24/36] Update documentation in regards to parity and add an example --- docs/src/main/asciidoc/qute-reference.adoc | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/docs/src/main/asciidoc/qute-reference.adoc b/docs/src/main/asciidoc/qute-reference.adoc index 6a50ddec75fe8..cd74be8f916f6 100644 --- a/docs/src/main/asciidoc/qute-reference.adoc +++ b/docs/src/main/asciidoc/qute-reference.adoc @@ -632,9 +632,9 @@ It's also possible to access the iteration metadata inside the loop via the foll * `hasNext` - `true` if the iteration has more elements * `isLast` - `true` if `hasNext == false` * `isFirst` - `true` if `count == 1` -* `odd` - `true` if the zero-based index is odd -* `even` - `true` if the zero-based index is even -* `indexParity` - outputs `odd` or `even` based on the zero-based index value +* `odd` - `true` if the element's count is odd +* `even` - `true` if the element's count is even +* `indexParity` - outputs `odd` or `even` based on the count value However, the keys cannot be used directly. Instead, a prefix is used to avoid possible collisions with variables from the outer scope. @@ -679,7 +679,7 @@ The `for` statement also works with integers, starting from 1. In the example be [source] ---- {#for i in total} - {i}: + {i}: ({i_count} {i_indexParity} {i_even})
{/for} ---- @@ -687,7 +687,9 @@ And the output will be: [source] ---- -1:2:3: +1: (1 odd false) +2: (2 even true) +3: (3 odd false) ---- A loop section may also define the `{#else}` block that is executed when there are no items to iterate: From 414519cbe81f558b6aba66be89a00ebaafc83a78 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 15 Sep 2022 15:51:15 +0100 Subject: [PATCH 25/36] Improve OIDC code flow tests --- .../runtime/CodeAuthenticationMechanism.java | 4 +- .../it/keycloak/ProtectedResource.java | 6 --- .../it/keycloak/TenantAutoRefresh.java | 7 +++- .../io/quarkus/it/keycloak/TenantLogout.java | 7 +++- .../src/main/resources/application.properties | 1 + .../io/quarkus/it/keycloak/CodeFlowTest.java | 37 +++++++++---------- 6 files changed, 32 insertions(+), 30 deletions(-) diff --git a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java index 2421c3b3b6dd3..7fdfea032c413 100644 --- a/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java +++ b/extensions/oidc/runtime/src/main/java/io/quarkus/oidc/runtime/CodeAuthenticationMechanism.java @@ -250,7 +250,7 @@ public Uni apply(Throwable t) { } if (!configContext.oidcConfig.token.refreshExpired) { LOG.debug("Token has expired, token refresh is not allowed"); - throw new AuthenticationCompletionException(t.getCause()); + throw new AuthenticationFailedException(t.getCause()); } LOG.debug("Token has expired, trying to refresh it"); return refreshSecurityIdentity(configContext, @@ -833,7 +833,7 @@ private Uni refreshSecurityIdentity(TenantConfigContext config @Override public Uni apply(final AuthorizationCodeTokens tokens, final Throwable t) { if (t != null) { - LOG.debugf("ID token refresh has failed: %s", t.getMessage()); + LOG.errorf("ID token refresh has failed: %s", t.getMessage()); if (autoRefresh) { LOG.debug("Using the current SecurityIdentity since the ID token is still valid"); return Uni.createFrom().item(((TokenAutoRefreshException) t).getSecurityIdentity()); diff --git a/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java b/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java index 9f4941b88e385..fbe80108b4407 100644 --- a/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java +++ b/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/ProtectedResource.java @@ -174,12 +174,6 @@ public String getNameCallbackJwtNotUsedAfterRedirect() { throw new InternalServerErrorException("This method must not be invoked"); } - @GET - @Path("tenant-logout") - public String getTenantLogout() { - return "Tenant Logout"; - } - @GET @Path("access") public String getAccessToken() { diff --git a/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantAutoRefresh.java b/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantAutoRefresh.java index 9d5e9b4345070..092d6c8b63f87 100644 --- a/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantAutoRefresh.java +++ b/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantAutoRefresh.java @@ -1,15 +1,20 @@ package io.quarkus.it.keycloak; +import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import io.quarkus.security.Authenticated; +import io.vertx.ext.web.RoutingContext; @Path("/tenant-autorefresh") public class TenantAutoRefresh { + @Inject + RoutingContext context; + @Authenticated @GET public String getTenantLogout() { - return "Tenant AutoRefresh"; + return "Tenant AutoRefresh, refreshed: " + (context.get("refresh_token_grant_response") != null); } } diff --git a/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantLogout.java b/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantLogout.java index cd9568ed14035..5ffabd9a6b9ea 100644 --- a/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantLogout.java +++ b/integration-tests/oidc-code-flow/src/main/java/io/quarkus/it/keycloak/TenantLogout.java @@ -1,5 +1,6 @@ package io.quarkus.it.keycloak; +import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.InternalServerErrorException; import javax.ws.rs.Path; @@ -9,6 +10,7 @@ import javax.ws.rs.core.HttpHeaders; import io.quarkus.security.Authenticated; +import io.vertx.ext.web.RoutingContext; @Path("/tenant-logout") public class TenantLogout { @@ -16,10 +18,13 @@ public class TenantLogout { @Context HttpHeaders headers; + @Inject + RoutingContext context; + @Authenticated @GET public String getTenantLogout() { - return "Tenant Logout"; + return "Tenant Logout, refreshed: " + (context.get("refresh_token_grant_response") != null); } // It is needed for the proactive-auth=false to work: /tenant-logout/logout should match a user initiated logout request diff --git a/integration-tests/oidc-code-flow/src/main/resources/application.properties b/integration-tests/oidc-code-flow/src/main/resources/application.properties index c693d6853e3d7..69b81d6eccce7 100644 --- a/integration-tests/oidc-code-flow/src/main/resources/application.properties +++ b/integration-tests/oidc-code-flow/src/main/resources/application.properties @@ -86,6 +86,7 @@ quarkus.oidc.tenant-autorefresh.application-type=web-app quarkus.oidc.tenant-autorefresh.authentication.cookie-path=/tenant-autorefresh quarkus.oidc.tenant-autorefresh.token.refresh-expired=true quarkus.oidc.tenant-autorefresh.token.refresh-token-time-skew=30S +quarkus.oidc.tenant-autorefresh.authentication.remove-redirect-parameters=false # Tenant which is used to test that the redirect_uri https scheme is enforced. quarkus.oidc.tenant-https.auth-server-url=${keycloak.url}/realms/quarkus diff --git a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java index c1571712c6937..a574802dbb855 100644 --- a/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java +++ b/integration-tests/oidc-code-flow/src/test/java/io/quarkus/it/keycloak/CodeFlowTest.java @@ -413,7 +413,7 @@ public void testRPInitiatedLogout() throws IOException { loginForm.getInputByName("username").setValueAttribute("alice"); loginForm.getInputByName("password").setValueAttribute("alice"); page = loginForm.getInputByName("login").click(); - assertTrue(page.asText().contains("Tenant Logout")); + assertEquals("Tenant Logout, refreshed: false", page.asText()); assertNotNull(getSessionCookie(webClient, "tenant-logout")); page = webClient.getPage("http://localhost:8081/tenant-logout/logout"); @@ -422,11 +422,20 @@ public void testRPInitiatedLogout() throws IOException { page = webClient.getPage("http://localhost:8081/tenant-logout"); assertEquals("Sign in to logout-realm", page.getTitleText()); - loginForm = page.getForms().get(0); + webClient.getCookieManager().clearCookies(); + } + } + + @Test + public void testTokenRefresh() throws IOException { + try (final WebClient webClient = createWebClient()) { + HtmlPage page = webClient.getPage("http://localhost:8081/tenant-logout"); + assertEquals("Sign in to logout-realm", page.getTitleText()); + HtmlForm loginForm = page.getForms().get(0); loginForm.getInputByName("username").setValueAttribute("alice"); loginForm.getInputByName("password").setValueAttribute("alice"); page = loginForm.getInputByName("login").click(); - assertTrue(page.asText().contains("Tenant Logout")); + assertEquals("Tenant Logout, refreshed: false", page.asText()); Cookie sessionCookie = getSessionCookie(webClient, "tenant-logout"); assertNotNull(sessionCookie); @@ -445,13 +454,14 @@ public Boolean call() throws Exception { // Should not redirect to OP but silently refresh token Cookie newSessionCookie = getSessionCookie(webClient, "tenant-logout"); assertNotNull(newSessionCookie); - return !idToken.equals(getIdToken(newSessionCookie)); + return webResponse.getContentAsString().equals("Tenant Logout, refreshed: true") + && !idToken.equals(getIdToken(newSessionCookie)); } }); // local session refreshed and still valid page = webClient.getPage("http://localhost:8081/tenant-logout"); - assertTrue(page.asText().contains("Tenant Logout")); + assertEquals("Tenant Logout, refreshed: false", page.asText()); assertNotNull(getSessionCookie(webClient, "tenant-logout")); //wait now so that we reach the refresh timeout @@ -495,7 +505,7 @@ public void testTokenAutoRefresh() throws IOException { loginForm.getInputByName("username").setValueAttribute("alice"); loginForm.getInputByName("password").setValueAttribute("alice"); page = loginForm.getInputByName("login").click(); - assertTrue(page.asText().contains("Tenant AutoRefresh")); + assertEquals("Tenant AutoRefresh, refreshed: false", page.asText()); Cookie sessionCookie = getSessionCookie(webClient, "tenant-autorefresh"); assertNotNull(sessionCookie); @@ -504,24 +514,11 @@ public void testTokenAutoRefresh() throws IOException { // Auto-refresh-interval is 30 secs so every call auto-refreshes the token // Right now the original ID token is still valid but will be auto-refreshed page = webClient.getPage("http://localhost:8081/tenant-autorefresh"); - assertTrue(page.getBody().asText().contains("Tenant AutoRefresh")); + assertEquals("Tenant AutoRefresh, refreshed: true", page.asText()); sessionCookie = getSessionCookie(webClient, "tenant-autorefresh"); assertNotNull(sessionCookie); String nextIdToken = getIdToken(sessionCookie); assertNotEquals(idToken, nextIdToken); - idToken = nextIdToken; - - //wait now so that we reach the ID token timeout - await().atLeast(6, TimeUnit.SECONDS); - - // ID token has expired, must be refreshed, auto-refresh-interval must not cause - // an auto-refresh loop even though it is larger than the ID token lifespan - page = webClient.getPage("http://localhost:8081/tenant-autorefresh"); - assertTrue(page.getBody().asText().contains("Tenant AutoRefresh")); - sessionCookie = getSessionCookie(webClient, "tenant-autorefresh"); - assertNotNull(sessionCookie); - nextIdToken = getIdToken(sessionCookie); - assertNotEquals(idToken, nextIdToken); webClient.getCookieManager().clearCookies(); } From 7c77aa6a470758003cfe13057c8b2168be8bd681 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Fri, 19 Aug 2022 14:38:42 +0200 Subject: [PATCH 26/36] Fix type variable resolution in ArC's bean type closure search --- .../arc/processor/ClientProxyGenerator.java | 7 ++-- .../arc/processor/DecoratorGenerator.java | 4 +- .../arc/processor/InjectionPointInfo.java | 7 ++-- .../quarkus/arc/processor/ObserverInfo.java | 3 +- .../arc/processor/SubclassGenerator.java | 4 +- .../java/io/quarkus/arc/processor/Types.java | 38 ++++++++++--------- .../io/quarkus/arc/processor/TypesTest.java | 5 +-- 7 files changed, 33 insertions(+), 35 deletions(-) diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java index 3523648b96b93..e13c36fcebb3b 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ClientProxyGenerator.java @@ -35,7 +35,6 @@ import org.jboss.jandex.IndexView; import org.jboss.jandex.MethodInfo; import org.jboss.jandex.Type; -import org.jboss.jandex.TypeVariable; /** * @@ -120,7 +119,7 @@ Collection generate(BeanInfo bean, String beanClassName, if (!providerClass.typeParameters().isEmpty()) { clientProxy.setSignature(AsmUtilCopy.getGeneratedSubClassSignature(providerClass, bean.getProviderType())); } - Map> resolvedTypeVariables = Types.resolvedTypeVariables(providerClass, + Map> resolvedTypeVariables = Types.resolvedTypeVariables(providerClass, bean.getDeployment()); FieldCreator beanField = clientProxy.getFieldCreator(BEAN_FIELD, InjectableBean.class) .setModifiers(ACC_PRIVATE | ACC_FINAL); @@ -148,10 +147,10 @@ Collection generate(BeanInfo bean, String beanClassName, MethodDescriptor originalMethodDescriptor = MethodDescriptor.of(method); MethodCreator forward = clientProxy.getMethodCreator(originalMethodDescriptor); if (AsmUtilCopy.needsSignature(method)) { - Map methodClassVariables = resolvedTypeVariables.get(method.declaringClass()); + Map methodClassVariables = resolvedTypeVariables.get(method.declaringClass()); String signature = AsmUtilCopy.getSignature(method, typeVariable -> { if (methodClassVariables != null) { - Type ret = methodClassVariables.get(typeVariable); + Type ret = methodClassVariables.get(typeVariable.identifier()); // let's not map a TV to itself if (ret != typeVariable) return ret; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorGenerator.java index 65fc970d8fece..6091bad566cc4 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/DecoratorGenerator.java @@ -187,7 +187,7 @@ private String generateDecoratorImplementation(String baseName, String targetPac // A decorated type can declare type parameters // For example Converter should result in a T -> String mapping List typeParameters = decoratedTypeClass.typeParameters(); - Map resolvedTypeParameters = Collections.emptyMap(); + Map resolvedTypeParameters = Collections.emptyMap(); if (!typeParameters.isEmpty()) { resolvedTypeParameters = new HashMap<>(); // The delegate type can be used to infer the parameter types @@ -195,7 +195,7 @@ private String generateDecoratorImplementation(String baseName, String targetPac if (type.kind() == Kind.PARAMETERIZED_TYPE) { List typeArguments = type.asParameterizedType().arguments(); for (int i = 0; i < typeParameters.size(); i++) { - resolvedTypeParameters.put(typeParameters.get(i), typeArguments.get(i)); + resolvedTypeParameters.put(typeParameters.get(i).identifier(), typeArguments.get(i)); } } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java index 1477f06aceedd..2bdf5ea705f41 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/InjectionPointInfo.java @@ -22,7 +22,6 @@ import org.jboss.jandex.MethodInfo; import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.Type; -import org.jboss.jandex.TypeVariable; /** * Represents an injection point. @@ -262,15 +261,15 @@ private static Type resolveType(Type type, ClassInfo beanClass, ClassInfo declar if (type.kind() == org.jboss.jandex.Type.Kind.CLASS) { return type; } - Map> resolvedTypeVariables = Types.resolvedTypeVariables(beanClass, beanDeployment); + Map> resolvedTypeVariables = Types.resolvedTypeVariables(beanClass, beanDeployment); return resolveType(type, declaringClass, beanDeployment, resolvedTypeVariables); } private static Type resolveType(Type type, ClassInfo beanClass, BeanDeployment beanDeployment, - Map> resolvedTypeVariables) { + Map> resolvedTypeVariables) { if (type.kind() == org.jboss.jandex.Type.Kind.TYPE_VARIABLE) { if (resolvedTypeVariables.containsKey(beanClass)) { - return resolvedTypeVariables.get(beanClass).getOrDefault(type.asTypeVariable(), type); + return resolvedTypeVariables.get(beanClass).getOrDefault(type.asTypeVariable().identifier(), type); } } else if (type.kind() == org.jboss.jandex.Type.Kind.PARAMETERIZED_TYPE) { ParameterizedType parameterizedType = type.asParameterizedType(); diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java index 6a4579e675011..b858c74df729e 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/ObserverInfo.java @@ -27,7 +27,6 @@ import org.jboss.jandex.MethodInfo; import org.jboss.jandex.MethodParameterInfo; import org.jboss.jandex.Type; -import org.jboss.jandex.TypeVariable; import org.jboss.logging.Logger; /** @@ -54,7 +53,7 @@ static ObserverInfo create(BeanInfo declaringBean, MethodInfo observerMethod, In Type observedType = observerMethod.parameterType(eventParameter.position()); if (Types.containsTypeVariable(observedType)) { - Map resolvedTypeVariables = Types + Map resolvedTypeVariables = Types .resolvedTypeVariables(declaringBean.getImplClazz(), declaringBean.getDeployment()) .getOrDefault(observerMethod.declaringClass(), Collections.emptyMap()); observedType = Types.resolveTypeParam(observedType, resolvedTypeVariables, diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java index 08462d17a133b..d385a31f874c0 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/SubclassGenerator.java @@ -480,7 +480,7 @@ private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type provi // The delegate type can declare type parameters // For example @Delegate Converter should result in a T -> String mapping List typeParameters = delegateTypeClass.typeParameters(); - Map resolvedTypeParameters = Collections.emptyMap(); + Map resolvedTypeParameters = Collections.emptyMap(); if (!typeParameters.isEmpty()) { resolvedTypeParameters = new HashMap<>(); // The delegate type can be used to infer the parameter types @@ -488,7 +488,7 @@ private void processDecorator(DecoratorInfo decorator, BeanInfo bean, Type provi if (delegateType.kind() == Kind.PARAMETERIZED_TYPE) { List typeArguments = delegateType.asParameterizedType().arguments(); for (int i = 0; i < typeParameters.size(); i++) { - resolvedTypeParameters.put(typeParameters.get(i), typeArguments.get(i)); + resolvedTypeParameters.put(typeParameters.get(i).identifier(), typeArguments.get(i)); } } } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java index c604471397b4b..59202d6cdf3f8 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java @@ -384,7 +384,7 @@ static List getResolvedParameters(ClassInfo classInfo, MethodInfo method, return getResolvedParameters(classInfo, Collections.emptyMap(), method, index); } - static List getResolvedParameters(ClassInfo classInfo, Map resolvedMap, + static List getResolvedParameters(ClassInfo classInfo, Map resolvedMap, MethodInfo method, IndexView index) { List typeParameters = classInfo.typeParameters(); // E.g. Foo, T, List @@ -414,12 +414,13 @@ static List getResolvedParameters(ClassInfo classInfo, Map getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFieldOrMethod, - Map resolvedTypeParameters, - BeanDeployment beanDeployment, BiConsumer> resolvedTypeVariablesConsumer) { + Map resolvedTypeParameters, + BeanDeployment beanDeployment, BiConsumer> resolvedTypeVariablesConsumer) { Set types = new HashSet<>(); List typeParameters = classInfo.typeParameters(); - if (typeParameters.isEmpty() || !typeParameters.stream().allMatch(resolvedTypeParameters::containsKey)) { + if (typeParameters.isEmpty() + || !typeParameters.stream().allMatch(it -> resolvedTypeParameters.containsKey(it.identifier()))) { // Not a parameterized type or a raw type types.add(Type.create(classInfo.name(), Kind.CLASS)); } else { @@ -427,7 +428,7 @@ static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFi Type[] typeParams = new Type[typeParameters.size()]; boolean skipThisType = false; for (int i = 0; i < typeParameters.size(); i++) { - typeParams[i] = resolvedTypeParameters.get(typeParameters.get(i)); + typeParams[i] = resolvedTypeParameters.get(typeParameters.get(i).identifier()); // this should only be the case for producers; wildcard is not a legal bean type // see https://docs.jboss.org/cdi/spec/2.0/cdi-spec.html#legal_bean_types if (typeParams[i].kind().equals(Kind.WILDCARD_TYPE) && producerFieldOrMethod != null) { @@ -440,9 +441,9 @@ static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFi } } if (resolvedTypeVariablesConsumer != null) { - Map resolved = new HashMap<>(); + Map resolved = new HashMap<>(); for (int i = 0; i < typeParameters.size(); i++) { - resolved.put(typeParameters.get(i), typeParams[i]); + resolved.put(typeParameters.get(i).identifier(), typeParams[i]); } resolvedTypeVariablesConsumer.accept(classInfo, resolved); } @@ -457,7 +458,7 @@ static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFi } ClassInfo interfaceClassInfo = getClassByName(beanDeployment.getBeanArchiveIndex(), interfaceType.name()); if (interfaceClassInfo != null) { - Map resolved = Collections.emptyMap(); + Map resolved = Collections.emptyMap(); if (Kind.PARAMETERIZED_TYPE.equals(interfaceType.kind())) { resolved = buildResolvedMap(interfaceType.asParameterizedType().arguments(), interfaceClassInfo.typeParameters(), resolvedTypeParameters, beanDeployment.getBeanArchiveIndex()); @@ -470,7 +471,7 @@ static Set getTypeClosure(ClassInfo classInfo, AnnotationTarget producerFi if (classInfo.superClassType() != null) { ClassInfo superClassInfo = getClassByName(beanDeployment.getBeanArchiveIndex(), classInfo.superName()); if (superClassInfo != null) { - Map resolved = Collections.emptyMap(); + Map resolved = Collections.emptyMap(); if (Kind.PARAMETERIZED_TYPE.equals(classInfo.superClassType().kind())) { resolved = buildResolvedMap(classInfo.superClassType().asParameterizedType().arguments(), superClassInfo.typeParameters(), @@ -509,9 +510,9 @@ static Set getDelegateTypeClosure(InjectionPointInfo delegateInjectionPoin return types; } - static Map> resolvedTypeVariables(ClassInfo classInfo, + static Map> resolvedTypeVariables(ClassInfo classInfo, BeanDeployment beanDeployment) { - Map> resolvedTypeVariables = new HashMap<>(); + Map> resolvedTypeVariables = new HashMap<>(); getTypeClosure(classInfo, null, Collections.emptyMap(), beanDeployment, resolvedTypeVariables::put); return resolvedTypeVariables; } @@ -540,26 +541,27 @@ static Set restrictBeanTypes(Set types, Collection Map buildResolvedMap(List resolvedArguments, + static Map buildResolvedMap(List resolvedArguments, List typeVariables, - Map resolvedTypeParameters, IndexView index) { - Map resolvedMap = new HashMap<>(); + Map resolvedTypeParameters, IndexView index) { + Map resolvedMap = new HashMap<>(); for (int i = 0; i < resolvedArguments.size(); i++) { - resolvedMap.put(typeVariables.get(i), resolveTypeParam(resolvedArguments.get(i), resolvedTypeParameters, index)); + resolvedMap.put(typeVariables.get(i).identifier(), + resolveTypeParam(resolvedArguments.get(i), resolvedTypeParameters, index)); } return resolvedMap; } - static Type resolveTypeParam(Type typeParam, Map resolvedTypeParameters, IndexView index) { + static Type resolveTypeParam(Type typeParam, Map resolvedTypeParameters, IndexView index) { if (typeParam.kind() == Kind.TYPE_VARIABLE) { - return resolvedTypeParameters.getOrDefault(typeParam, typeParam); + return resolvedTypeParameters.getOrDefault(typeParam.asTypeVariable().identifier(), typeParam); } else if (typeParam.kind() == Kind.PARAMETERIZED_TYPE) { ParameterizedType parameterizedType = typeParam.asParameterizedType(); ClassInfo classInfo = getClassByName(index, parameterizedType.name()); if (classInfo != null) { List typeParameters = classInfo.typeParameters(); List arguments = parameterizedType.arguments(); - Map resolvedMap = buildResolvedMap(arguments, typeParameters, + Map resolvedMap = buildResolvedMap(arguments, typeParameters, resolvedTypeParameters, index); Type[] typeParams = new Type[typeParameters.size()]; for (int i = 0; i < typeParameters.size(); i++) { diff --git a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java index 787085e203cb2..b24c18e3cba06 100644 --- a/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java +++ b/independent-projects/arc/processor/src/test/java/io/quarkus/arc/processor/TypesTest.java @@ -18,7 +18,6 @@ import org.jboss.jandex.ParameterizedType; import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; -import org.jboss.jandex.TypeVariable; import org.junit.jupiter.api.Test; public class TypesTest { @@ -31,7 +30,7 @@ public void testGetTypeClosure() throws IOException { DotName fooName = DotName.createSimple(Foo.class.getName()); DotName producerName = DotName.createSimple(Producer.class.getName()); ClassInfo fooClass = index.getClassByName(fooName); - Map> resolvedTypeVariables = new HashMap<>(); + Map> resolvedTypeVariables = new HashMap<>(); BeanDeployment dummyDeployment = BeanProcessor.builder().setBeanArchiveIndex(index).build().getBeanDeployment(); // Baz, Foo, Object @@ -46,7 +45,7 @@ public void testGetTypeClosure() throws IOException { null))); assertEquals(resolvedTypeVariables.size(), 1); assertTrue(resolvedTypeVariables.containsKey(fooClass)); - assertEquals(resolvedTypeVariables.get(fooClass).get(fooClass.typeParameters().get(0)), + assertEquals(resolvedTypeVariables.get(fooClass).get(fooClass.typeParameters().get(0).identifier()), Type.create(DotName.createSimple(String.class.getName()), Kind.CLASS)); resolvedTypeVariables.clear(); From fbc44b839d5839182507e29f81cf284795123d71 Mon Sep 17 00:00:00 2001 From: Ladislav Thon Date: Thu, 18 Aug 2022 16:37:45 +0200 Subject: [PATCH 27/36] Upgrade to Jandex 3.0.0 --- .github/dependabot.yml | 4 +- bom/application/pom.xml | 20 ++- build-parent/pom.xml | 12 +- core/deployment/pom.xml | 2 +- .../dev/RuntimeUpdatesProcessor.java | 2 +- .../deployment/index/IndexWrapper.java | 158 ++++++++++++++---- .../deployment/index/IndexingUtil.java | 32 +++- .../recording/AnnotationProxyProvider.java | 5 +- .../steps/ReflectiveHierarchyStep.java | 4 +- .../io/quarkus/deployment/util/AsmUtil.java | 13 ++ .../main/java/io/quarkus/maven/DevMojo.java | 20 ++- docs/pom.xml | 2 +- docs/src/main/asciidoc/cdi-reference.adoc | 4 +- .../main/asciidoc/grpc-getting-started.adoc | 4 +- docs/src/main/asciidoc/maven-tooling.adoc | 4 +- extensions/hibernate-orm/runtime/pom.xml | 2 +- ...bernateSearchOutboxPollingClassesTest.java | 1 + .../runtime/pom.xml | 2 +- .../hibernate-orm-panache/runtime/pom.xml | 2 +- .../runtime/pom.xml | 2 +- .../mongodb-panache-common/runtime/pom.xml | 2 +- .../mongodb-panache-kotlin/runtime/pom.xml | 2 +- .../panache/mongodb-panache/runtime/pom.xml | 2 +- .../panache/panache-common/deployment/pom.xml | 2 +- .../deployment/pom.xml | 2 +- extensions/picocli/runtime/pom.xml | 2 +- .../rest-client-config/runtime/pom.xml | 2 +- .../JaxrsClientReactiveProcessor.java | 1 + .../graphql/deployment/OverridableIndex.java | 24 +++ .../spring-boot-properties/runtime/pom.xml | 2 +- .../di/deployment/SpringDIProcessorTest.java | 19 +-- .../SpringScheduledProcessorTest.java | 20 +-- .../vertx/deployment/EventBusConsumer.java | 8 +- .../vertx/deployment/VertxProcessor.java | 2 +- independent-projects/arc/pom.xml | 6 +- independent-projects/arc/processor/pom.xml | 2 +- .../io/quarkus/arc/processor/AsmUtilCopy.java | 11 +- .../quarkus/arc/processor/BeanArchives.java | 158 ++++++++++++++---- .../io/quarkus/arc/processor/Methods.java | 1 + .../java/io/quarkus/arc/processor/Types.java | 4 +- .../bootstrap/app-model/pom.xml | 2 +- independent-projects/bootstrap/pom.xml | 6 +- .../quarkus-banned-dependencies.xml | 2 + independent-projects/qute/generator/pom.xml | 2 +- .../generator/ValueResolverGenerator.java | 1 + independent-projects/qute/pom.xml | 6 +- .../client/processor/pom.xml | 2 +- .../common/processor/pom.xml | 2 +- .../reactive/common/processor/AsmUtil.java | 12 ++ .../processor/CalculatingIndexView.java | 158 ++++++++++++++---- .../resteasy-reactive/pom.xml | 6 +- .../server/processor/pom.xml | 2 +- independent-projects/tools/pom.xml | 2 +- .../tools/registry-client/pom.xml | 4 +- integration-tests/common-jpa-entities/pom.xml | 2 +- .../common/build.gradle | 8 +- .../jpa-mapping-xml/modern-library-a/pom.xml | 2 +- .../jpa-mapping-xml/modern-library-b/pom.xml | 2 +- jakarta/rewrite.yml | 6 +- tcks/microprofile-fault-tolerance/pom.xml | 3 - .../microprofile-rest-client-reactive/pom.xml | 3 +- test-framework/common/pom.xml | 2 +- test-framework/security-jwt/pom.xml | 2 +- test-framework/security-oidc/pom.xml | 2 +- test-framework/security-webauthn/pom.xml | 2 +- test-framework/security/pom.xml | 2 +- 66 files changed, 574 insertions(+), 236 deletions(-) diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 419943c20baaa..43810566bee45 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -93,8 +93,8 @@ updates: # Agroal - dependency-name: io.agroal:* # Jandex - - dependency-name: org.jboss:jandex - - dependency-name: org.jboss.jandex:jandex-maven-plugin + - dependency-name: io.smallrye:jandex + - dependency-name: io.smallrye:jandex-maven-plugin # WireMock - dependency-name: com.github.tomakehurst:wiremock-jre8-standalone - dependency-name: uk.co.automatictester:wiremock-maven-plugin diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 10a20945120bd..7ce21ae75f94d 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -18,7 +18,7 @@ 1.0.2.3 1.0.12.3 3.0.2 - 2.4.3.Final + 3.0.0 4.7.7.Final 0.33.0 0.2.4 @@ -43,8 +43,8 @@ 2.12.0 3.3.0 3.0.5 - 2.2.1 - 1.7.1 + 2.3.0 + 1.8.0 2.1.1 5.5.0 3.5.3 @@ -82,7 +82,7 @@ 2.1.0 22.2.0 ${graal-sdk.version} - 1.1.0.Final + 1.2.0.Final 2.13.4 1.0.0.Final 3.12.0 @@ -4247,7 +4247,7 @@ ${graal-sdk.version}
- org.jboss + io.smallrye jandex ${jandex.version} @@ -4855,6 +4855,12 @@ org.hibernate hibernate-core ${hibernate-orm.version} + + + org.jboss + jandex + + org.hibernate @@ -4901,6 +4907,10 @@ hibernate-search-mapper-orm ${hibernate-search.version} + + org.jboss + jandex + org.hibernate.common diff --git a/build-parent/pom.xml b/build-parent/pom.xml index 2a9d7f36089c1..8188023f3a832 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -33,9 +33,9 @@ 3.0.0 ${version.surefire.plugin} - - 1.2.3 - 0.13.0 + + 3.0.0 + 1.0.0 - org.jboss + io.smallrye jandex diff --git a/extensions/hibernate-search-orm-coordination-outbox-polling/deployment/src/test/java/io/quarkus/hibernate/search/orm/coordination/outboxpolling/test/HibernateSearchOutboxPollingClassesTest.java b/extensions/hibernate-search-orm-coordination-outbox-polling/deployment/src/test/java/io/quarkus/hibernate/search/orm/coordination/outboxpolling/test/HibernateSearchOutboxPollingClassesTest.java index 15c55c8c03e26..be49d54a33287 100644 --- a/extensions/hibernate-search-orm-coordination-outbox-polling/deployment/src/test/java/io/quarkus/hibernate/search/orm/coordination/outboxpolling/test/HibernateSearchOutboxPollingClassesTest.java +++ b/extensions/hibernate-search-orm-coordination-outbox-polling/deployment/src/test/java/io/quarkus/hibernate/search/orm/coordination/outboxpolling/test/HibernateSearchOutboxPollingClassesTest.java @@ -165,6 +165,7 @@ private static void collectModelClassesRecursively(Index index, Type type, Set - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/extensions/panache/hibernate-orm-panache/runtime/pom.xml b/extensions/panache/hibernate-orm-panache/runtime/pom.xml index 5eae026f64935..79101f6144cc6 100644 --- a/extensions/panache/hibernate-orm-panache/runtime/pom.xml +++ b/extensions/panache/hibernate-orm-panache/runtime/pom.xml @@ -96,7 +96,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/extensions/panache/hibernate-reactive-panache/runtime/pom.xml b/extensions/panache/hibernate-reactive-panache/runtime/pom.xml index 8f177204ea95c..f3021f1329f25 100644 --- a/extensions/panache/hibernate-reactive-panache/runtime/pom.xml +++ b/extensions/panache/hibernate-reactive-panache/runtime/pom.xml @@ -104,7 +104,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/extensions/panache/mongodb-panache-common/runtime/pom.xml b/extensions/panache/mongodb-panache-common/runtime/pom.xml index c6decdfdad1ab..a0ee38dc6a428 100644 --- a/extensions/panache/mongodb-panache-common/runtime/pom.xml +++ b/extensions/panache/mongodb-panache-common/runtime/pom.xml @@ -73,7 +73,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/extensions/panache/mongodb-panache-kotlin/runtime/pom.xml b/extensions/panache/mongodb-panache-kotlin/runtime/pom.xml index 962592a648616..ef6c31f23d2f4 100644 --- a/extensions/panache/mongodb-panache-kotlin/runtime/pom.xml +++ b/extensions/panache/mongodb-panache-kotlin/runtime/pom.xml @@ -96,7 +96,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/extensions/panache/mongodb-panache/runtime/pom.xml b/extensions/panache/mongodb-panache/runtime/pom.xml index b586cc5b1a193..ef61e86e16c9c 100644 --- a/extensions/panache/mongodb-panache/runtime/pom.xml +++ b/extensions/panache/mongodb-panache/runtime/pom.xml @@ -70,7 +70,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/extensions/panache/panache-common/deployment/pom.xml b/extensions/panache/panache-common/deployment/pom.xml index ef1a0737cadca..d68ec60715fbe 100644 --- a/extensions/panache/panache-common/deployment/pom.xml +++ b/extensions/panache/panache-common/deployment/pom.xml @@ -25,7 +25,7 @@ quarkus-arc-deployment - org.jboss + io.smallrye jandex diff --git a/extensions/panache/panache-hibernate-common/deployment/pom.xml b/extensions/panache/panache-hibernate-common/deployment/pom.xml index fcc44e98545af..6592af6cd1c03 100644 --- a/extensions/panache/panache-hibernate-common/deployment/pom.xml +++ b/extensions/panache/panache-hibernate-common/deployment/pom.xml @@ -25,7 +25,7 @@ quarkus-arc-deployment - org.jboss + io.smallrye jandex diff --git a/extensions/picocli/runtime/pom.xml b/extensions/picocli/runtime/pom.xml index ea7fc88598ff3..2691817ad14a0 100644 --- a/extensions/picocli/runtime/pom.xml +++ b/extensions/picocli/runtime/pom.xml @@ -35,7 +35,7 @@ quarkus-extension-maven-plugin - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/extensions/resteasy-classic/rest-client-config/runtime/pom.xml b/extensions/resteasy-classic/rest-client-config/runtime/pom.xml index 4d68b76280d4c..24efe5c74c332 100644 --- a/extensions/resteasy-classic/rest-client-config/runtime/pom.xml +++ b/extensions/resteasy-classic/rest-client-config/runtime/pom.xml @@ -28,7 +28,7 @@ microprofile-rest-client-api - org.jboss + io.smallrye jandex diff --git a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java index cbbaffa9befb2..edf3f2cbe5885 100644 --- a/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java +++ b/extensions/resteasy-reactive/jaxrs-client-reactive/deployment/src/main/java/io/quarkus/jaxrs/client/reactive/deployment/JaxrsClientReactiveProcessor.java @@ -1610,6 +1610,7 @@ private ResultHandle createMultipartForm(MethodCreator methodCreator, ResultHand case VOID: case TYPE_VARIABLE: case UNRESOLVED_TYPE_VARIABLE: + case TYPE_VARIABLE_REFERENCE: case WILDCARD_TYPE: throw new IllegalArgumentException("Unsupported multipart form field type: " + fieldType + " in " + "field class " + formClassType.name()); diff --git a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/OverridableIndex.java b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/OverridableIndex.java index 1c17b0f415891..fcb4b35f10a2b 100644 --- a/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/OverridableIndex.java +++ b/extensions/smallrye-graphql/deployment/src/main/java/io/quarkus/smallrye/graphql/deployment/OverridableIndex.java @@ -2,6 +2,7 @@ import java.util.Collection; import java.util.Comparator; +import java.util.HashSet; import java.util.Set; import java.util.TreeSet; @@ -52,6 +53,18 @@ public Collection getAllKnownSubclasses(DotName dn) { return overrideCollection(original.getAllKnownSubclasses(dn), override.getAllKnownSubclasses(dn), classInfoComparator); } + @Override + public Collection getKnownDirectSubinterfaces(DotName dn) { + return overrideCollection(original.getKnownDirectSubinterfaces(dn), override.getKnownDirectSubinterfaces(dn), + classInfoComparator); + } + + @Override + public Collection getAllKnownSubinterfaces(DotName dn) { + return overrideCollection(original.getAllKnownSubinterfaces(dn), override.getAllKnownSubinterfaces(dn), + classInfoComparator); + } + @Override public Collection getKnownDirectImplementors(DotName dn) { return overrideCollection(original.getKnownDirectImplementors(dn), override.getKnownDirectImplementors(dn), @@ -90,6 +103,17 @@ public Collection getKnownUsers(DotName dn) { return overrideCollection(original.getKnownUsers(dn), override.getKnownUsers(dn), classInfoComparator); } + @Override + public Collection getClassesInPackage(DotName pn) { + return overrideCollection(original.getClassesInPackage(pn), override.getClassesInPackage(pn), classInfoComparator); + } + + @Override + public Set getSubpackages(DotName pn) { + return new HashSet<>(overrideCollection(original.getSubpackages(pn), override.getSubpackages(pn), + Comparator.naturalOrder())); + } + private Comparator classInfoComparator = new Comparator() { @Override public int compare(ClassInfo t, ClassInfo t1) { diff --git a/extensions/spring-boot-properties/runtime/pom.xml b/extensions/spring-boot-properties/runtime/pom.xml index eb83c846666e6..88d6eb8da01e9 100644 --- a/extensions/spring-boot-properties/runtime/pom.xml +++ b/extensions/spring-boot-properties/runtime/pom.xml @@ -41,7 +41,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/SpringDIProcessorTest.java b/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/SpringDIProcessorTest.java index 74eccaef4e87b..5791891b3fca2 100644 --- a/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/SpringDIProcessorTest.java +++ b/extensions/spring-di/deployment/src/test/java/io/quarkus/spring/di/deployment/SpringDIProcessorTest.java @@ -3,7 +3,6 @@ import static org.junit.jupiter.api.Assertions.assertEquals; import java.io.IOException; -import java.io.InputStream; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; @@ -26,8 +25,8 @@ import org.jboss.jandex.AnnotationValue; import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; import org.jboss.jandex.IndexView; -import org.jboss.jandex.Indexer; import org.jboss.jandex.MethodInfo; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.Test; @@ -39,7 +38,6 @@ import io.quarkus.arc.processor.BeanArchives; import io.quarkus.arc.processor.DotNames; -import io.quarkus.deployment.util.IoUtil; /** * @author
Brent Douglas @@ -205,17 +203,12 @@ public void getAnnotationsToAddBeanMethodWithScope() { } private IndexView getIndex(final Class... classes) { - final Indexer indexer = new Indexer(); - for (final Class clazz : classes) { - final String className = clazz.getName(); - try (InputStream stream = IoUtil.readClass(getClass().getClassLoader(), className)) { - final ClassInfo beanInfo = indexer.index(stream); - } catch (IOException e) { - throw new IllegalStateException("Failed to index: " + className, e); - } + try { + Index index = Index.of(classes); + return BeanArchives.buildBeanArchiveIndex(getClass().getClassLoader(), new ConcurrentHashMap<>(), index); + } catch (IOException e) { + throw new IllegalStateException("Failed to index classes", e); } - return BeanArchives.buildBeanArchiveIndex(getClass().getClassLoader(), new ConcurrentHashMap<>(), - indexer.complete()); } @SafeVarargs diff --git a/extensions/spring-scheduled/deployment/src/test/java/io/quarkus/spring/scheduled/deployment/SpringScheduledProcessorTest.java b/extensions/spring-scheduled/deployment/src/test/java/io/quarkus/spring/scheduled/deployment/SpringScheduledProcessorTest.java index ad55c1f7ece30..3abb0f2f3334b 100644 --- a/extensions/spring-scheduled/deployment/src/test/java/io/quarkus/spring/scheduled/deployment/SpringScheduledProcessorTest.java +++ b/extensions/spring-scheduled/deployment/src/test/java/io/quarkus/spring/scheduled/deployment/SpringScheduledProcessorTest.java @@ -4,7 +4,6 @@ import static org.junit.jupiter.api.Assertions.fail; import java.io.IOException; -import java.io.InputStream; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.TimeUnit; @@ -13,16 +12,14 @@ import org.jboss.jandex.AnnotationInstance; import org.jboss.jandex.AnnotationValue; -import org.jboss.jandex.ClassInfo; import org.jboss.jandex.DotName; +import org.jboss.jandex.Index; import org.jboss.jandex.IndexView; -import org.jboss.jandex.Indexer; import org.jboss.jandex.MethodInfo; import org.junit.jupiter.api.Test; import org.springframework.scheduling.annotation.Scheduled; import io.quarkus.arc.processor.BeanArchives; -import io.quarkus.deployment.util.IoUtil; public class SpringScheduledProcessorTest { @@ -116,17 +113,12 @@ public void testBuildDelayParamFromInvalidFormat() { } private IndexView getIndex(final Class... classes) { - final Indexer indexer = new Indexer(); - for (final Class clazz : classes) { - final String className = clazz.getName(); - try (InputStream stream = IoUtil.readClass(getClass().getClassLoader(), className)) { - final ClassInfo beanInfo = indexer.index(stream); - } catch (IOException e) { - throw new IllegalStateException("Failed to index: " + className, e); - } + try { + Index index = Index.of(classes); + return BeanArchives.buildBeanArchiveIndex(getClass().getClassLoader(), new ConcurrentHashMap<>(), index); + } catch (IOException e) { + throw new IllegalStateException("Failed to index classes", e); } - return BeanArchives.buildBeanArchiveIndex(getClass().getClassLoader(), new ConcurrentHashMap<>(), - indexer.complete()); } @ApplicationScoped diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventBusConsumer.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventBusConsumer.java index 046ef6886e5e4..a4069c07d4609 100644 --- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventBusConsumer.java +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/EventBusConsumer.java @@ -169,14 +169,14 @@ private static void implementInvoke(BeanInfo bean, MethodInfo method, ClassCreat // https://github.com/quarkusio/quarkus/issues/21621 Optional headerType = Optional.empty(); Type paramType; - if (method.parameters().size() == 2) { - Type firstParamType = method.parameters().get(0); + if (method.parametersCount() == 2) { + Type firstParamType = method.parameterType(0); if (VertxConstants.isMessageHeaders(firstParamType.name())) { headerType = Optional.of(firstParamType); } - paramType = method.parameters().get(1); + paramType = method.parameterType(1); } else { - paramType = method.parameters().get(0); + paramType = method.parameterType(0); } if (paramType.name().equals(MESSAGE)) { // io.vertx.core.eventbus.Message diff --git a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java index 43b5c21d71156..7eeb82fb9f917 100644 --- a/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java +++ b/extensions/vertx/deployment/src/main/java/io/quarkus/vertx/deployment/VertxProcessor.java @@ -125,7 +125,7 @@ void collectEventConsumers( AnnotationInstance consumeEvent = annotationStore.getAnnotation(method, CONSUME_EVENT); if (consumeEvent != null) { // Validate method params and return type - List params = method.parameters(); + List params = method.parameterTypes(); if (params.size() == 2) { if (!isMessageHeaders(params.get(0).name())) { // If there are two parameters, the first must be message headers. diff --git a/independent-projects/arc/pom.xml b/independent-projects/arc/pom.xml index ea45249f5d2db..9838bd1a6cf34 100644 --- a/independent-projects/arc/pom.xml +++ b/independent-projects/arc/pom.xml @@ -42,13 +42,13 @@ 2.0.2 1.3.3 - 2.4.3.Final + 3.0.0 5.9.0 3.8.6 3.23.1 3.5.0.Final 1.3.5 - 1.1.0.Final + 1.2.0.Final 2.2.3 1.6.0 @@ -85,7 +85,7 @@ - org.jboss + io.smallrye jandex ${version.jandex} diff --git a/independent-projects/arc/processor/pom.xml b/independent-projects/arc/processor/pom.xml index 84e264d8f9208..c0411c79cb570 100644 --- a/independent-projects/arc/processor/pom.xml +++ b/independent-projects/arc/processor/pom.xml @@ -31,7 +31,7 @@ - org.jboss + io.smallrye jandex diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AsmUtilCopy.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AsmUtilCopy.java index f5835c0fde19c..6b60e5587e664 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AsmUtilCopy.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/AsmUtilCopy.java @@ -9,6 +9,8 @@ import org.jboss.jandex.PrimitiveType.Primitive; import org.jboss.jandex.Type; import org.jboss.jandex.TypeVariable; +import org.jboss.jandex.TypeVariableReference; +import org.jboss.jandex.UnresolvedTypeVariable; /** * Copy of quarkus-core AsmUtil for some methods, with a tweak on the ARG_MAPPER (not name->String anymore) and @@ -146,8 +148,14 @@ else if (erased) sb.append("T").append(typeVariable.identifier()).append(";"); break; case UNRESOLVED_TYPE_VARIABLE: - // FIXME: ?? + // TODO no mapping, no support for "erased" + UnresolvedTypeVariable unresolvedTypeVariable = type.asUnresolvedTypeVariable(); + sb.append("T").append(unresolvedTypeVariable.identifier()).append(";"); break; + case TYPE_VARIABLE_REFERENCE: + // TODO no mapping, no support for "erased" + TypeVariableReference typeVariableReference = type.asTypeVariableReference(); + sb.append("T").append(typeVariableReference.identifier()).append(";"); case VOID: sb.append("V"); break; @@ -243,6 +251,7 @@ private static boolean needsSignature(Type type) { case PARAMETERIZED_TYPE: case TYPE_VARIABLE: case UNRESOLVED_TYPE_VARIABLE: + case TYPE_VARIABLE_REFERENCE: case WILDCARD_TYPE: return true; } diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java index e54908eb06fee..18f24553f034f 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/BeanArchives.java @@ -10,6 +10,7 @@ import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Modifier; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -17,7 +18,9 @@ import java.util.Iterator; import java.util.List; import java.util.Map; +import java.util.Objects; import java.util.Optional; +import java.util.Queue; import java.util.Set; import javax.enterprise.context.BeforeDestroyed; import javax.enterprise.context.Destroyed; @@ -144,6 +147,73 @@ public Collection getAllKnownSubclasses(DotName className) { return allKnown; } + private void getAllKnownSubClasses(DotName className, Set allKnown, Set processedClasses) { + final Set subClassesToProcess = new HashSet(); + subClassesToProcess.add(className); + while (!subClassesToProcess.isEmpty()) { + final Iterator toProcess = subClassesToProcess.iterator(); + DotName name = toProcess.next(); + toProcess.remove(); + processedClasses.add(name); + getAllKnownSubClasses(name, allKnown, subClassesToProcess, processedClasses); + } + } + + private void getAllKnownSubClasses(DotName name, Set allKnown, Set subClassesToProcess, + Set processedClasses) { + final Collection directSubclasses = getKnownDirectSubclasses(name); + if (directSubclasses != null) { + for (final ClassInfo clazz : directSubclasses) { + final DotName className = clazz.name(); + if (!processedClasses.contains(className)) { + allKnown.add(clazz); + subClassesToProcess.add(className); + } + } + } + } + + @Override + public Collection getKnownDirectSubinterfaces(DotName interfaceName) { + if (additionalClasses.isEmpty()) { + return index.getKnownDirectSubinterfaces(interfaceName); + } + Set directSubinterfaces = new HashSet<>(index.getKnownDirectSubinterfaces(interfaceName)); + for (Optional additional : additionalClasses.values()) { + if (additional.isPresent() && additional.get().interfaceNames().contains(interfaceName)) { + directSubinterfaces.add(additional.get()); + } + } + return directSubinterfaces; + } + + @Override + public Collection getAllKnownSubinterfaces(DotName interfaceName) { + if (additionalClasses.isEmpty()) { + return index.getAllKnownSubinterfaces(interfaceName); + } + + Set result = new HashSet<>(); + + Queue workQueue = new ArrayDeque<>(); + Set alreadyProcessed = new HashSet<>(); + + workQueue.add(interfaceName); + while (!workQueue.isEmpty()) { + DotName iface = workQueue.remove(); + if (!alreadyProcessed.add(iface)) { + continue; + } + + for (ClassInfo directSubinterface : getKnownDirectSubinterfaces(iface)) { + result.add(directSubinterface); + workQueue.add(directSubinterface.name()); + } + } + + return result; + } + @Override public Collection getKnownDirectImplementors(DotName className) { if (additionalClasses.isEmpty()) { @@ -183,6 +253,27 @@ public Collection getAllKnownImplementors(DotName interfaceName) { return allKnown; } + private void getKnownImplementors(DotName name, Set allKnown, Set subInterfacesToProcess, + Set processedClasses) { + final Collection list = getKnownDirectImplementors(name); + if (list != null) { + for (final ClassInfo clazz : list) { + final DotName className = clazz.name(); + if (!processedClasses.contains(className)) { + if (Modifier.isInterface(clazz.flags())) { + subInterfacesToProcess.add(className); + } else { + if (!allKnown.contains(clazz)) { + allKnown.add(clazz); + processedClasses.add(className); + getAllKnownSubClasses(className, allKnown, processedClasses); + } + } + } + } + } + } + @Override public Collection getAnnotations(DotName annotationName) { return index.getAnnotations(annotationName); @@ -208,51 +299,44 @@ public Collection getKnownUsers(DotName className) { return this.index.getKnownUsers(className); } - private void getAllKnownSubClasses(DotName className, Set allKnown, Set processedClasses) { - final Set subClassesToProcess = new HashSet(); - subClassesToProcess.add(className); - while (!subClassesToProcess.isEmpty()) { - final Iterator toProcess = subClassesToProcess.iterator(); - DotName name = toProcess.next(); - toProcess.remove(); - processedClasses.add(name); - getAllKnownSubClasses(name, allKnown, subClassesToProcess, processedClasses); + @Override + public Collection getClassesInPackage(DotName packageName) { + if (additionalClasses.isEmpty()) { + return index.getClassesInPackage(packageName); } - } - - private void getAllKnownSubClasses(DotName name, Set allKnown, Set subClassesToProcess, - Set processedClasses) { - final Collection directSubclasses = getKnownDirectSubclasses(name); - if (directSubclasses != null) { - for (final ClassInfo clazz : directSubclasses) { - final DotName className = clazz.name(); - if (!processedClasses.contains(className)) { - allKnown.add(clazz); - subClassesToProcess.add(className); - } + Set classesInPackage = new HashSet<>(index.getClassesInPackage(packageName)); + for (Optional additional : additionalClasses.values()) { + if (additional.isEmpty()) { + continue; + } + if (Objects.equals(packageName, additional.get().name().packagePrefixName())) { + classesInPackage.add(additional.get()); } } + return classesInPackage; } - private void getKnownImplementors(DotName name, Set allKnown, Set subInterfacesToProcess, - Set processedClasses) { - final Collection list = getKnownDirectImplementors(name); - if (list != null) { - for (final ClassInfo clazz : list) { - final DotName className = clazz.name(); - if (!processedClasses.contains(className)) { - if (Modifier.isInterface(clazz.flags())) { - subInterfacesToProcess.add(className); - } else { - if (!allKnown.contains(clazz)) { - allKnown.add(clazz); - processedClasses.add(className); - getAllKnownSubClasses(className, allKnown, processedClasses); - } - } + @Override + public Set getSubpackages(DotName packageName) { + if (additionalClasses.isEmpty()) { + return index.getSubpackages(packageName); + } + Set subpackages = new HashSet<>(index.getSubpackages(packageName)); + for (Optional additional : additionalClasses.values()) { + if (additional.isEmpty()) { + continue; + } + DotName pkg = additional.get().name().packagePrefixName(); + while (pkg != null) { + DotName superPkg = pkg.packagePrefixName(); + if (superPkg != null && superPkg.equals(packageName)) { + subpackages.add(pkg); } + pkg = superPkg; } + } + return subpackages; } private Optional computeAdditional(DotName className) { diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java index 0095e82894e5d..623dc14c5589a 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Methods.java @@ -438,6 +438,7 @@ static DotName toRawType(Type a) { return a.asParameterizedType().name(); case TYPE_VARIABLE: case UNRESOLVED_TYPE_VARIABLE: + case TYPE_VARIABLE_REFERENCE: case WILDCARD_TYPE: default: return DotNames.OBJECT; diff --git a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java index 59202d6cdf3f8..a17f01ed58dbb 100644 --- a/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java +++ b/independent-projects/arc/processor/src/main/java/io/quarkus/arc/processor/Types.java @@ -224,8 +224,8 @@ private static void getTypeHandle(AssignableResultHandle variable, BytecodeCreat default: throw new IllegalArgumentException("Unsupported primitive type: " + type); } - } else if (Kind.UNRESOLVED_TYPE_VARIABLE.equals(type.kind())) { - String identifier = type.asUnresolvedTypeVariable().identifier(); + } else if (Kind.TYPE_VARIABLE_REFERENCE.equals(type.kind())) { + String identifier = type.asTypeVariableReference().identifier(); TypeVariableInfo recursive = TypeVariableInfo.find(identifier, typeVariableStack); if (recursive != null) { ResultHandle typeVariableHandle = creator.newInstance( diff --git a/independent-projects/bootstrap/app-model/pom.xml b/independent-projects/bootstrap/app-model/pom.xml index ed97e4bac6473..937d0784c806f 100644 --- a/independent-projects/bootstrap/app-model/pom.xml +++ b/independent-projects/bootstrap/app-model/pom.xml @@ -86,7 +86,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/independent-projects/bootstrap/pom.xml b/independent-projects/bootstrap/pom.xml index 1a6374d70b395..0fac28e2f3ab4 100644 --- a/independent-projects/bootstrap/pom.xml +++ b/independent-projects/bootstrap/pom.xml @@ -36,7 +36,7 @@ 11 3.0.0-M7 1.6.8 - 1.2.3 + 3.0.0 3.23.1 @@ -83,9 +83,9 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin - ${jandex-maven-plugin.version} + ${jandex.version} maven-javadoc-plugin diff --git a/independent-projects/enforcer-rules/src/main/resources/enforcer-rules/quarkus-banned-dependencies.xml b/independent-projects/enforcer-rules/src/main/resources/enforcer-rules/quarkus-banned-dependencies.xml index 805b934d866db..30f9458575ccd 100644 --- a/independent-projects/enforcer-rules/src/main/resources/enforcer-rules/quarkus-banned-dependencies.xml +++ b/independent-projects/enforcer-rules/src/main/resources/enforcer-rules/quarkus-banned-dependencies.xml @@ -100,6 +100,8 @@ org.jboss.modules:jboss-modules org.javassist:javassist + + org.jboss:jandex diff --git a/independent-projects/qute/generator/pom.xml b/independent-projects/qute/generator/pom.xml index e0b861f993875..25eb37333cf27 100644 --- a/independent-projects/qute/generator/pom.xml +++ b/independent-projects/qute/generator/pom.xml @@ -23,7 +23,7 @@ jboss-logging - org.jboss + io.smallrye jandex diff --git a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java index 3dfeebfea4bc7..e4257de003936 100644 --- a/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java +++ b/independent-projects/qute/generator/src/main/java/io/quarkus/qute/generator/ValueResolverGenerator.java @@ -994,6 +994,7 @@ private boolean skipMemberType(Type type) { case ARRAY: case TYPE_VARIABLE: case UNRESOLVED_TYPE_VARIABLE: + case TYPE_VARIABLE_REFERENCE: case WILDCARD_TYPE: return true; default: diff --git a/independent-projects/qute/pom.xml b/independent-projects/qute/pom.xml index 0fa65445960e5..b6c9de8348830 100644 --- a/independent-projects/qute/pom.xml +++ b/independent-projects/qute/pom.xml @@ -42,8 +42,8 @@ 11 5.9.0 3.23.1 - 2.4.3.Final - 1.1.0.Final + 3.0.0 + 1.2.0.Final 3.5.0.Final 3.0.0-M7 1.6.8 @@ -86,7 +86,7 @@ ${version.jboss-logging} - org.jboss + io.smallrye jandex ${version.jandex} diff --git a/independent-projects/resteasy-reactive/client/processor/pom.xml b/independent-projects/resteasy-reactive/client/processor/pom.xml index dd20ccce3997c..513306efc372b 100644 --- a/independent-projects/resteasy-reactive/client/processor/pom.xml +++ b/independent-projects/resteasy-reactive/client/processor/pom.xml @@ -15,7 +15,7 @@ - org.jboss + io.smallrye jandex diff --git a/independent-projects/resteasy-reactive/common/processor/pom.xml b/independent-projects/resteasy-reactive/common/processor/pom.xml index cdad011e35ef8..69fed04ce862a 100644 --- a/independent-projects/resteasy-reactive/common/processor/pom.xml +++ b/independent-projects/resteasy-reactive/common/processor/pom.xml @@ -15,7 +15,7 @@ - org.jboss + io.smallrye jandex diff --git a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java index c12c423c62737..cc3bc29b06939 100644 --- a/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java +++ b/independent-projects/resteasy-reactive/common/processor/src/main/java/org/jboss/resteasy/reactive/common/processor/AsmUtil.java @@ -11,6 +11,7 @@ import org.jboss.jandex.Type; import org.jboss.jandex.Type.Kind; import org.jboss.jandex.TypeVariable; +import org.jboss.jandex.TypeVariableReference; import org.jboss.jandex.UnresolvedTypeVariable; import org.jboss.jandex.WildcardType; import org.objectweb.asm.MethodVisitor; @@ -216,6 +217,17 @@ private static void toSignature(StringBuilder sb, Type type, Function getAllKnownSubclasses(DotName className) { return allKnown; } + private void getAllKnownSubClasses(DotName className, Set allKnown, Set processedClasses) { + final Set subClassesToProcess = new HashSet(); + subClassesToProcess.add(className); + while (!subClassesToProcess.isEmpty()) { + final Iterator toProcess = subClassesToProcess.iterator(); + DotName name = toProcess.next(); + toProcess.remove(); + processedClasses.add(name); + getAllKnownSubClasses(name, allKnown, subClassesToProcess, processedClasses); + } + } + + private void getAllKnownSubClasses(DotName name, Set allKnown, Set subClassesToProcess, + Set processedClasses) { + final Collection directSubclasses = getKnownDirectSubclasses(name); + if (directSubclasses != null) { + for (final ClassInfo clazz : directSubclasses) { + final DotName className = clazz.name(); + if (!processedClasses.contains(className)) { + allKnown.add(clazz); + subClassesToProcess.add(className); + } + } + } + } + + @Override + public Collection getKnownDirectSubinterfaces(DotName interfaceName) { + if (additionalClasses.isEmpty()) { + return index.getKnownDirectSubinterfaces(interfaceName); + } + Set directSubinterfaces = new HashSet<>(index.getKnownDirectSubinterfaces(interfaceName)); + for (Optional additional : additionalClasses.values()) { + if (additional.isPresent() && additional.get().interfaceNames().contains(interfaceName)) { + directSubinterfaces.add(additional.get()); + } + } + return directSubinterfaces; + } + + @Override + public Collection getAllKnownSubinterfaces(DotName interfaceName) { + if (additionalClasses.isEmpty()) { + return index.getAllKnownSubinterfaces(interfaceName); + } + + Set result = new HashSet<>(); + + Queue workQueue = new ArrayDeque<>(); + Set alreadyProcessed = new HashSet<>(); + + workQueue.add(interfaceName); + while (!workQueue.isEmpty()) { + DotName iface = workQueue.remove(); + if (!alreadyProcessed.add(iface)) { + continue; + } + + for (ClassInfo directSubinterface : getKnownDirectSubinterfaces(iface)) { + result.add(directSubinterface); + workQueue.add(directSubinterface.name()); + } + } + + return result; + } + @Override public Collection getKnownDirectImplementors(DotName className) { if (additionalClasses.isEmpty()) { @@ -124,6 +194,27 @@ public Collection getAllKnownImplementors(DotName interfaceName) { return allKnown; } + private void getKnownImplementors(DotName name, Set allKnown, Set subInterfacesToProcess, + Set processedClasses) { + final Collection list = getKnownDirectImplementors(name); + if (list != null) { + for (final ClassInfo clazz : list) { + final DotName className = clazz.name(); + if (!processedClasses.contains(className)) { + if (Modifier.isInterface(clazz.flags())) { + subInterfacesToProcess.add(className); + } else { + if (!allKnown.contains(clazz)) { + allKnown.add(clazz); + processedClasses.add(className); + getAllKnownSubClasses(className, allKnown, processedClasses); + } + } + } + } + } + } + @Override public Collection getAnnotations(DotName annotationName) { return index.getAnnotations(annotationName); @@ -149,51 +240,44 @@ public Collection getKnownUsers(DotName className) { return this.index.getKnownUsers(className); } - private void getAllKnownSubClasses(DotName className, Set allKnown, Set processedClasses) { - final Set subClassesToProcess = new HashSet(); - subClassesToProcess.add(className); - while (!subClassesToProcess.isEmpty()) { - final Iterator toProcess = subClassesToProcess.iterator(); - DotName name = toProcess.next(); - toProcess.remove(); - processedClasses.add(name); - getAllKnownSubClasses(name, allKnown, subClassesToProcess, processedClasses); + @Override + public Collection getClassesInPackage(DotName packageName) { + if (additionalClasses.isEmpty()) { + return index.getClassesInPackage(packageName); } - } - - private void getAllKnownSubClasses(DotName name, Set allKnown, Set subClassesToProcess, - Set processedClasses) { - final Collection directSubclasses = getKnownDirectSubclasses(name); - if (directSubclasses != null) { - for (final ClassInfo clazz : directSubclasses) { - final DotName className = clazz.name(); - if (!processedClasses.contains(className)) { - allKnown.add(clazz); - subClassesToProcess.add(className); - } + Set classesInPackage = new HashSet<>(index.getClassesInPackage(packageName)); + for (Optional additional : additionalClasses.values()) { + if (additional.isEmpty()) { + continue; + } + if (Objects.equals(packageName, additional.get().name().packagePrefixName())) { + classesInPackage.add(additional.get()); } } + return classesInPackage; } - private void getKnownImplementors(DotName name, Set allKnown, Set subInterfacesToProcess, - Set processedClasses) { - final Collection list = getKnownDirectImplementors(name); - if (list != null) { - for (final ClassInfo clazz : list) { - final DotName className = clazz.name(); - if (!processedClasses.contains(className)) { - if (Modifier.isInterface(clazz.flags())) { - subInterfacesToProcess.add(className); - } else { - if (!allKnown.contains(clazz)) { - allKnown.add(clazz); - processedClasses.add(className); - getAllKnownSubClasses(className, allKnown, processedClasses); - } - } + @Override + public Set getSubpackages(DotName packageName) { + if (additionalClasses.isEmpty()) { + return index.getSubpackages(packageName); + } + Set subpackages = new HashSet<>(index.getSubpackages(packageName)); + for (Optional additional : additionalClasses.values()) { + if (additional.isEmpty()) { + continue; + } + DotName pkg = additional.get().name().packagePrefixName(); + while (pkg != null) { + DotName superPkg = pkg.packagePrefixName(); + if (superPkg != null && superPkg.equals(packageName)) { + subpackages.add(pkg); } + pkg = superPkg; } + } + return subpackages; } private Optional computeAdditional(DotName className) { diff --git a/independent-projects/resteasy-reactive/pom.xml b/independent-projects/resteasy-reactive/pom.xml index 690c7443b5aef..513c58149396e 100644 --- a/independent-projects/resteasy-reactive/pom.xml +++ b/independent-projects/resteasy-reactive/pom.xml @@ -41,14 +41,14 @@ 11 2.0.2 - 2.4.3.Final + 3.0.0 1.12.12 5.9.0 3.8.6 3.23.1 3.5.0.Final 1.3.5 - 1.1.0.Final + 1.2.0.Final 2.2.3 3.0.0-M7 @@ -154,7 +154,7 @@ - org.jboss + io.smallrye jandex ${jandex.version} diff --git a/independent-projects/resteasy-reactive/server/processor/pom.xml b/independent-projects/resteasy-reactive/server/processor/pom.xml index 16d112c07e8c1..c4623210140c9 100644 --- a/independent-projects/resteasy-reactive/server/processor/pom.xml +++ b/independent-projects/resteasy-reactive/server/processor/pom.xml @@ -15,7 +15,7 @@ - org.jboss + io.smallrye jandex diff --git a/independent-projects/tools/pom.xml b/independent-projects/tools/pom.xml index ecbf1f2673203..84fd8512e6d30 100644 --- a/independent-projects/tools/pom.xml +++ b/independent-projects/tools/pom.xml @@ -62,7 +62,7 @@ 20 2.11.0 1.13.1 - 1.2.3 + 3.0.0 registry-client diff --git a/independent-projects/tools/registry-client/pom.xml b/independent-projects/tools/registry-client/pom.xml index f3efab5cb315e..84c6a71a14aa8 100644 --- a/independent-projects/tools/registry-client/pom.xml +++ b/independent-projects/tools/registry-client/pom.xml @@ -44,9 +44,9 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin - ${jandex-maven-plugin.version} + ${jandex.version} make-index diff --git a/integration-tests/common-jpa-entities/pom.xml b/integration-tests/common-jpa-entities/pom.xml index d5ee8734e20b1..b029b6d3d3ddc 100644 --- a/integration-tests/common-jpa-entities/pom.xml +++ b/integration-tests/common-jpa-entities/pom.xml @@ -24,7 +24,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/integration-tests/gradle/src/main/resources/jandex-basic-multi-module-project/common/build.gradle b/integration-tests/gradle/src/main/resources/jandex-basic-multi-module-project/common/build.gradle index 19a292e288dec..02bd53a2ac5f2 100644 --- a/integration-tests/gradle/src/main/resources/jandex-basic-multi-module-project/common/build.gradle +++ b/integration-tests/gradle/src/main/resources/jandex-basic-multi-module-project/common/build.gradle @@ -1,9 +1,13 @@ plugins { id 'io.quarkus' id 'java-library' - id "org.kordamp.gradle.jandex" version "0.13.0" + id "org.kordamp.gradle.jandex" version "1.0.0" } dependencies { -} \ No newline at end of file +} + +jandex { + version = "3.0.0" +} diff --git a/integration-tests/jpa-mapping-xml/modern-library-a/pom.xml b/integration-tests/jpa-mapping-xml/modern-library-a/pom.xml index 4ae2ab65279cb..971f7fb8c8d10 100644 --- a/integration-tests/jpa-mapping-xml/modern-library-a/pom.xml +++ b/integration-tests/jpa-mapping-xml/modern-library-a/pom.xml @@ -44,7 +44,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/integration-tests/jpa-mapping-xml/modern-library-b/pom.xml b/integration-tests/jpa-mapping-xml/modern-library-b/pom.xml index d231faaffbe7d..5ccb00936280c 100644 --- a/integration-tests/jpa-mapping-xml/modern-library-b/pom.xml +++ b/integration-tests/jpa-mapping-xml/modern-library-b/pom.xml @@ -44,7 +44,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/jakarta/rewrite.yml b/jakarta/rewrite.yml index 8b37a399dfdc6..a3e3bfbbb9ca9 100644 --- a/jakarta/rewrite.yml +++ b/jakarta/rewrite.yml @@ -544,7 +544,7 @@ recipeList: newValue: 6.0.0-RC4 - org.openrewrite.maven.ChangePropertyValue: key: smallrye-graphql.version - newValue: 2.0.0.RC8 + newValue: 2.0.0.RC9 - org.openrewrite.maven.ChangePropertyValue: key: smallrye-health.version newValue: 4.0.0 @@ -582,10 +582,10 @@ recipeList: newValue: 4.0.1 - org.openrewrite.maven.ChangePropertyValue: key: smallrye-metrics.version - newValue: 4.0.0-RC1 + newValue: 4.0.0 - org.openrewrite.maven.ChangePropertyValue: key: smallrye-open-api.version - newValue: 3.0.0-RC4 + newValue: 3.0.0 - org.openrewrite.maven.ChangePropertyValue: key: microprofile-opentracing-api.version newValue: 3.0 diff --git a/tcks/microprofile-fault-tolerance/pom.xml b/tcks/microprofile-fault-tolerance/pom.xml index 6236ed6dd6cad..0035bad6ad1f6 100644 --- a/tcks/microprofile-fault-tolerance/pom.xml +++ b/tcks/microprofile-fault-tolerance/pom.xml @@ -37,9 +37,6 @@ org.eclipse.microprofile.fault.tolerance.tck.interceptor.xmlInterceptorEnabling.FaultToleranceInterceptorEnableByXmlTest - - org.eclipse.microprofile.fault.tolerance.tck.invalidParameters.InvalidCircuitBreakerFailureRatioNegTest - org.eclipse.microprofile.fault.tolerance.tck.invalidParameters.InvalidCircuitBreakerFailureRatioPosTest diff --git a/tcks/microprofile-rest-client-reactive/pom.xml b/tcks/microprofile-rest-client-reactive/pom.xml index f1892acafbac3..78639150f1b62 100644 --- a/tcks/microprofile-rest-client-reactive/pom.xml +++ b/tcks/microprofile-rest-client-reactive/pom.xml @@ -101,9 +101,8 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin - ${jandex-maven-plugin.version} make-index diff --git a/test-framework/common/pom.xml b/test-framework/common/pom.xml index 9f4a830fe20d6..7ace31a5d36a4 100644 --- a/test-framework/common/pom.xml +++ b/test-framework/common/pom.xml @@ -27,7 +27,7 @@ quarkus-bootstrap-gradle-resolver - org.jboss + io.smallrye jandex diff --git a/test-framework/security-jwt/pom.xml b/test-framework/security-jwt/pom.xml index 8bd1a0123be40..0aa072bb0146d 100644 --- a/test-framework/security-jwt/pom.xml +++ b/test-framework/security-jwt/pom.xml @@ -38,7 +38,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/test-framework/security-oidc/pom.xml b/test-framework/security-oidc/pom.xml index 095270779e35a..27aba21bfe3f4 100644 --- a/test-framework/security-oidc/pom.xml +++ b/test-framework/security-oidc/pom.xml @@ -38,7 +38,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/test-framework/security-webauthn/pom.xml b/test-framework/security-webauthn/pom.xml index 807d734ea3b95..38923a799df21 100644 --- a/test-framework/security-webauthn/pom.xml +++ b/test-framework/security-webauthn/pom.xml @@ -48,7 +48,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin diff --git a/test-framework/security/pom.xml b/test-framework/security/pom.xml index 56d34268f01da..56420535a7f2a 100644 --- a/test-framework/security/pom.xml +++ b/test-framework/security/pom.xml @@ -38,7 +38,7 @@ - org.jboss.jandex + io.smallrye jandex-maven-plugin From 9a74058898a405118b3630903ce28c684f38470a Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 15 Sep 2022 17:57:53 +0200 Subject: [PATCH 28/36] Jakarta - Transform some references in build.gradle --- jakarta/transform.sh | 3 +++ 1 file changed, 3 insertions(+) diff --git a/jakarta/transform.sh b/jakarta/transform.sh index af6ba984ead8e..b19632b0c6625 100755 --- a/jakarta/transform.sh +++ b/jakarta/transform.sh @@ -300,6 +300,9 @@ sed -i 's@org.jboss.narayana.rts:narayana-lra@org.jboss.narayana.rts:narayana-lr sed -i 's@org.jboss.narayana.rts:lra-client@org.jboss.narayana.rts:lra-client-jakarta@g' extensions/narayana-lra/runtime/pom.xml sed -i 's@META-INF/services/javax.ws.rs.client.ClientBuilder@META-INF/services/jakarta.ws.rs.client.ClientBuilder@g' extensions/narayana-lra/runtime/pom.xml +find integration-tests/gradle -name build.gradle | xargs sed -i 's/javax.enterprise.context.ApplicationScoped/jakarta.enterprise.context.ApplicationScoped/g' +find integration-tests/gradle -name build.gradle | xargs sed -i 's/javax.ws.rs.Path/jakarta.ws.rs.Path/g' + transform_documentation sed -i 's@javax/ws/rs@jakarta/ws/rs@g' docs/src/main/asciidoc/resteasy-reactive.adoc sed -i 's@https://javadoc.io/doc/jakarta.ws.rs/jakarta.ws.rs-api/2.1.1@https://javadoc.io/doc/jakarta.ws.rs/jakarta.ws.rs-api/3.1.0@g' docs/src/main/asciidoc/resteasy-reactive.adoc From 42b876ab6afaa6e1fd0b8707a36255e7838ee39f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Thu, 15 Sep 2022 17:58:40 +0200 Subject: [PATCH 29/36] Jakarta - Update the RESTEasy Reactive TCK --- jakarta/rewrite.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jakarta/rewrite.yml b/jakarta/rewrite.yml index 98c4e0c465eac..c7c02bb31f30d 100644 --- a/jakarta/rewrite.yml +++ b/jakarta/rewrite.yml @@ -677,7 +677,7 @@ recipeList: newValue: 3.0 - org.openrewrite.maven.ChangePropertyValue: key: resteasy-reactive-testsuite.repo.ref - newValue: c5785f3465fa87395574fde1274c1712b3aa728b + newValue: 4116f1a0c5605ad00d7779367dac8002af8c6882 --- type: specs.openrewrite.org/v1beta/recipe name: io.quarkus.maven.javax.managed From 7164593a0edbb59fda69d6d6b6cb9eb18b59a4ad Mon Sep 17 00:00:00 2001 From: shjones Date: Thu, 15 Sep 2022 17:59:09 +0100 Subject: [PATCH 30/36] QDOCS-31: Fix image alignment --- .../asciidoc/security-openid-connect-web-authentication.adoc | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc index 7f5a410e8c0c0..559905d7aad4c 100644 --- a/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc +++ b/docs/src/main/asciidoc/security-openid-connect-web-authentication.adoc @@ -15,8 +15,7 @@ After authentication, the OIDC provider redirects the user back to the applicati The following diagram outlines the Authorization Code Flow mechanism in Quarkus. .Authorization code flow mechanism in Quarkus -image::authorization_code_flow.png[alt=Authorization Code Flow, align=center] - +image::authorization_code_flow.png[alt=Authorization Code Flow, role="center"] . The Quarkus user requests access to a Quarkus web-app application. . The Quarkus web-app redirects the user to the authorization endpoint, that is, the OIDC provider for authentication. From 4de08609400c88d7fa8952f76f464d66f3561e2f Mon Sep 17 00:00:00 2001 From: Bill Burke Date: Thu, 15 Sep 2022 13:33:56 -0400 Subject: [PATCH 31/36] Stable Funqy Knative Events --- .../runtime/src/main/resources/META-INF/quarkus-extension.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/extensions/funqy/funqy-knative-events/runtime/src/main/resources/META-INF/quarkus-extension.yaml b/extensions/funqy/funqy-knative-events/runtime/src/main/resources/META-INF/quarkus-extension.yaml index d307ac236dbbd..eee08b1544b0d 100644 --- a/extensions/funqy/funqy-knative-events/runtime/src/main/resources/META-INF/quarkus-extension.yaml +++ b/extensions/funqy/funqy-knative-events/runtime/src/main/resources/META-INF/quarkus-extension.yaml @@ -9,7 +9,7 @@ metadata: guide: "https://quarkus.io/guides/funqy-knative-events" categories: - "cloud" - status: "experimental" + status: "stable" codestart: name: "funqy-knative-events" kind: "example" From 09320790012a0f7b64d161577079d8572231cd58 Mon Sep 17 00:00:00 2001 From: Georgios Andrianakis Date: Thu, 15 Sep 2022 16:07:55 +0300 Subject: [PATCH 32/36] Ensure that @ClientHeaderParam works when the rest client contains a method with a primitive return type Relates to: #27959 --- .../deployment/MicroProfileRestClientEnricher.java | 2 +- .../rest/client/reactive/subresource/SubResourceTest.java | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java index 176c3721cdd70..738cc489ee0ea 100644 --- a/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java +++ b/extensions/resteasy-reactive/rest-client-reactive/deployment/src/main/java/io/quarkus/rest/client/reactive/deployment/MicroProfileRestClientEnricher.java @@ -445,7 +445,7 @@ private String mockInterface(ClassInfo declaringClass, BuildProducer Date: Thu, 15 Sep 2022 21:26:34 +0000 Subject: [PATCH 33/36] Bump wiremock-jre8-standalone from 2.33.2 to 2.34.0 Bumps [wiremock-jre8-standalone](https://github.com/wiremock/wiremock) from 2.33.2 to 2.34.0. - [Release notes](https://github.com/wiremock/wiremock/releases) - [Commits](https://github.com/wiremock/wiremock/compare/2.33.2...2.34.0) --- updated-dependencies: - dependency-name: com.github.tomakehurst:wiremock-jre8-standalone dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- build-parent/pom.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build-parent/pom.xml b/build-parent/pom.xml index ea7c0e5e4a45e..ad56d5e5d144d 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -111,7 +111,7 @@ 3.23.1 - 2.33.2 + 2.34.0 7.2.0 From ef78d38a46249d8c9ab4fbcabe01e744d8949524 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Thu, 15 Sep 2022 22:37:05 +0100 Subject: [PATCH 34/36] Bump Keycloak version to 19.0.2 --- bom/application/pom.xml | 2 +- build-parent/pom.xml | 2 +- .../deployment/devservices/keycloak/DevServicesConfig.java | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/bom/application/pom.xml b/bom/application/pom.xml index 10a20945120bd..6a7f80a6cbd6b 100644 --- a/bom/application/pom.xml +++ b/bom/application/pom.xml @@ -177,7 +177,7 @@ 5.8.0 4.9.2 1.1.4.Final - 19.0.1 + 19.0.2 1.15.0 3.25.0 2.15.0 diff --git a/build-parent/pom.xml b/build-parent/pom.xml index ea7c0e5e4a45e..195dcd03b888e 100644 --- a/build-parent/pom.xml +++ b/build-parent/pom.xml @@ -103,7 +103,7 @@ - 19.0.1 + 19.0.2 quay.io/keycloak/keycloak:${keycloak.version} quay.io/keycloak/keycloak:${keycloak.version}-legacy diff --git a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java index 27590f08142b4..c6a052ad3b0cd 100644 --- a/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java +++ b/extensions/oidc/deployment/src/main/java/io/quarkus/oidc/deployment/devservices/keycloak/DevServicesConfig.java @@ -27,7 +27,7 @@ public class DevServicesConfig { * * Image with a Quarkus based distribution is used by default. * Image with a WildFly based distribution can be selected instead, for example: - * 'quay.io/keycloak/keycloak:19.0.1-legacy'. + * 'quay.io/keycloak/keycloak:19.0.2-legacy'. *

* Note Keycloak Quarkus and Keycloak WildFly images are initialized differently. * By default, Dev Services for Keycloak will assume it is a Keycloak Quarkus image if the image version does not end with a @@ -35,7 +35,7 @@ public class DevServicesConfig { * string. * Set 'quarkus.keycloak.devservices.keycloak-x-image' to override this check. */ - @ConfigItem(defaultValue = "quay.io/keycloak/keycloak:19.0.1") + @ConfigItem(defaultValue = "quay.io/keycloak/keycloak:19.0.2") public String imageName; /** From 1a9c550603f2d983736d4d902ba36eb87f771575 Mon Sep 17 00:00:00 2001 From: Sergey Beryozkin Date: Fri, 16 Sep 2022 10:56:19 +0100 Subject: [PATCH 35/36] Remove keycloak-admin-client dep from the keycloak-authorization test --- .../keycloak-authorization/pom.xml | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/integration-tests/keycloak-authorization/pom.xml b/integration-tests/keycloak-authorization/pom.xml index 89aa96d57ce33..c2ad8eeac7c0a 100644 --- a/integration-tests/keycloak-authorization/pom.xml +++ b/integration-tests/keycloak-authorization/pom.xml @@ -51,24 +51,6 @@ rest-assured test - - org.keycloak - keycloak-admin-client - - - jakarta.activation - jakarta.activation-api - - - org.jboss.spec.javax.ws.rs - jboss-jaxrs-api_3.0_spec - - - com.sun.activation - jakarta.activation - - - From ce3d92669555459a4ba425f36cfb307c7ea5577f Mon Sep 17 00:00:00 2001 From: Guillaume Smet Date: Fri, 16 Sep 2022 14:05:02 +0200 Subject: [PATCH 36/36] Revert "Narayana recovery manager service" --- .../agroal/deployment/AgroalProcessor.java | 3 --- .../quarkus/agroal/runtime/DataSources.java | 15 +++--------- .../jta/deployment/NarayanaInitBuildItem.java | 9 -------- .../jta/deployment/NarayanaJtaProcessor.java | 23 +------------------ .../jta/runtime/NarayanaJtaProducers.java | 8 ++----- .../jta/runtime/NarayanaJtaRecorder.java | 19 +-------------- .../TransactionManagerConfiguration.java | 22 ------------------ 7 files changed, 7 insertions(+), 92 deletions(-) delete mode 100644 extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaInitBuildItem.java diff --git a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java index e90502dc4dfe2..769ad707fd29e 100644 --- a/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java +++ b/extensions/agroal/deployment/src/main/java/io/quarkus/agroal/deployment/AgroalProcessor.java @@ -40,7 +40,6 @@ import io.quarkus.deployment.Feature; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.annotations.Consume; import io.quarkus.deployment.annotations.ExecutionTime; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.ExtensionSslNativeSupportBuildItem; @@ -49,7 +48,6 @@ import io.quarkus.deployment.builditem.nativeimage.NativeImageResourceBuildItem; import io.quarkus.deployment.builditem.nativeimage.ReflectiveClassBuildItem; import io.quarkus.deployment.pkg.builditem.CurateOutcomeBuildItem; -import io.quarkus.narayana.jta.deployment.NarayanaInitBuildItem; import io.quarkus.runtime.configuration.ConfigurationException; import io.quarkus.smallrye.health.deployment.spi.HealthBuildItem; @@ -223,7 +221,6 @@ void generateDataSourceSupportBean(AgroalRecorder recorder, @Record(ExecutionTime.RUNTIME_INIT) @BuildStep - @Consume(NarayanaInitBuildItem.class) void generateDataSourceBeans(AgroalRecorder recorder, DataSourcesRuntimeConfig dataSourcesRuntimeConfig, List aggregatedBuildTimeConfigBuildItems, diff --git a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java index 9adbe0abf066c..5008a18b6da99 100644 --- a/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java +++ b/extensions/agroal/runtime/src/main/java/io/quarkus/agroal/runtime/DataSources.java @@ -22,7 +22,6 @@ import javax.transaction.TransactionSynchronizationRegistry; import org.jboss.logging.Logger; -import org.jboss.tm.XAResourceRecoveryRegistry; import io.agroal.api.AgroalDataSource; import io.agroal.api.AgroalPoolInterceptor; @@ -73,7 +72,6 @@ public class DataSources { private final DataSourcesJdbcBuildTimeConfig dataSourcesJdbcBuildTimeConfig; private final DataSourcesJdbcRuntimeConfig dataSourcesJdbcRuntimeConfig; private final TransactionManager transactionManager; - private final XAResourceRecoveryRegistry xaResourceRecoveryRegistry; private final TransactionSynchronizationRegistry transactionSynchronizationRegistry; private final DataSourceSupport dataSourceSupport; private final Instance agroalPoolInterceptors; @@ -84,7 +82,6 @@ public DataSources(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, DataSourcesRuntimeConfig dataSourcesRuntimeConfig, DataSourcesJdbcBuildTimeConfig dataSourcesJdbcBuildTimeConfig, DataSourcesJdbcRuntimeConfig dataSourcesJdbcRuntimeConfig, TransactionManager transactionManager, - XAResourceRecoveryRegistry xaResourceRecoveryRegistry, TransactionSynchronizationRegistry transactionSynchronizationRegistry, DataSourceSupport dataSourceSupport, @Any Instance agroalPoolInterceptors) { this.dataSourcesBuildTimeConfig = dataSourcesBuildTimeConfig; @@ -92,7 +89,6 @@ public DataSources(DataSourcesBuildTimeConfig dataSourcesBuildTimeConfig, this.dataSourcesJdbcBuildTimeConfig = dataSourcesJdbcBuildTimeConfig; this.dataSourcesJdbcRuntimeConfig = dataSourcesJdbcRuntimeConfig; this.transactionManager = transactionManager; - this.xaResourceRecoveryRegistry = xaResourceRecoveryRegistry; this.transactionSynchronizationRegistry = transactionSynchronizationRegistry; this.dataSourceSupport = dataSourceSupport; this.agroalPoolInterceptors = agroalPoolInterceptors; @@ -272,10 +268,7 @@ private void applyNewConfiguration(AgroalDataSourceConfigurationSupplier dataSou if (dataSourceJdbcBuildTimeConfig.transactions != io.quarkus.agroal.runtime.TransactionIntegration.DISABLED) { TransactionIntegration txIntegration = new NarayanaTransactionIntegration(transactionManager, - transactionSynchronizationRegistry, null, false, - dataSourceJdbcBuildTimeConfig.transactions == io.quarkus.agroal.runtime.TransactionIntegration.XA - ? xaResourceRecoveryRegistry - : null); + transactionSynchronizationRegistry); poolConfiguration.transactionIntegration(txIntegration); } @@ -294,14 +287,12 @@ private void applyNewConfiguration(AgroalDataSourceConfigurationSupplier dataSou // Authentication if (dataSourceRuntimeConfig.username.isPresent()) { - NamePrincipal username = new NamePrincipal(dataSourceRuntimeConfig.username.get()); connectionFactoryConfiguration - .principal(username).recoveryPrincipal(username); + .principal(new NamePrincipal(dataSourceRuntimeConfig.username.get())); } if (dataSourceRuntimeConfig.password.isPresent()) { - SimplePassword password = new SimplePassword(dataSourceRuntimeConfig.password.get()); connectionFactoryConfiguration - .credential(password).recoveryCredential(password); + .credential(new SimplePassword(dataSourceRuntimeConfig.password.get())); } // credentials provider diff --git a/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaInitBuildItem.java b/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaInitBuildItem.java deleted file mode 100644 index 5cbee9c7ce40d..0000000000000 --- a/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaInitBuildItem.java +++ /dev/null @@ -1,9 +0,0 @@ -package io.quarkus.narayana.jta.deployment; - -import io.quarkus.builder.item.EmptyBuildItem; - -/** - * Marker build item that indicates that the Narayana JTA extension has been initialized. - */ -public final class NarayanaInitBuildItem extends EmptyBuildItem { -} diff --git a/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java b/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java index 18e88ec312178..397f771bdfc79 100644 --- a/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java +++ b/extensions/narayana-jta/deployment/src/main/java/io/quarkus/narayana/jta/deployment/NarayanaJtaProcessor.java @@ -10,20 +10,11 @@ import javax.transaction.TransactionScoped; import com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean; -import com.arjuna.ats.arjuna.recovery.TransactionStatusConnectionManager; import com.arjuna.ats.internal.arjuna.coordinator.CheckedActionFactoryImple; import com.arjuna.ats.internal.arjuna.objectstore.ShadowNoFileLockStore; -import com.arjuna.ats.internal.arjuna.recovery.AtomicActionExpiryScanner; -import com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule; -import com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner; import com.arjuna.ats.internal.arjuna.utils.SocketProcessId; import com.arjuna.ats.internal.jta.recovery.arjunacore.CommitMarkableResourceRecordRecoveryModule; -import com.arjuna.ats.internal.jta.recovery.arjunacore.JTAActionStatusServiceXAResourceOrphanFilter; -import com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter; -import com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter; import com.arjuna.ats.internal.jta.recovery.arjunacore.RecoverConnectableAtomicAction; -import com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule; -import com.arjuna.ats.internal.jta.resources.arjunacore.XAResourceRecord; import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple; import com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionSynchronizationRegistryImple; import com.arjuna.ats.internal.jta.transaction.arjunacore.UserTransactionImple; @@ -41,7 +32,6 @@ import io.quarkus.deployment.IsTest; import io.quarkus.deployment.annotations.BuildProducer; import io.quarkus.deployment.annotations.BuildStep; -import io.quarkus.deployment.annotations.Produce; import io.quarkus.deployment.annotations.Record; import io.quarkus.deployment.builditem.FeatureBuildItem; import io.quarkus.deployment.builditem.ShutdownContextBuildItem; @@ -74,7 +64,6 @@ public NativeImageSystemPropertyBuildItem nativeImageSystemPropertyBuildItem() { @BuildStep @Record(RUNTIME_INIT) - @Produce(NarayanaInitBuildItem.class) public void build(NarayanaJtaRecorder recorder, BuildProducer additionalBeans, BuildProducer reflectiveClass, @@ -92,9 +81,6 @@ public void build(NarayanaJtaRecorder recorder, runtimeInit.produce(new RuntimeInitializedClassBuildItem(SocketProcessId.class.getName())); runtimeInit.produce(new RuntimeInitializedClassBuildItem(CommitMarkableResourceRecordRecoveryModule.class.getName())); runtimeInit.produce(new RuntimeInitializedClassBuildItem(RecoverConnectableAtomicAction.class.getName())); - runtimeInit.produce(new RuntimeInitializedClassBuildItem(TransactionStatusConnectionManager.class.getName())); - runtimeInit.produce(new RuntimeInitializedClassBuildItem(JTAActionStatusServiceXAResourceOrphanFilter.class.getName())); - runtimeInit.produce(new RuntimeInitializedClassBuildItem(AtomicActionExpiryScanner.class.getName())); reflectiveClass.produce(new ReflectiveClassBuildItem(false, false, JTAEnvironmentBean.class.getName(), UserTransactionImple.class.getName(), @@ -103,14 +89,7 @@ public void build(NarayanaJtaRecorder recorder, TransactionSynchronizationRegistryImple.class.getName(), ObjectStoreEnvironmentBean.class.getName(), ShadowNoFileLockStore.class.getName(), - SocketProcessId.class.getName(), - AtomicActionRecoveryModule.class.getName(), - XARecoveryModule.class.getName(), - XAResourceRecord.class.getName(), - JTATransactionLogXAResourceOrphanFilter.class.getName(), - JTANodeNameXAResourceOrphanFilter.class.getName(), - JTAActionStatusServiceXAResourceOrphanFilter.class.getName(), - ExpiredTransactionStatusManagerScanner.class.getName())); + SocketProcessId.class.getName())); AdditionalBeanBuildItem.Builder builder = AdditionalBeanBuildItem.builder(); builder.addBeanClass(TransactionalInterceptorSupports.class); diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaProducers.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaProducers.java index abfb7d0d36f97..85e4b99d2e17e 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaProducers.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaProducers.java @@ -3,7 +3,6 @@ import javax.enterprise.context.ApplicationScoped; import javax.enterprise.context.Dependent; import javax.enterprise.inject.Produces; -import javax.inject.Singleton; import javax.transaction.TransactionSynchronizationRegistry; import org.jboss.tm.JBossXATerminator; @@ -33,12 +32,9 @@ public javax.transaction.UserTransaction userTransaction() { } @Produces - @Singleton + @ApplicationScoped public XAResourceRecoveryRegistry xaResourceRecoveryRegistry() { - RecoveryManagerService recoveryManagerService = new RecoveryManagerService(); - recoveryManagerService.create(); - recoveryManagerService.start(); - return recoveryManagerService; + return new RecoveryManagerService(); } @Produces diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java index 82d2e61b95ec4..b66dd67487d37 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/NarayanaJtaRecorder.java @@ -7,15 +7,10 @@ import org.jboss.logging.Logger; import com.arjuna.ats.arjuna.common.CoreEnvironmentBeanException; -import com.arjuna.ats.arjuna.common.ObjectStoreEnvironmentBean; -import com.arjuna.ats.arjuna.common.RecoveryEnvironmentBean; import com.arjuna.ats.arjuna.common.arjPropertyManager; import com.arjuna.ats.arjuna.coordinator.TransactionReaper; import com.arjuna.ats.arjuna.coordinator.TxControl; -import com.arjuna.ats.arjuna.recovery.RecoveryManager; -import com.arjuna.ats.jta.common.JTAEnvironmentBean; import com.arjuna.ats.jta.common.jtaPropertyManager; -import com.arjuna.common.internal.util.propertyservice.BeanPopulator; import com.arjuna.common.util.propertyservice.PropertiesFactory; import io.quarkus.runtime.ShutdownContext; @@ -72,25 +67,13 @@ public void disableTransactionStatusManager() { } public void setConfig(final TransactionManagerConfiguration transactions) { - BeanPopulator.getDefaultInstance(ObjectStoreEnvironmentBean.class) - .setObjectStoreDir(transactions.objectStoreDirectory); - BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "communicationStore") - .setObjectStoreDir(transactions.objectStoreDirectory); - BeanPopulator.getNamedInstance(ObjectStoreEnvironmentBean.class, "stateStore") - .setObjectStoreDir(transactions.objectStoreDirectory); - BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class) - .setRecoveryModuleClassNames(transactions.recoveryModules); - BeanPopulator.getDefaultInstance(RecoveryEnvironmentBean.class) - .setExpiryScannerClassNames(transactions.expiryScanners); - BeanPopulator.getDefaultInstance(JTAEnvironmentBean.class) - .setXaResourceOrphanFilterClassNames(transactions.xaResourceOrphanFilters); + arjPropertyManager.getObjectStoreEnvironmentBean().setObjectStoreDir(transactions.objectStoreDirectory); } public void handleShutdown(ShutdownContext context) { context.addLastShutdownTask(new Runnable() { @Override public void run() { - RecoveryManager.manager().terminate(true); TransactionReaper.terminate(false); } }); diff --git a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerConfiguration.java b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerConfiguration.java index b4f8ab1ee8cc1..ee2c70841b308 100644 --- a/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerConfiguration.java +++ b/extensions/narayana-jta/runtime/src/main/java/io/quarkus/narayana/jta/runtime/TransactionManagerConfiguration.java @@ -1,7 +1,6 @@ package io.quarkus.narayana.jta.runtime; import java.time.Duration; -import java.util.List; import io.quarkus.runtime.annotations.ConfigItem; import io.quarkus.runtime.annotations.ConfigPhase; @@ -31,25 +30,4 @@ public final class TransactionManagerConfiguration { */ @ConfigItem(defaultValue = "ObjectStore") public String objectStoreDirectory; - - /** - * The list of recovery modules - */ - @ConfigItem(defaultValue = "com.arjuna.ats.internal.arjuna.recovery.AtomicActionRecoveryModule," + - "com.arjuna.ats.internal.jta.recovery.arjunacore.XARecoveryModule") - public List recoveryModules; - - /** - * The list of expiry scanners - */ - @ConfigItem(defaultValue = "com.arjuna.ats.internal.arjuna.recovery.ExpiredTransactionStatusManagerScanner") - public List expiryScanners; - - /** - * The list of orphan filters - */ - @ConfigItem(defaultValue = "com.arjuna.ats.internal.jta.recovery.arjunacore.JTATransactionLogXAResourceOrphanFilter," + - "com.arjuna.ats.internal.jta.recovery.arjunacore.JTANodeNameXAResourceOrphanFilter," + - "com.arjuna.ats.internal.jta.recovery.arjunacore.JTAActionStatusServiceXAResourceOrphanFilter") - public List xaResourceOrphanFilters; }