From 722daa45a256e0e3a0756ae41e3859f2807df485 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 17:28:22 +0200 Subject: [PATCH 01/54] Move OptionalMatcher and other JUnit features to common Remove OptionalMatcher from config testing Common Parameters module (no refactoring to it) --- bom/pom.xml | 10 + common/parameters/pom.xml | 47 ++++ .../helidon/common/parameters/Parameters.java | 228 ++++++++++++++++++ .../common/parameters/ParametersEmpty.java | 64 +++++ .../common/parameters/ParametersMap.java | 75 ++++++ .../parameters/ParametersSingleValueMap.java | 75 ++++++ .../common/parameters/package-info.java | 20 ++ .../parameters/src/main/java/module-info.java | 24 ++ .../common/parameters/ParametersTest.java | 113 +++++++++ common/pom.xml | 2 + common/testing/junit5/pom.xml | 44 ++++ .../testing/junit5}/OptionalMatcher.java | 14 +- .../junit5/RestoreSystemPropertiesExt.java | 60 +++++ .../testing/junit5/TemporaryFolderExt.java | 147 +++++++++++ .../common/testing/junit5/package-info.java | 30 +++ .../junit5/src/main/java/module-info.java | 27 +++ .../common/testing/OptionalMatcherTest.java | 71 ++++++ common/testing/pom.xml | 37 +++ config/hocon/pom.xml | 5 + .../hocon/HoconMediaTypeDetectorTest.java | 8 +- .../config/testing/OptionalMatcherTest.java | 73 ------ .../config/testing/ValueNodeMatcherTest.java | 42 ++++ config/yaml/pom.xml | 5 + .../yaml/YamlMediaTypeDetectorTest.java | 6 +- examples/security/google-login/pom.xml | 5 + .../examples/google/GoogleMainTest.java | 6 +- fault-tolerance/pom.xml | 4 +- .../faulttolerance/DelayRetryPolicyTest.java | 20 +- .../faulttolerance/JitterRetryPolicyTest.java | 34 +-- integrations/micronaut/data/pom.xml | 5 +- .../data/MicronautDataCdiExtensionTest.java | 6 +- microprofile/server/pom.xml | 4 +- .../server/JaxRsApplicationTest.java | 12 +- .../server/JaxRsCdiExtensionTest.java | 24 +- tests/integration/config/gh-2171-yml/pom.xml | 4 +- .../gh2171/yml/ConfigurationTest.java | 6 +- tests/integration/config/gh-2171/pom.xml | 4 +- .../integration/gh2171/ConfigurationTest.java | 12 +- .../vault/mp/TestKubernetesAuth.java | 4 +- webserver/webserver/pom.xml | 4 +- .../helidon/webserver/NettyWebServerTest.java | 4 +- .../webserver/NettyWebServerV2ApiTest.java | 4 +- .../webserver/SocketConfigurationTest.java | 8 +- .../SocketConfigurationV2ApiTest.java | 8 +- 44 files changed, 1231 insertions(+), 174 deletions(-) create mode 100644 common/parameters/pom.xml create mode 100644 common/parameters/src/main/java/io/helidon/common/parameters/Parameters.java create mode 100644 common/parameters/src/main/java/io/helidon/common/parameters/ParametersEmpty.java create mode 100644 common/parameters/src/main/java/io/helidon/common/parameters/ParametersMap.java create mode 100644 common/parameters/src/main/java/io/helidon/common/parameters/ParametersSingleValueMap.java create mode 100644 common/parameters/src/main/java/io/helidon/common/parameters/package-info.java create mode 100644 common/parameters/src/main/java/module-info.java create mode 100644 common/parameters/src/test/java/io/helidon/common/parameters/ParametersTest.java create mode 100644 common/testing/junit5/pom.xml rename {config/testing/src/main/java/io/helidon/config/testing => common/testing/junit5/src/main/java/io/helidon/common/testing/junit5}/OptionalMatcher.java (89%) create mode 100644 common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/RestoreSystemPropertiesExt.java create mode 100644 common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/TemporaryFolderExt.java create mode 100644 common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/package-info.java create mode 100644 common/testing/junit5/src/main/java/module-info.java create mode 100644 common/testing/junit5/src/test/java/io/helidon/common/testing/OptionalMatcherTest.java create mode 100644 common/testing/pom.xml delete mode 100644 config/testing/src/test/java/io/helidon/config/testing/OptionalMatcherTest.java create mode 100644 config/testing/src/test/java/io/helidon/config/testing/ValueNodeMatcherTest.java diff --git a/bom/pom.xml b/bom/pom.xml index 50cd58edc55..833e1473225 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -631,6 +631,16 @@ helidon-common-crypto ${helidon.version} + + io.helidon.common + helidon-common-parameters + ${helidon.version} + + + io.helidon.common.testing + helidon-common-testing-junit5 + ${helidon.version} + diff --git a/common/parameters/pom.xml b/common/parameters/pom.xml new file mode 100644 index 00000000000..39ddeae32ad --- /dev/null +++ b/common/parameters/pom.xml @@ -0,0 +1,47 @@ + + + + + 4.0.0 + + io.helidon.common + helidon-common-project + 4.0.0-SNAPSHOT + + helidon-common-parameters + Helidon Common Parameters + + + + io.helidon.common + helidon-common + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + diff --git a/common/parameters/src/main/java/io/helidon/common/parameters/Parameters.java b/common/parameters/src/main/java/io/helidon/common/parameters/Parameters.java new file mode 100644 index 00000000000..e531bc71d52 --- /dev/null +++ b/common/parameters/src/main/java/io/helidon/common/parameters/Parameters.java @@ -0,0 +1,228 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.parameters; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Objects; +import java.util.Optional; +import java.util.Set; +import java.util.function.Supplier; + +import io.helidon.common.GenericType; + +/** + * Parameters abstraction (used by any component that has named parameters with possible multiple values). + * This is a read-only access to the parameters. + */ +public interface Parameters { + /** + * Generic type for parameters. + */ + GenericType GENERIC_TYPE = GenericType.create(Parameters.class); + + /** + * Creates a new {@link Builder} of {@link io.helidon.common.parameters.Parameters}. + * + * @param component component these parameters represent (such as request headers) + * @return builder instance + */ + static Builder builder(String component) { + return new Builder(component); + } + + /** + * Create empty (named) parameters. + * + * @param component component of the parameters to correctly report errors + * @return new named parameters with no values + */ + static Parameters empty(String component) { + return new ParametersEmpty(component); + } + + /** + * Read only parameters based on a map. + * + * @param component component of the parameters to correctly report errors + * @param params underlying map + * @return new named parameters with values based on the map + */ + static Parameters create(String component, Map> params) { + return new ParametersMap(component, params); + } + + /** + * Read only parameters based on a map with just a single value. + * + * @param component component of the parameters to correctly report errors + * @param params underlying map + * @return new named parameters with values based on the map + */ + static Parameters createSingleValueMap(String component, Map params) { + return new ParametersSingleValueMap(component, params); + } + + /** + * Get all values. + * + * @param name name of the parameter + * @return all values as a list + * @throws NoSuchElementException in case the name is not present + */ + List all(String name) throws NoSuchElementException; + + /** + * Get all values using a default value supplier if the parameter does not exist. + * + * @param name name of the parameter + * @param defaultValues default values supplier to use if parameter is not present + * @return all values as a list + */ + default List all(String name, Supplier> defaultValues) { + if (contains(name)) { + return all(name); + } + return defaultValues.get(); + } + + /** + * Get the first value. + * + * @param name name of the parameter + * @return first value + * @throws NoSuchElementException in case the name is not present + */ + String value(String name) throws NoSuchElementException; + + /** + * Get the first value as an optional. + * Managing optionals has performance impact. If performance is of issue, use {@link #contains(String)} + * and {@link #value(String)} instead. + * + * @param name name of the parameter + * @return first value or empty optional + */ + default Optional first(String name) { + if (contains(name)) { + return Optional.of(value(name)); + } + return Optional.empty(); + } + + /** + * Whether these parameters contain the provided name. + * + * @param name name of the parameter + * @return {@code true} if the parameter is present, {@code false} otherwise + */ + boolean contains(String name); + + /** + * Whether these parameters are empty. + * + * @return {@code true} if there is no parameter defined + */ + default boolean isEmpty() { + return size() == 0; + } + + /** + * Number of parameter names in these parameters. + * + * @return size of these parameters + */ + int size(); + + /** + * Set of parameter names. + * + * @return names available in these parameters + */ + Set names(); + + /** + * Name of the component of these parameters. + * + * @return component name + */ + String component(); + + /** + * Get a map representation of these parameters. Changes to the map will not be propagated to this instance. + * + * @return a new map + */ + default Map> toMap() { + Map> result = new HashMap<>(); + + for (String name : names()) { + result.put(name, all(name)); + } + + return result; + } + + /** + * Builder of a new {@link io.helidon.common.parameters.Parameters} instance. + */ + class Builder implements io.helidon.common.Builder { + + private final Map> params = new LinkedHashMap<>(); + private final String component; + + private Builder(String component) { + this.component = component; + } + + @Override + public Parameters build() { + return Parameters.create(component, params); + } + + /** + * Add new value(s) to the parameters. If the name existed, values will be added. + * + * @param name parameter name + * @param values parameter value + * @return updated builder + */ + public Builder add(String name, String... values) { + Objects.requireNonNull(name); + params.computeIfAbsent(name, k -> new ArrayList<>()).addAll(Arrays.asList(values)); + return this; + } + + /** + * Set new value(s) to the parameters. If the name existed, values will be replaced. + * + * @param name parameter name + * @param values parameter value + * @return updated builder + */ + public Builder set(String name, String... values) { + Objects.requireNonNull(name); + params.put(name, new ArrayList<>(List.of(values))); + return this; + } + } +} diff --git a/common/parameters/src/main/java/io/helidon/common/parameters/ParametersEmpty.java b/common/parameters/src/main/java/io/helidon/common/parameters/ParametersEmpty.java new file mode 100644 index 00000000000..944f9a95d3b --- /dev/null +++ b/common/parameters/src/main/java/io/helidon/common/parameters/ParametersEmpty.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.parameters; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +class ParametersEmpty implements Parameters { + private final String component; + + ParametersEmpty(String component) { + this.component = component; + } + + @Override + public List all(String name) throws NoSuchElementException { + throw new NoSuchElementException("This " + component + " does not contain parameter named \"" + name + "\""); + } + + @Override + public String value(String name) throws NoSuchElementException { + throw new NoSuchElementException("This " + component + " does not contain parameter named \"" + name + "\""); + } + + @Override + public boolean contains(String name) { + return false; + } + + @Override + public int size() { + return 0; + } + + @Override + public Set names() { + return Set.of(); + } + + @Override + public String component() { + return component; + } + + @Override + public String toString() { + return component + ": "; + } +} diff --git a/common/parameters/src/main/java/io/helidon/common/parameters/ParametersMap.java b/common/parameters/src/main/java/io/helidon/common/parameters/ParametersMap.java new file mode 100644 index 00000000000..f30c4718e40 --- /dev/null +++ b/common/parameters/src/main/java/io/helidon/common/parameters/ParametersMap.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.parameters; + +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +class ParametersMap implements Parameters { + private final String component; + private final Map> params; + + ParametersMap(String component, Map> params) { + this.component = component; + this.params = params; + } + + @Override + public List all(String name) throws NoSuchElementException { + List value = params.get(name); + if (value == null) { + throw new NoSuchElementException("This " + component + " does not contain parameter named \"" + name + "\""); + } + return value; + } + + @Override + public String value(String name) throws NoSuchElementException { + List value = params.get(name); + if (value == null) { + throw new NoSuchElementException("This " + component + " does not contain parameter named \"" + name + "\""); + } + return value.get(0); + } + + @Override + public boolean contains(String name) { + return params.containsKey(name); + } + + @Override + public int size() { + return params.size(); + } + + @Override + public Set names() { + return Set.copyOf(params.keySet()); + } + + @Override + public String component() { + return component; + } + + @Override + public String toString() { + return component + ": " + params; + } +} diff --git a/common/parameters/src/main/java/io/helidon/common/parameters/ParametersSingleValueMap.java b/common/parameters/src/main/java/io/helidon/common/parameters/ParametersSingleValueMap.java new file mode 100644 index 00000000000..b120dbcd362 --- /dev/null +++ b/common/parameters/src/main/java/io/helidon/common/parameters/ParametersSingleValueMap.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.parameters; + +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +class ParametersSingleValueMap implements Parameters { + private final String component; + private final Map params; + + ParametersSingleValueMap(String component, Map params) { + this.component = component; + this.params = params; + } + + @Override + public List all(String name) throws NoSuchElementException { + String value = params.get(name); + if (value == null) { + throw new NoSuchElementException("This " + component + " does not contain parameter named \"" + name + "\""); + } + return List.of(value); + } + + @Override + public String value(String name) throws NoSuchElementException { + String value = params.get(name); + if (value == null) { + throw new NoSuchElementException("This " + component + " does not contain parameter named \"" + name + "\""); + } + return value; + } + + @Override + public boolean contains(String name) { + return params.containsKey(name); + } + + @Override + public int size() { + return params.size(); + } + + @Override + public Set names() { + return Set.copyOf(params.keySet()); + } + + @Override + public String component() { + return component; + } + + @Override + public String toString() { + return component + ": " + params; + } +} diff --git a/common/parameters/src/main/java/io/helidon/common/parameters/package-info.java b/common/parameters/src/main/java/io/helidon/common/parameters/package-info.java new file mode 100644 index 00000000000..ae5f005dd48 --- /dev/null +++ b/common/parameters/src/main/java/io/helidon/common/parameters/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Support for parameters with multiple values. + */ +package io.helidon.common.parameters; diff --git a/common/parameters/src/main/java/module-info.java b/common/parameters/src/main/java/module-info.java new file mode 100644 index 00000000000..8e96fed23dc --- /dev/null +++ b/common/parameters/src/main/java/module-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +/** + * Support for parameters with multiple values. + */ +module io.helidon.common.parameters { + // GenericType + requires transitive io.helidon.common; + + exports io.helidon.common.parameters; +} \ No newline at end of file diff --git a/common/parameters/src/test/java/io/helidon/common/parameters/ParametersTest.java b/common/parameters/src/test/java/io/helidon/common/parameters/ParametersTest.java new file mode 100644 index 00000000000..2ba45ee18c3 --- /dev/null +++ b/common/parameters/src/test/java/io/helidon/common/parameters/ParametersTest.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.common.parameters; + +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.containsString; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsCollectionWithSize.hasSize; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ParametersTest { + private static final String UNIT_TEST = "unit-test"; + + @Test + void testEmpty() { + Parameters empty = Parameters.empty(UNIT_TEST); + assertThat(empty.component(), is(UNIT_TEST)); + assertThat("Empty parameters should not contain anything", empty.contains("anything"), is(false)); + assertThat("Empty parameters should have size 0", empty.size(), is(0)); + assertThat("Empty parameters should have empty names", empty.names(), hasSize(0)); + assertThrows(NoSuchElementException.class, () -> empty.value("anything")); + assertThrows(NoSuchElementException.class, () -> empty.all("anything")); + assertThat(empty.toString(), containsString(UNIT_TEST)); + assertThat("Empty parameters should be empty", empty.isEmpty(), is(true)); + } + + @Test + void testMultiValueMap() { + Map> map = Map.of("something", List.of("first"), + "other", List.of("first", "second")); + + Parameters params = Parameters.create(UNIT_TEST, map); + assertThat(params.component(), is(UNIT_TEST)); + assertThat("Parameters should contain \"something\"", params.contains("something"), is(true)); + assertThat("Parameters should contain \"other\"", params.contains("other"), is(true)); + assertThat("Parameters should not contain \"anything\"", params.contains("anything"), is(false)); + assertThat("Parameters should have size 2", params.size(), is(2)); + assertThat("parameters should have correct names", params.names(), hasItems("something", "other")); + assertThrows(NoSuchElementException.class, () -> params.value("anything")); + assertThrows(NoSuchElementException.class, () -> params.all("anything")); + assertThat(params.value("something"), is("first")); + assertThat(params.value("other"), is("first")); + assertThat(params.all("something"), hasItems("first")); + assertThat(params.all("other"), hasItems("first", "second")); + assertThat(params.toString(), containsString(UNIT_TEST)); + assertThat("Parameters should not be empty", params.isEmpty(), is(false)); + } + + @Test + void testSingleValueMap() { + Map map = Map.of("something", "first", + "other", "first"); + + Parameters params = Parameters.createSingleValueMap(UNIT_TEST, map); + assertThat(params.component(), is(UNIT_TEST)); + assertThat("Parameters should contain \"something\"", params.contains("something"), is(true)); + assertThat("Parameters should contain \"other\"", params.contains("other"), is(true)); + assertThat("Parameters should not contain \"anything\"", params.contains("anything"), is(false)); + assertThat("Parameters should have size 2", params.size(), is(2)); + assertThat("parameters should have correct names", params.names(), hasItems("something", "other")); + assertThrows(NoSuchElementException.class, () -> params.value("anything")); + assertThrows(NoSuchElementException.class, () -> params.all("anything")); + assertThat(params.value("something"), is("first")); + assertThat(params.value("other"), is("first")); + assertThat(params.all("something"), hasItems("first")); + assertThat(params.all("other"), hasItems("first")); + assertThat(params.toString(), containsString(UNIT_TEST)); + assertThat("Parameters should not be empty", params.isEmpty(), is(false)); + } + + @Test + void testBuilder() { + Parameters params = Parameters.builder(UNIT_TEST) + .add("something", "first") + .add("other", "first", "second") + .build(); + + assertThat(params.component(), is(UNIT_TEST)); + assertThat("Parameters should contain \"something\"", params.contains("something"), is(true)); + assertThat("Parameters should contain \"other\"", params.contains("other"), is(true)); + assertThat("Parameters should not contain \"anything\"", params.contains("anything"), is(false)); + assertThat("Parameters should have size 2", params.size(), is(2)); + assertThat("parameters should have correct names", params.names(), hasItems("something", "other")); + assertThrows(NoSuchElementException.class, () -> params.value("anything")); + assertThrows(NoSuchElementException.class, () -> params.all("anything")); + assertThat(params.value("something"), is("first")); + assertThat(params.value("other"), is("first")); + assertThat(params.all("something"), hasItems("first")); + assertThat(params.all("other"), hasItems("first", "second")); + assertThat(params.toString(), containsString(UNIT_TEST)); + assertThat("Parameters should not be empty", params.isEmpty(), is(false)); + } +} \ No newline at end of file diff --git a/common/pom.xml b/common/pom.xml index 813c4ddadd6..3836b474235 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -40,5 +40,7 @@ mapper media-type crypto + parameters + testing diff --git a/common/testing/junit5/pom.xml b/common/testing/junit5/pom.xml new file mode 100644 index 00000000000..6e69104ccd1 --- /dev/null +++ b/common/testing/junit5/pom.xml @@ -0,0 +1,44 @@ + + + + + 4.0.0 + + io.helidon.common.testing + helidon-common-testing-project + 4.0.0-SNAPSHOT + + + helidon-common-testing-junit5 + Helidon Common Testing JUnit5 + + + + org.hamcrest + hamcrest-all + provided + + + org.junit.jupiter + junit-jupiter-api + provided + + + diff --git a/config/testing/src/main/java/io/helidon/config/testing/OptionalMatcher.java b/common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/OptionalMatcher.java similarity index 89% rename from config/testing/src/main/java/io/helidon/config/testing/OptionalMatcher.java rename to common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/OptionalMatcher.java index 182fc7d95c2..8c1c110dbd5 100644 --- a/config/testing/src/main/java/io/helidon/config/testing/OptionalMatcher.java +++ b/common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/OptionalMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -package io.helidon.config.testing; +package io.helidon.common.testing.junit5; import java.util.Optional; @@ -24,7 +24,7 @@ import static org.hamcrest.Matchers.any; /** - * Matchers for {@link java.util.Optional}. + * Hamcrest matchers for {@link java.util.Optional}. */ public final class OptionalMatcher { private OptionalMatcher() { @@ -36,13 +36,13 @@ private OptionalMatcher() { *

* Usage example: *

-     *     assertThat(myOptional, value(is("expected")));
+     *     assertThat(myOptional, present(is("expected")));
      * 
* @param matcher matcher to validate the content of the optional * @param type of the value * @return matcher validating the {@link java.util.Optional} is present and matches the provided matcher */ - public static Matcher> value(Matcher matcher) { + public static Matcher> optionalValue(Matcher matcher) { return new WithValue<>(matcher); } @@ -56,7 +56,7 @@ public static Matcher> value(Matcher matcher) { * @param type of the optional * @return matcher validating the {@link java.util.Optional} is empty */ - public static Matcher> empty() { + public static Matcher> optionalEmpty() { return new Empty<>(); } @@ -71,7 +71,7 @@ public static Matcher> empty() { * @param type of the value * @return matcher validating the {@link java.util.Optional} is present */ - public static Matcher> present() { + public static Matcher> optionalPresent() { return new WithValue<>(any(Object.class)); } diff --git a/common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/RestoreSystemPropertiesExt.java b/common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/RestoreSystemPropertiesExt.java new file mode 100644 index 00000000000..5b06f82710d --- /dev/null +++ b/common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/RestoreSystemPropertiesExt.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.testing.junit5; + +import java.util.Properties; + +import org.junit.jupiter.api.extension.AfterTestExecutionCallback; +import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; +import org.junit.jupiter.api.extension.ExtensionContext; +import org.junit.jupiter.api.extension.ExtensionContext.Namespace; +import org.junit.jupiter.api.extension.ExtensionContext.Store; + +/** + * JUnit 5 extension for preserving and restoring system properties around + * test executions. + *

+ * Annotate each test method that modifies system properties using + * @ExtendWith(RestoreSystemPropertiesExt.class) + * + */ +public class RestoreSystemPropertiesExt implements BeforeTestExecutionCallback, AfterTestExecutionCallback { + + private static final String SYSPROPS_KEY = "systemProps"; + + @Override + public void beforeTestExecution(ExtensionContext ec) throws Exception { + getStore(ec).put(SYSPROPS_KEY, System.getProperties()); + Properties copy = new Properties(); + copy.putAll(System.getProperties()); + System.setProperties(copy); + } + + @Override + public void afterTestExecution(ExtensionContext ec) throws Exception { + System.setProperties(getSavedProps(ec)); + } + + private Store getStore(ExtensionContext context) { + return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); + } + + private Properties getSavedProps(ExtensionContext ec) throws Exception { + Object o = getStore(ec).get(SYSPROPS_KEY); + return Properties.class.cast(o); + } +} diff --git a/common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/TemporaryFolderExt.java b/common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/TemporaryFolderExt.java new file mode 100644 index 00000000000..e5febeae1fb --- /dev/null +++ b/common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/TemporaryFolderExt.java @@ -0,0 +1,147 @@ +/* + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.testing.junit5; + +import java.io.File; +import java.io.IOException; +import java.nio.file.FileVisitResult; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.SimpleFileVisitor; +import java.nio.file.attribute.BasicFileAttributes; + +import org.junit.jupiter.api.extension.AfterEachCallback; +import org.junit.jupiter.api.extension.BeforeEachCallback; +import org.junit.jupiter.api.extension.ExtensionContext; + + +/** + * JUnit 5 extension for temporary folder operations. + *

+ * Declare the extension in a test using + *

+ * @RegisterExtension + *
static TemporaryFolderExt folder = TemporaryFolderExt.build(); + *
+ *

+ * The static is important. + *

+ * When a test needs a temporary folder it invokes folder.newFolder(). + *

+ * The extension automatically deletes the temporary files after all tests in + * the test class have finished. + * + */ +public class TemporaryFolderExt implements BeforeEachCallback, AfterEachCallback { + + private Path root; + + private TemporaryFolderExt() { + } + + /** + * Builds an instance of TemporaryFolderExt. + * @return a TemporaryFolderExt + */ + public static TemporaryFolderExt build() { + return new TemporaryFolderExt(); + } + + /** + * Creates a new temporary folder with a unique generated name. + * @return File for the newly-created temporary folder + * @throws java.io.IOException in case of error creating the new folder + */ + public File newFolder() throws IOException { + final Path tempPath = Files.createTempDirectory(root, "test"); + return tempPath.toFile(); + } + + /** + * Creates a new temporary folder with the specified name. + * @param name of the folder to create + * @return File for the new folder + * @throws java.io.IOException in case of error creating the new folder + */ + public File newFolder(String name) throws IOException { + int nameStart = (name.startsWith("/") ? 1 : 0); + return Files.createDirectory(root.resolve(name.substring(nameStart))).toFile(); + } + + /** + * Creates a new temporary file with a generated unique name. + * @return the new File + * @throws java.io.IOException in case of error creating the new file + */ + public File newFile() throws IOException { + return Files.createTempFile(root, "test", "file").toFile(); + } + + /** + * Creates a new temporary file with the specified name. + * @param name name to be used for the new file + * @return File for the newly-created file + * @throws java.io.IOException in case of error creating the new file + */ + public File newFile(String name) throws IOException { + int nameStart = (name.startsWith("/") ? 1 : 0); + return Files.createFile(root.resolve(name.substring(nameStart))).toFile(); + } + + /** + * The root for this test's temporary files. + * @return the root File + */ + public File getRoot() { + return root.toFile(); + } + + @Override + public void beforeEach(ExtensionContext ec) throws Exception { + root = Files.createTempDirectory("test"); + } + @Override + public void afterEach(ExtensionContext ec) throws Exception { + deleteDir(root); + } + + private static void deleteDir(Path dir) throws IOException { + Files.walkFileTree(dir, new SimpleFileVisitor() { + + @Override + public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { + if (!Files.isWritable(path)) { + //When you try to delete the file on Windows and it is marked as read-only + //it would fail unless this change + path.toFile().setWritable(true); + } + Files.delete(path); + return FileVisitResult.CONTINUE; + } + + @Override + public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { + if (exc != null) { + throw exc; + } else { + Files.delete(dir); + return FileVisitResult.CONTINUE; + } + } + }); + } +} diff --git a/common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/package-info.java b/common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/package-info.java new file mode 100644 index 00000000000..ae533033d3d --- /dev/null +++ b/common/testing/junit5/src/main/java/io/helidon/common/testing/junit5/package-info.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Basic testing support for Helidon. + *

+ * Hamcrest Matchers: + *

    + *
  • {@link io.helidon.common.testing.junit5.OptionalMatcher}
  • + *
+ * JUnit Extensions: + *
    + *
  • {@link io.helidon.common.testing.junit5.RestoreSystemPropertiesExt}
  • + *
  • {@link io.helidon.common.testing.junit5.TemporaryFolderExt}
  • + *
+ */ +package io.helidon.common.testing.junit5; diff --git a/common/testing/junit5/src/main/java/module-info.java b/common/testing/junit5/src/main/java/module-info.java new file mode 100644 index 00000000000..179183cb090 --- /dev/null +++ b/common/testing/junit5/src/main/java/module-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Hamcrest matchers. + * + * @see io.helidon.common.testing.junit5.OptionalMatcher + */ +module io.helidon.common.testing.junit5 { + requires hamcrest.all; + requires org.junit.jupiter.api; + + exports io.helidon.common.testing.junit5; +} \ No newline at end of file diff --git a/common/testing/junit5/src/test/java/io/helidon/common/testing/OptionalMatcherTest.java b/common/testing/junit5/src/test/java/io/helidon/common/testing/OptionalMatcherTest.java new file mode 100644 index 00000000000..ef3b9d32f5e --- /dev/null +++ b/common/testing/junit5/src/test/java/io/helidon/common/testing/OptionalMatcherTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.testing; + +import java.util.List; +import java.util.Optional; + +import org.junit.jupiter.api.Test; + +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalPresent; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; +import static org.hamcrest.CoreMatchers.any; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.startsWith; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class OptionalMatcherTest { + @Test + void testNull() { + Optional nullOptional = null; + + assertThrows(AssertionError.class, () -> assertThat(nullOptional, optionalEmpty())); + assertThrows(AssertionError.class, () -> assertThat(nullOptional, optionalPresent())); + assertThrows(AssertionError.class, () -> assertThat(nullOptional, optionalValue(any(Object.class)))); + } + @Test + void testEmpty() { + Optional empty = Optional.empty(); + assertThat(empty, is(optionalEmpty())); + assertThat(empty, not(optionalPresent())); + } + + @Test + void testPresent() { + Optional stringOptional = Optional.of("my-value"); + assertThat(stringOptional, not(optionalEmpty())); + assertThat(stringOptional, is(optionalPresent())); + } + + @Test + void testNestedStringMatcher() { + Optional stringOptional = Optional.of("my-value"); + assertThat(stringOptional, optionalValue(startsWith("my-"))); + assertThat(stringOptional, optionalValue(not(startsWith("her-")))); + } + + @Test + void testNestedListMatcher() { + Optional> listOptional = Optional.of(List.of("one", "two")); + assertThat(listOptional, optionalValue(hasItems("one", "two"))); + assertThat(listOptional, optionalValue(not(hasItems("one", "three")))); + } +} \ No newline at end of file diff --git a/common/testing/pom.xml b/common/testing/pom.xml new file mode 100644 index 00000000000..d5548ac6ef2 --- /dev/null +++ b/common/testing/pom.xml @@ -0,0 +1,37 @@ + + + + + 4.0.0 + + io.helidon.common + helidon-common-project + 4.0.0-SNAPSHOT + + + io.helidon.common.testing + helidon-common-testing-project + Helidon Common Testing Project + pom + + + junit5 + + diff --git a/config/hocon/pom.xml b/config/hocon/pom.xml index bcc4e0492d9..149f9bf3371 100644 --- a/config/hocon/pom.xml +++ b/config/hocon/pom.xml @@ -55,6 +55,11 @@ helidon-config-testing test
+ + io.helidon.common.testing + helidon-common-testing-junit5 + test + org.junit.jupiter junit-jupiter-api diff --git a/config/hocon/src/test/java/io/helidon/config/hocon/HoconMediaTypeDetectorTest.java b/config/hocon/src/test/java/io/helidon/config/hocon/HoconMediaTypeDetectorTest.java index ed3bd609af9..f801662f296 100644 --- a/config/hocon/src/test/java/io/helidon/config/hocon/HoconMediaTypeDetectorTest.java +++ b/config/hocon/src/test/java/io/helidon/config/hocon/HoconMediaTypeDetectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.value; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -34,11 +34,11 @@ public class HoconMediaTypeDetectorTest { @Test public void testProbeContentTypeHocon() { - assertThat(MediaTypes.detectType(Paths.get("config.conf")), value(is("application/hocon"))); + assertThat(MediaTypes.detectType(Paths.get("config.conf")), optionalValue(is("application/hocon"))); } @Test public void testProbeContentTypeJson() { - assertThat(MediaTypes.detectType(Paths.get("config.json")), value(is("application/json"))); + assertThat(MediaTypes.detectType(Paths.get("config.json")), optionalValue(is("application/json"))); } } diff --git a/config/testing/src/test/java/io/helidon/config/testing/OptionalMatcherTest.java b/config/testing/src/test/java/io/helidon/config/testing/OptionalMatcherTest.java deleted file mode 100644 index 854d2832bd8..00000000000 --- a/config/testing/src/test/java/io/helidon/config/testing/OptionalMatcherTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.config.testing; - -import java.util.Optional; - -import org.hamcrest.Matcher; -import org.junit.jupiter.api.Test; - -import static io.helidon.config.testing.OptionalMatcher.empty; -import static io.helidon.config.testing.OptionalMatcher.present; -import static io.helidon.config.testing.OptionalMatcher.value; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * Unit test for {@link OptionalMatcher}. - */ -class OptionalMatcherTest { - private static final Optional EMPTY = Optional.empty(); - private static final Optional NULL = null; - private static final Optional EXPECTED = Optional.of("expected"); - private static final Optional ACTUAL = Optional.of("actual"); - - @Test - void testEmpty() { - Matcher> matcher = empty(); - - assertThat(EMPTY, matcher); - assertFail(NULL, matcher); - assertFail(EXPECTED, matcher); - assertFail(ACTUAL, matcher); - } - - @Test - void testValue() { - Matcher> matcher = value(is("expected")); - - assertFail(EMPTY, matcher); - assertFail(NULL, matcher); - assertThat(EXPECTED, matcher); - assertFail(ACTUAL, matcher); - } - - @Test - void testPresent() { - Matcher> matcher = present(); - - assertFail(EMPTY, matcher); - assertFail(NULL, matcher); - assertThat(EXPECTED, matcher); - assertThat(ACTUAL, matcher); - } - - private static void assertFail(Optional actual, Matcher> matcher) { - assertThrows(AssertionError.class, () -> assertThat(actual, matcher)); - } -} diff --git a/config/testing/src/test/java/io/helidon/config/testing/ValueNodeMatcherTest.java b/config/testing/src/test/java/io/helidon/config/testing/ValueNodeMatcherTest.java new file mode 100644 index 00000000000..d91a7b88b33 --- /dev/null +++ b/config/testing/src/test/java/io/helidon/config/testing/ValueNodeMatcherTest.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.config.testing; + +import io.helidon.config.spi.ConfigNode; + +import org.junit.jupiter.api.Test; + +import static io.helidon.config.testing.ValueNodeMatcher.valueNode; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.MatcherAssert.assertThat; + +class ValueNodeMatcherTest { + @Test + void testValueNode() { + ConfigNode.ValueNode valueNode = ConfigNode.ValueNode.create("my-value"); + assertThat(valueNode, is(valueNode("my-value"))); + } + @Test + void testConfigNode() { + ConfigNode.ListNode listNode = ConfigNode.ListNode.builder() + .addValue("first") + .build(); + + assertThat(listNode, not(valueNode("first"))); + } +} \ No newline at end of file diff --git a/config/yaml/pom.xml b/config/yaml/pom.xml index c6d54e95667..960818dd79a 100644 --- a/config/yaml/pom.xml +++ b/config/yaml/pom.xml @@ -50,6 +50,11 @@ jakarta.annotation jakarta.annotation-api + + io.helidon.common.testing + helidon-common-testing-junit5 + test + io.helidon.config helidon-config-testing diff --git a/config/yaml/src/test/java/io/helidon/config/yaml/YamlMediaTypeDetectorTest.java b/config/yaml/src/test/java/io/helidon/config/yaml/YamlMediaTypeDetectorTest.java index 187450f90c2..caeb1f5708b 100644 --- a/config/yaml/src/test/java/io/helidon/config/yaml/YamlMediaTypeDetectorTest.java +++ b/config/yaml/src/test/java/io/helidon/config/yaml/YamlMediaTypeDetectorTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.value; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -33,7 +33,7 @@ public class YamlMediaTypeDetectorTest { @Test public void testProbeContentType() { - assertThat(MediaTypes.detectType(Paths.get("config.yaml")), value(is("application/x-yaml"))); + assertThat(MediaTypes.detectType(Paths.get("config.yaml")), optionalValue(is("application/x-yaml"))); } } diff --git a/examples/security/google-login/pom.xml b/examples/security/google-login/pom.xml index 1b589d7160a..c5b97e8a48b 100644 --- a/examples/security/google-login/pom.xml +++ b/examples/security/google-login/pom.xml @@ -79,6 +79,11 @@ helidon-webclient test + + io.helidon.common.testing + helidon-common-testing-junit5 + test + io.helidon.config helidon-config-testing diff --git a/examples/security/google-login/src/test/java/io/helidon/security/examples/google/GoogleMainTest.java b/examples/security/google-login/src/test/java/io/helidon/security/examples/google/GoogleMainTest.java index f85fce05f66..90ee2bdcefd 100644 --- a/examples/security/google-login/src/test/java/io/helidon/security/examples/google/GoogleMainTest.java +++ b/examples/security/google-login/src/test/java/io/helidon/security/examples/google/GoogleMainTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.value; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -66,7 +66,7 @@ public void testEndpoint() { .thenAccept(it -> { assertThat(it.status(), is(Http.Status.UNAUTHORIZED_401)); assertThat(it.headers().first(Http.Header.WWW_AUTHENTICATE), - value(is("Bearer realm=\"helidon\",scope=\"openid profile email\""))); + optionalValue(is("Bearer realm=\"helidon\",scope=\"openid profile email\""))); }) .await(); } diff --git a/fault-tolerance/pom.xml b/fault-tolerance/pom.xml index 497c2f78291..4cee896d62d 100644 --- a/fault-tolerance/pom.xml +++ b/fault-tolerance/pom.xml @@ -70,8 +70,8 @@ test - io.helidon.config - helidon-config-testing + io.helidon.common.testing + helidon-common-testing-junit5 test diff --git a/fault-tolerance/src/test/java/io/helidon/faulttolerance/DelayRetryPolicyTest.java b/fault-tolerance/src/test/java/io/helidon/faulttolerance/DelayRetryPolicyTest.java index 33c3c012839..4723fa1208a 100644 --- a/fault-tolerance/src/test/java/io/helidon/faulttolerance/DelayRetryPolicyTest.java +++ b/fault-tolerance/src/test/java/io/helidon/faulttolerance/DelayRetryPolicyTest.java @@ -21,8 +21,8 @@ import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.empty; -import static io.helidon.config.testing.OptionalMatcher.value; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -38,13 +38,13 @@ void testDelay() { long firstCall = System.nanoTime(); Optional delay = policy.nextDelayMillis(firstCall, 0, 0); - assertThat(delay, value(is(0L))); + assertThat(delay, optionalValue(is(0L))); delay = policy.nextDelayMillis(firstCall, delay.orElseThrow(), 1); - assertThat(delay, value(is(100L))); + assertThat(delay, optionalValue(is(100L))); delay = policy.nextDelayMillis(firstCall, delay.orElseThrow(), 2); // should just apply factor on last delay - assertThat(delay, value(is(300L))); + assertThat(delay, optionalValue(is(300L))); delay = policy.nextDelayMillis(firstCall, delay.orElseThrow(), 3); // limit of calls - assertThat(delay, is(empty())); + assertThat(delay, is(optionalEmpty())); } @Test @@ -58,12 +58,12 @@ void testNoDelay() { long firstCall = System.nanoTime(); Optional delay = policy.nextDelayMillis(firstCall, 0, 0); - assertThat(delay, value(is(0L))); + assertThat(delay, optionalValue(is(0L))); delay = policy.nextDelayMillis(firstCall, delay.orElseThrow(), 1); - assertThat(delay, value(is(0L))); + assertThat(delay, optionalValue(is(0L))); delay = policy.nextDelayMillis(firstCall, delay.orElseThrow(), 2); // should just apply factor on last delay - assertThat(delay, value(is(0L))); + assertThat(delay, optionalValue(is(0L))); delay = policy.nextDelayMillis(firstCall, delay.orElseThrow(), 3); // limit of calls - assertThat(delay, is(empty())); + assertThat(delay, is(optionalEmpty())); } } diff --git a/fault-tolerance/src/test/java/io/helidon/faulttolerance/JitterRetryPolicyTest.java b/fault-tolerance/src/test/java/io/helidon/faulttolerance/JitterRetryPolicyTest.java index 10257de8b66..93b1e9065d0 100644 --- a/fault-tolerance/src/test/java/io/helidon/faulttolerance/JitterRetryPolicyTest.java +++ b/fault-tolerance/src/test/java/io/helidon/faulttolerance/JitterRetryPolicyTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,9 +23,9 @@ import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.empty; -import static io.helidon.config.testing.OptionalMatcher.present; -import static io.helidon.config.testing.OptionalMatcher.value; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalPresent; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.both; import static org.hamcrest.Matchers.greaterThan; @@ -43,9 +43,9 @@ void testFixedDelay() { long firstCall = System.nanoTime(); Optional aLong = policy.nextDelayMillis(firstCall, 0, 0); - assertThat(aLong, value(is(100L))); + assertThat(aLong, optionalValue(is(100L))); aLong = policy.nextDelayMillis(firstCall, aLong.get(), 1); - assertThat(aLong, value(is(100L))); + assertThat(aLong, optionalValue(is(100L))); } @Test @@ -59,11 +59,11 @@ void testRepeats() { for (int i = 0; i < 100; i++) { long firstCall = System.nanoTime(); // running in cycle to ensure we do not store state - assertThat(policy.nextDelayMillis(firstCall, 0, 1), value(is(100L))); - assertThat(policy.nextDelayMillis(firstCall, 0, 2), value(is(100L))); - assertThat(policy.nextDelayMillis(firstCall, 0, 3), is(empty())); - assertThat(policy.nextDelayMillis(firstCall, 0, 1), value(is(100L))); - assertThat(policy.nextDelayMillis(firstCall, 0, 3), is(empty())); + assertThat(policy.nextDelayMillis(firstCall, 0, 1), optionalValue(is(100L))); + assertThat(policy.nextDelayMillis(firstCall, 0, 2), optionalValue(is(100L))); + assertThat(policy.nextDelayMillis(firstCall, 0, 3), is(optionalEmpty())); + assertThat(policy.nextDelayMillis(firstCall, 0, 1), optionalValue(is(100L))); + assertThat(policy.nextDelayMillis(firstCall, 0, 3), is(optionalEmpty())); } } @@ -81,7 +81,7 @@ void testRandomDelay() { boolean hadPositive = false; for (int i = 0; i < 10000; i++) { Optional aLong = policy.nextDelayMillis(firstCall, 0, 0); - assertThat(aLong, present()); + assertThat(aLong, optionalPresent()); long value = aLong.get(); assertThat(value, is(both(greaterThan(49L)).and(lessThan(151L)))); if (value < 100) { @@ -109,7 +109,7 @@ void testNoDelayJitter() { boolean hadZero = false; for (int i = 0; i < 10000; i++) { Optional aLong = policy.nextDelayMillis(firstCall, 0, 0); - assertThat(aLong, present()); + assertThat(aLong, optionalPresent()); long value = aLong.get(); assertThat(value, is(both(greaterThan(-1L)).and(lessThan(50L)))); if (value == 0) { @@ -135,12 +135,12 @@ void testNoDelay() { long firstCall = System.nanoTime(); Optional aLong = policy.nextDelayMillis(firstCall, 0, 0); - assertThat(aLong, value(is(0L))); + assertThat(aLong, optionalValue(is(0L))); aLong = policy.nextDelayMillis(firstCall, 0, 1); - assertThat(aLong, value(is(0L))); + assertThat(aLong, optionalValue(is(0L))); aLong = policy.nextDelayMillis(firstCall, 0, 2); // should just apply factor on last delay - assertThat(aLong, value(is(0L))); + assertThat(aLong, optionalValue(is(0L))); aLong = policy.nextDelayMillis(firstCall, 100, 3); // limit of calls - assertThat(aLong, is(empty())); + assertThat(aLong, is(optionalEmpty())); } } diff --git a/integrations/micronaut/data/pom.xml b/integrations/micronaut/data/pom.xml index 854e437a7de..da3e8975c4c 100644 --- a/integrations/micronaut/data/pom.xml +++ b/integrations/micronaut/data/pom.xml @@ -110,9 +110,8 @@ test - io.helidon.config - helidon-config-testing - test + io.helidon.common.testing + helidon-common-testing-junit5 org.hamcrest diff --git a/integrations/micronaut/data/src/test/java/io/helidon/integrations/micronaut/cdi/data/MicronautDataCdiExtensionTest.java b/integrations/micronaut/data/src/test/java/io/helidon/integrations/micronaut/cdi/data/MicronautDataCdiExtensionTest.java index f18f9405c57..918330ed44f 100644 --- a/integrations/micronaut/data/src/test/java/io/helidon/integrations/micronaut/cdi/data/MicronautDataCdiExtensionTest.java +++ b/integrations/micronaut/data/src/test/java/io/helidon/integrations/micronaut/cdi/data/MicronautDataCdiExtensionTest.java @@ -37,7 +37,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.present; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalPresent; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.notNullValue; @@ -58,7 +58,7 @@ class MicronautDataCdiExtensionTest { @Test void testPet() { Optional dinoOptional = petRepository.findByName("Dino"); - assertThat(dinoOptional, is(present())); + assertThat(dinoOptional, is(optionalPresent())); Pet pet = dinoOptional.get(); assertThat(pet.getName(), is("Dino")); @@ -70,7 +70,7 @@ void testPet() { @Test void testOwner() { Optional maybeBarney = ownerRepository.findByName("Barney"); - assertThat(maybeBarney, is(present())); + assertThat(maybeBarney, is(optionalPresent())); Owner barney = maybeBarney.get(); assertThat(barney.getName(), is("Barney")); diff --git a/microprofile/server/pom.xml b/microprofile/server/pom.xml index 603d6140229..f182e4402e3 100644 --- a/microprofile/server/pom.xml +++ b/microprofile/server/pom.xml @@ -122,8 +122,8 @@ test - io.helidon.config - helidon-config-testing + io.helidon.common.testing + helidon-common-testing-junit5 test diff --git a/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsApplicationTest.java b/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsApplicationTest.java index 727872f2236..dbf8600d2ed 100644 --- a/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsApplicationTest.java +++ b/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsApplicationTest.java @@ -20,7 +20,7 @@ import jakarta.ws.rs.core.Application; import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.value; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -36,14 +36,14 @@ static class MyApplication extends Application { @Test public void testContextRootNormalization() { JaxRsApplication app1 = JaxRsApplication.builder().contextRoot("/").build(); - assertThat(app1.contextRoot(), value(is("/"))); + assertThat(app1.contextRoot(), optionalValue(is("/"))); JaxRsApplication app2 = JaxRsApplication.builder().contextRoot("/foo").build(); - assertThat(app2.contextRoot(), value(is("/foo"))); + assertThat(app2.contextRoot(), optionalValue(is("/foo"))); JaxRsApplication app3 = JaxRsApplication.builder().contextRoot("/foo/").build(); - assertThat(app3.contextRoot(), value(is("/foo"))); + assertThat(app3.contextRoot(), optionalValue(is("/foo"))); JaxRsApplication app4 = JaxRsApplication.builder().contextRoot("/foo/bar/").build(); - assertThat(app4.contextRoot(), value(is("/foo/bar"))); + assertThat(app4.contextRoot(), optionalValue(is("/foo/bar"))); JaxRsApplication app5 = JaxRsApplication.builder().application(MyApplication.class).build(); - assertThat(app5.contextRoot(), value(is("/foo/bar"))); + assertThat(app5.contextRoot(), optionalValue(is("/foo/bar"))); } } diff --git a/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsCdiExtensionTest.java b/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsCdiExtensionTest.java index 8ec064fb34b..2e8ab965a3b 100644 --- a/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsCdiExtensionTest.java +++ b/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsCdiExtensionTest.java @@ -25,8 +25,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.empty; -import static io.helidon.config.testing.OptionalMatcher.value; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; @@ -78,7 +78,7 @@ void testRouting() { JaxRsApplication app = JaxRsApplication.create(TestApp.class); Optional namedRouting = extension.findNamedRouting(EMPTY_CONFIG, app); - assertThat(namedRouting, value(is("admin"))); + assertThat(namedRouting, optionalValue(is("admin"))); } @Test @@ -89,7 +89,7 @@ void testRoutingNone() { .build(); Optional namedRouting = extension.findNamedRouting(EMPTY_CONFIG, app); - assertThat(namedRouting, empty()); + assertThat(namedRouting, optionalEmpty()); } @Test @@ -99,13 +99,13 @@ void testRoutingOverrideByConfig() { Config config = Config .create(ConfigSources.create(Map.of(TestApp.class.getName() + "." + RoutingName.CONFIG_KEY_NAME, "config"))); Optional namedRouting = extension.findNamedRouting(config, app); - assertThat(namedRouting, value(is("config"))); + assertThat(namedRouting, optionalValue(is("config"))); config = Config .create(ConfigSources.create(Map.of(TestApp.class.getName() + "." + RoutingName.CONFIG_KEY_NAME, RoutingName.DEFAULT_NAME))); namedRouting = extension.findNamedRouting(config, app); - assertThat(namedRouting, empty()); + assertThat(namedRouting, optionalEmpty()); } @Test @@ -113,12 +113,12 @@ void testContextRootOverrideByConfig() { JaxRsApplication app = JaxRsApplication.create(TestApp.class); Optional contextRoot = extension.findContextRoot(EMPTY_CONFIG, app); - assertThat(contextRoot, value(is("/wrong"))); + assertThat(contextRoot, optionalValue(is("/wrong"))); Config config = Config .create(ConfigSources.create(Map.of(TestApp.class.getName() + "." + RoutingPath.CONFIG_KEY_PATH, "config"))); contextRoot = extension.findContextRoot(config, app); - assertThat(contextRoot, value(is("/config"))); + assertThat(contextRoot, optionalValue(is("/config"))); } @Test @@ -128,7 +128,7 @@ void testContextRootNoConfigGoodPath() { .build(); Optional contextRoot = extension.findContextRoot(EMPTY_CONFIG, app); - assertThat(contextRoot, value(is("/myApp"))); + assertThat(contextRoot, optionalValue(is("/myApp"))); } @Test @@ -137,7 +137,7 @@ void testContextRootNone() { .build(); Optional contextRoot = extension.findContextRoot(EMPTY_CONFIG, app); - assertThat(contextRoot, empty()); + assertThat(contextRoot, optionalEmpty()); } @Test @@ -147,7 +147,7 @@ void testContextRootNoConfigNoLeadingSlash() { .build(); Optional contextRoot = extension.findContextRoot(EMPTY_CONFIG, app); - assertThat(contextRoot, value(is("/myApp"))); + assertThat(contextRoot, optionalValue(is("/myApp"))); } @Test @@ -157,7 +157,7 @@ void testContextRootNoConfigWithTrailingSlash() { .build(); Optional contextRoot = extension.findContextRoot(EMPTY_CONFIG, app); - assertThat(contextRoot, value(is("/myApp"))); + assertThat(contextRoot, optionalValue(is("/myApp"))); } @Test diff --git a/tests/integration/config/gh-2171-yml/pom.xml b/tests/integration/config/gh-2171-yml/pom.xml index dd848d86f83..6a2e09f839b 100644 --- a/tests/integration/config/gh-2171-yml/pom.xml +++ b/tests/integration/config/gh-2171-yml/pom.xml @@ -38,8 +38,8 @@ helidon-config-yaml - io.helidon.config - helidon-config-testing + io.helidon.common.testing + helidon-common-testing-junit5 test diff --git a/tests/integration/config/gh-2171-yml/src/test/java/io/helidon/tests/integration/gh2171/yml/ConfigurationTest.java b/tests/integration/config/gh-2171-yml/src/test/java/io/helidon/tests/integration/gh2171/yml/ConfigurationTest.java index 9c72b28cd98..38897ec93f4 100644 --- a/tests/integration/config/gh-2171-yml/src/test/java/io/helidon/tests/integration/gh2171/yml/ConfigurationTest.java +++ b/tests/integration/config/gh-2171-yml/src/test/java/io/helidon/tests/integration/gh2171/yml/ConfigurationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.value; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -33,7 +33,7 @@ static void createInstance() { @Test void testValue() { - assertThat(configuration.value("value"), value(is("yml"))); + assertThat(configuration.value("value"), optionalValue(is("yml"))); } } \ No newline at end of file diff --git a/tests/integration/config/gh-2171/pom.xml b/tests/integration/config/gh-2171/pom.xml index 143e84e3326..7aeed1b3774 100644 --- a/tests/integration/config/gh-2171/pom.xml +++ b/tests/integration/config/gh-2171/pom.xml @@ -38,8 +38,8 @@ helidon-config-yaml - io.helidon.config - helidon-config-testing + io.helidon.common.testing + helidon-common-testing-junit5 test diff --git a/tests/integration/config/gh-2171/src/test/java/io/helidon/tests/integration/gh2171/ConfigurationTest.java b/tests/integration/config/gh-2171/src/test/java/io/helidon/tests/integration/gh2171/ConfigurationTest.java index ed0c8b28f37..25f3d2631ed 100644 --- a/tests/integration/config/gh-2171/src/test/java/io/helidon/tests/integration/gh2171/ConfigurationTest.java +++ b/tests/integration/config/gh-2171/src/test/java/io/helidon/tests/integration/gh2171/ConfigurationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.empty; -import static io.helidon.config.testing.OptionalMatcher.value; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -34,17 +34,17 @@ static void createInstance() { @Test void testSameKey() { - assertThat(configuration.value("value"), value(is("yaml"))); + assertThat(configuration.value("value"), optionalValue(is("yaml"))); } @Test void testYamlPresent() { - assertThat(configuration.value("yaml"), value(is("yaml"))); + assertThat(configuration.value("yaml"), optionalValue(is("yaml"))); } @Test void testYmlNotPresent() { - assertThat(configuration.value("yml"), empty()); + assertThat(configuration.value("yml"), optionalEmpty()); } } \ No newline at end of file diff --git a/tests/integration/vault/mp/src/test/java/io/helidon/tests/integration/vault/mp/TestKubernetesAuth.java b/tests/integration/vault/mp/src/test/java/io/helidon/tests/integration/vault/mp/TestKubernetesAuth.java index f340c69c290..5426e4ea28b 100644 --- a/tests/integration/vault/mp/src/test/java/io/helidon/tests/integration/vault/mp/TestKubernetesAuth.java +++ b/tests/integration/vault/mp/src/test/java/io/helidon/tests/integration/vault/mp/TestKubernetesAuth.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,7 +60,7 @@ import static io.helidon.config.testing.OptionalMatcher.empty; import static io.helidon.config.testing.OptionalMatcher.present; -import static io.helidon.config.testing.OptionalMatcher.value; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; diff --git a/webserver/webserver/pom.xml b/webserver/webserver/pom.xml index 0dc493a9092..377a727798c 100644 --- a/webserver/webserver/pom.xml +++ b/webserver/webserver/pom.xml @@ -221,8 +221,8 @@ test - io.helidon.config - helidon-config-testing + io.helidon.common.testing + helidon-common-testing-junit5 test diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/NettyWebServerTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/NettyWebServerTest.java index 7c168f18536..4c75b3ca042 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/NettyWebServerTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/NettyWebServerTest.java @@ -38,7 +38,7 @@ import org.hamcrest.core.Is; import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.present; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalPresent; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -270,6 +270,6 @@ public void additionalCoupledPairedRoutingsDoWork() { .socket("matched", (s, r) -> {}) .build(); - assertThat(webServer.configuration().namedSocket("matched"), present()); + assertThat(webServer.configuration().namedSocket("matched"), optionalPresent()); } } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/NettyWebServerV2ApiTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/NettyWebServerV2ApiTest.java index 767082e212c..0254e5c5ca1 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/NettyWebServerV2ApiTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/NettyWebServerV2ApiTest.java @@ -37,7 +37,7 @@ import org.hamcrest.core.Is; import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.present; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalPresent; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -272,6 +272,6 @@ public void additionalCoupledPairedRoutingsDoWork() { Routing.builder().build()) .build(); - assertThat(webServer.configuration().namedSocket("matched"), present()); + assertThat(webServer.configuration().namedSocket("matched"), optionalPresent()); } } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/SocketConfigurationTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/SocketConfigurationTest.java index 886b73682ac..b36f3e4f08f 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/SocketConfigurationTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/SocketConfigurationTest.java @@ -28,7 +28,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.present; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalPresent; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -87,14 +87,14 @@ void testRunnableConfig() throws ExecutionException, InterruptedException { Optional maybeConfig = configuration.namedSocket("admin"); assertThat(ERROR_PREFIX + " runnable admin socket must be configured", maybeConfig, - present()); + optionalPresent()); validateRunnableSocket("admin", maybeConfig.get(), true); validateRunnablePort("admin", server, true); maybeConfig = configuration.namedSocket("static"); assertThat(ERROR_PREFIX + " runnable static socket must be configured", maybeConfig, - present()); + optionalPresent()); validateRunnableSocket("static", maybeConfig.get(), false); validateRunnablePort("static", server, false); } finally { @@ -144,7 +144,7 @@ private void validateSocket(String type, ServerConfiguration configuration, Stri Optional socketConfiguration = configuration.namedSocket(name); assertThat(ERROR_PREFIX + type + " " + name + " socket must be configured", socketConfiguration, - present()); + optionalPresent()); SocketConfiguration socket = socketConfiguration.get(); assertThat(ERROR_PREFIX + type + " " + name + " socket port", diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/SocketConfigurationV2ApiTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/SocketConfigurationV2ApiTest.java index d231911b2b1..bdb67e4a2e7 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/SocketConfigurationV2ApiTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/SocketConfigurationV2ApiTest.java @@ -27,7 +27,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import static io.helidon.config.testing.OptionalMatcher.present; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalPresent; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.MatcherAssert.assertThat; @@ -86,14 +86,14 @@ void testRunnableConfig() throws ExecutionException, InterruptedException { Optional maybeConfig = configuration.namedSocket("admin"); assertThat(ERROR_PREFIX + " runnable admin socket must be configured", maybeConfig, - present()); + optionalPresent()); validateRunnableSocket("admin", maybeConfig.get(), true); validateRunnablePort("admin", server, true); maybeConfig = configuration.namedSocket("static"); assertThat(ERROR_PREFIX + " runnable static socket must be configured", maybeConfig, - present()); + optionalPresent()); validateRunnableSocket("static", maybeConfig.get(), false); validateRunnablePort("static", server, false); } finally { @@ -144,7 +144,7 @@ private void validateSocket(String type, ServerConfiguration configuration, Stri Optional socketConfiguration = configuration.namedSocket(name); assertThat(ERROR_PREFIX + type + " " + name + " socket must be configured", socketConfiguration, - present()); + optionalPresent()); SocketConfiguration socket = socketConfiguration.get(); assertThat(ERROR_PREFIX + type + " " + name + " socket port", From a21bee3d119c267aa327a5fd42188ff1cd85020d Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 21:41:01 +0200 Subject: [PATCH 02/54] Media types update (detectors, new types) --- CHANGELOG.md | 4 +- common/media-type/pom.xml | 10 +- .../common/media/type/BuiltInsDetector.java | 6 +- .../common/media/type/CustomDetector.java | 16 +- .../helidon/common/media/type/Detectors.java | 80 ++++++++ .../helidon/common/media/type/MediaType.java | 90 +++++++++ .../common/media/type/MediaTypeEnum.java | 104 ++++++++++ .../common/media/type/MediaTypeImpl.java | 34 ++++ .../helidon/common/media/type/MediaTypes.java | 178 +++++++++++++----- .../common/media/type/package-info.java | 3 +- .../media/type/spi/MediaTypeDetector.java | 14 +- .../common/media/type/CustomTypeDetector.java | 10 +- .../media/type/DockerfileTypeDetector.java | 9 +- .../common/media/type/MediaTypesTest.java | 95 +++++++--- 14 files changed, 559 insertions(+), 94 deletions(-) create mode 100644 common/media-type/src/main/java/io/helidon/common/media/type/Detectors.java create mode 100644 common/media-type/src/main/java/io/helidon/common/media/type/MediaType.java create mode 100644 common/media-type/src/main/java/io/helidon/common/media/type/MediaTypeEnum.java create mode 100644 common/media-type/src/main/java/io/helidon/common/media/type/MediaTypeImpl.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 9df7f79d2fa..ce0dc770e05 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -23,4 +23,6 @@ We are pleased to announce Helidon 4.0.0 a major release that includes significa - Introduction of `@Weight`, `Weighted` and `Weights` instead of `@Priority` and `Prioritized`, to base ordering on a double (allows to fit a component between any other two components), all modules using priority are refactored (except for MicroProfile where required by specifications). - higher weight means a component is more important - moved priority related types to MP config (as that is the lowest level MP module) - - replaces all instances in SE that use priority with weight (no dependency on Jakarta, predictible and easy to understand behavior) \ No newline at end of file + - replaces all instances in SE that use priority with weight (no dependency on Jakarta, predictible and easy to understand behavior) +- Introduction of `MediaType` as the abstraction of any media type, as used by Config, static content and HTTP in general. See `MediaType` and `MediaTypes` +- \ No newline at end of file diff --git a/common/media-type/pom.xml b/common/media-type/pom.xml index 2b235dd9de1..b460ad89a4e 100644 --- a/common/media-type/pom.xml +++ b/common/media-type/pom.xml @@ -16,7 +16,9 @@ limitations under the License. --> - + + 4.0.0 io.helidon.common @@ -26,12 +28,18 @@ helidon-common-media-type Helidon Common Media Type + Media type support (usable both for files and for HTTP) io.helidon.common helidon-common + + io.helidon.common.testing + helidon-common-testing-junit5 + test + org.junit.jupiter junit-jupiter-api diff --git a/common/media-type/src/main/java/io/helidon/common/media/type/BuiltInsDetector.java b/common/media-type/src/main/java/io/helidon/common/media/type/BuiltInsDetector.java index 93227f1e578..7b72e2b7264 100644 --- a/common/media-type/src/main/java/io/helidon/common/media/type/BuiltInsDetector.java +++ b/common/media-type/src/main/java/io/helidon/common/media/type/BuiltInsDetector.java @@ -30,15 +30,15 @@ */ class BuiltInsDetector implements MediaTypeDetector { private static final System.Logger LOGGER = System.getLogger(BuiltInsDetector.class.getName()); + private static final Map MAPPINGS = new HashMap<>(); - private static final Map MAPPINGS = new HashMap<>(); static { try (InputStream builtIns = MediaTypes.class.getResourceAsStream("default-media-types.properties")) { if (null != builtIns) { Properties properties = new Properties(); properties.load(builtIns); for (String name : properties.stringPropertyNames()) { - MAPPINGS.put(name, properties.getProperty(name)); + MAPPINGS.put(name, MediaTypes.create(properties.getProperty(name))); } } else { LOGGER.log(Level.ERROR, "Failed to find default media type mapping resource"); @@ -49,7 +49,7 @@ class BuiltInsDetector implements MediaTypeDetector { } @Override - public Optional detectExtensionType(String fileSuffix) { + public Optional detectExtensionType(String fileSuffix) { return Optional.ofNullable(MAPPINGS.get(fileSuffix)); } } diff --git a/common/media-type/src/main/java/io/helidon/common/media/type/CustomDetector.java b/common/media-type/src/main/java/io/helidon/common/media/type/CustomDetector.java index 6f583014075..edda36c4a28 100644 --- a/common/media-type/src/main/java/io/helidon/common/media/type/CustomDetector.java +++ b/common/media-type/src/main/java/io/helidon/common/media/type/CustomDetector.java @@ -17,7 +17,6 @@ import java.io.IOException; import java.io.InputStream; -import java.lang.System.Logger.Level; import java.net.URL; import java.util.Enumeration; import java.util.HashMap; @@ -27,13 +26,16 @@ import io.helidon.common.media.type.spi.MediaTypeDetector; +import static java.lang.System.Logger.Level.ERROR; +import static java.lang.System.Logger.Level.TRACE; + /** * Detector for custom media type mappings. */ class CustomDetector implements MediaTypeDetector { private static final String MEDIA_TYPE_RESOURCE = "META-INF/helidon/media-types.properties"; private static final System.Logger LOGGER = System.getLogger(CustomDetector.class.getName()); - private static final Map MAPPINGS = new HashMap<>(); + private static final Map MAPPINGS = new HashMap<>(); static { // look for configured mapping by a user @@ -44,22 +46,24 @@ class CustomDetector implements MediaTypeDetector { Enumeration resources = classLoader.getResources(MEDIA_TYPE_RESOURCE); while (resources.hasMoreElements()) { URL url = resources.nextElement(); - LOGGER.log(Level.DEBUG, () -> "Loading custom media type mapping from: " + url); + if (LOGGER.isLoggable(TRACE)) { + LOGGER.log(TRACE, "Loading custom media type mapping from: " + url); + } try (InputStream is = url.openStream()) { Properties properties = new Properties(); properties.load(is); for (String name : properties.stringPropertyNames()) { - MAPPINGS.put(name, properties.getProperty(name)); + MAPPINGS.put(name, MediaTypes.create(properties.getProperty(name))); } } } } catch (IOException e) { - LOGGER.log(Level.ERROR, "Failed to load custom media types mapping", e); + LOGGER.log(ERROR, "Failed to load custom media types mapping", e); } } @Override - public Optional detectExtensionType(String fileSuffix) { + public Optional detectExtensionType(String fileSuffix) { return Optional.ofNullable(MAPPINGS.get(fileSuffix)); } } diff --git a/common/media-type/src/main/java/io/helidon/common/media/type/Detectors.java b/common/media-type/src/main/java/io/helidon/common/media/type/Detectors.java new file mode 100644 index 00000000000..6bb476ae783 --- /dev/null +++ b/common/media-type/src/main/java/io/helidon/common/media/type/Detectors.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.common.media.type; + +import java.net.URI; +import java.net.URL; +import java.nio.file.Path; +import java.util.List; +import java.util.Optional; +import java.util.ServiceLoader; +import java.util.concurrent.ConcurrentHashMap; + +import io.helidon.common.HelidonServiceLoader; +import io.helidon.common.media.type.spi.MediaTypeDetector; + +final class Detectors { + private static final List DETECTORS; + private static final ConcurrentHashMap> CACHE = new ConcurrentHashMap<>(); + + static { + // and load media type detectors + DETECTORS = HelidonServiceLoader.builder(ServiceLoader.load(MediaTypeDetector.class)) + .addService(new BuiltInsDetector(), 0) + .addService(new CustomDetector(), 1) + .build() + .asList(); + } + + private Detectors() { + } + + static Optional detectExtensionType(String fileSuffix) { + return CACHE.computeIfAbsent(fileSuffix, it -> + DETECTORS.stream() + .map(mtd -> mtd.detectExtensionType(fileSuffix)) + .flatMap(Optional::stream) + .findFirst()); + } + + static Optional detectType(String fileName) { + return DETECTORS.stream() + .map(mtd -> mtd.detectType(fileName)) + .flatMap(Optional::stream) + .findFirst(); + } + + static Optional detectType(Path file) { + return DETECTORS.stream() + .map(mtd -> mtd.detectType(file)) + .flatMap(Optional::stream) + .findFirst(); + } + + static Optional detectType(URI uri) { + return DETECTORS.stream() + .map(mtd -> mtd.detectType(uri)) + .flatMap(Optional::stream) + .findFirst(); + } + + static Optional detectType(URL url) { + return DETECTORS.stream() + .map(mtd -> mtd.detectType(url)) + .flatMap(Optional::stream) + .findFirst(); + } +} diff --git a/common/media-type/src/main/java/io/helidon/common/media/type/MediaType.java b/common/media-type/src/main/java/io/helidon/common/media/type/MediaType.java new file mode 100644 index 00000000000..454446b8029 --- /dev/null +++ b/common/media-type/src/main/java/io/helidon/common/media/type/MediaType.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.media.type; + +/** + * Media type support and known media types. + * @see io.helidon.common.media.type.MediaTypes + */ +public interface MediaType { + + /** + * Type, such as {@code application}. + * + * @return type part of the media type + */ + String type(); + + /** + * Subtype, such as {@code yaml}. + * + * @return subtype part of the media type + */ + String subtype(); + + /** + * Full type, such as {@code application/yaml}. + * + * @return full media type string + * @deprecated use {@link #text()} + */ + @Deprecated + default String fullType() { + return text(); + } + + /** + * Full type, such as {@code application/yaml}. + * + * @return full media type string + */ + String text(); + + /** + * Is the type a wildcard? + * @return whether this is a wildcard type + */ + default boolean isWildcardType() { + return "*".equals(type()); + } + + /** + * Is the subtype a wildcard? + * @return whether this is a wildcard subtype + */ + default boolean isWildcardSubtype() { + return "*".equals(subtype()); + } + + /** + * Tests if this media type has provided Structured Syntax {@code suffix} (RFC 6839). + * + * @param suffix Suffix with or without '+' prefix. If null or empty then returns {@code true} if this media type + * has ANY suffix. + * @return {@code true} if media type has specified {@code suffix} or has any suffix if parameter is {@code null} or empty. + */ + default boolean hasSuffix(String suffix) { + if (suffix != null && !suffix.isEmpty()) { + if (suffix.charAt(0) != '+') { + suffix = "+" + suffix; + } + return subtype().endsWith(suffix); + } else { + return subtype().indexOf('+') >= 0; + } + } +} diff --git a/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypeEnum.java b/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypeEnum.java new file mode 100644 index 00000000000..aeb25553fdf --- /dev/null +++ b/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypeEnum.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.media.type; + +import java.util.HashMap; +import java.util.Map; + +enum MediaTypeEnum implements MediaType { + WILDCARD("*", "*", true), + APPLICATION_XML("application", "xml"), + APPLICATION_ATOM_XML("application", "atom+xml"), + APPLICATION_XHTML_XML("application", "xhtml+xml"), + APPLICATION_SVG_XML("application", "svg+xml"), + APPLICATION_JSON("application", "json"), + APPLICATION_STREAM_JSON("application", "stream+json"), + APPLICATION_FORM_URLENCODED("application", "x-www-form-urlencoded"), + MULTIPART_FORM_DATA("multipart", "form-data"), + MULTIPART_BYTERANGES("multipart", "byteranges"), + APPLICATION_OCTET_STREAM("application", "octet-stream"), + TEXT_PLAIN("text", "plain"), + TEXT_XML("text", "xml"), + TEXT_HTML("text", "html"), + APPLICATION_OPENAPI_YAML("application", "vnd.oai.openapi"), + APPLICATION_OPENAPI_JSON("application", "vnd.oai.openapi+json"), + APPLICATION_X_YAML("application", "x-yaml"), + APPLICATION_YAML("application", "yaml"), + TEXT_X_YAML("text", "x-yaml"), + TEXT_YAML("text", "yaml"), + APPLICATION_JAVASCRIPT("application", "javascript"), + TEXT_EVENT_STREAM("text", "event-stream"), + APPLICATION_X_NDJSON("application", "x-ndjson"), + APPLICATION_HOCON("application", "hocon"); + + private static final Map BY_FULL_TYPE; + + static { + Map byFullType = new HashMap<>(); + + for (MediaTypeEnum value : MediaTypeEnum.values()) { + byFullType.put(value.text(), value); + } + + BY_FULL_TYPE = Map.copyOf(byFullType); + } + + private final String type; + private final String subtype; + private final String fullType; + private final boolean wildcard; + + MediaTypeEnum(String type, String subtype) { + this(type, subtype, false); + } + + MediaTypeEnum(String type, String subtype, boolean wildcard) { + this.type = type; + this.subtype = subtype; + this.fullType = type + "/" + subtype; + this.wildcard = wildcard; + } + + static MediaTypeEnum find(String fullType) { + return BY_FULL_TYPE.get(fullType); + } + + @Override + public String type() { + return type; + } + + @Override + public String subtype() { + return subtype; + } + + @Override + public String text() { + return fullType; + } + + @Override + public boolean isWildcardType() { + return wildcard; + } + + @Override + public boolean isWildcardSubtype() { + return wildcard; + } +} diff --git a/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypeImpl.java b/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypeImpl.java new file mode 100644 index 00000000000..589beaa456f --- /dev/null +++ b/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypeImpl.java @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.media.type; + +record MediaTypeImpl(String type, String subtype, String text) implements MediaType { + static MediaType parse(String fullType) { + int slashIndex = fullType.indexOf('/'); + if (slashIndex < 1) { + throw new IllegalArgumentException("Cannot parse media type: " + fullType); + } + return new MediaTypeImpl(fullType.substring(0, slashIndex), + fullType.substring(slashIndex + 1), + fullType); + } + + @Override + public String toString() { + return fullType(); + } +} diff --git a/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypes.java b/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypes.java index 09263617edf..adcb2f37d0b 100644 --- a/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypes.java +++ b/common/media-type/src/main/java/io/helidon/common/media/type/MediaTypes.java @@ -18,13 +18,7 @@ import java.net.URI; import java.net.URL; import java.nio.file.Path; -import java.util.List; import java.util.Optional; -import java.util.ServiceLoader; -import java.util.concurrent.ConcurrentHashMap; - -import io.helidon.common.HelidonServiceLoader; -import io.helidon.common.media.type.spi.MediaTypeDetector; /** * Media type detection based on a resource. @@ -36,22 +30,136 @@ * */ public final class MediaTypes { - private static final List DETECTORS; - private static final ConcurrentHashMap> CACHE = new ConcurrentHashMap<>(); - - static { - // and load media type detectors - DETECTORS = HelidonServiceLoader.builder(ServiceLoader.load(MediaTypeDetector.class)) - .addService(new BuiltInsDetector(), 10) - .addService(new CustomDetector(), 50) - .build() - .asList(); - } + /** + * Wildcard media type. + */ + public static final MediaType WILDCARD = MediaTypeEnum.WILDCARD; + /** + * {@code application/xml} media type. + */ + public static final MediaType APPLICATION_XML = MediaTypeEnum.APPLICATION_XML; + /** + * {@code application/atom+xml} media type. + */ + public static final MediaType APPLICATION_ATOM_XML = MediaTypeEnum.APPLICATION_ATOM_XML; + /** + * {@code application/xhtml+xml} media type. + */ + public static final MediaType APPLICATION_XHTML_XML = MediaTypeEnum.APPLICATION_XHTML_XML; + /** + * {@code application/svg+xml} media type. + */ + public static final MediaType APPLICATION_SVG_XML = MediaTypeEnum.APPLICATION_SVG_XML; + /** + * {@code application/json} media type. + */ + public static final MediaType APPLICATION_JSON = MediaTypeEnum.APPLICATION_JSON; + /** + * {@code application/stream+json} media type. + */ + public static final MediaType APPLICATION_STREAM_JSON = MediaTypeEnum.APPLICATION_STREAM_JSON; + /** + * {@code application/x-www-form-urlencoded} media type. + */ + public static final MediaType APPLICATION_FORM_URLENCODED = MediaTypeEnum.APPLICATION_FORM_URLENCODED; + /** + * {@code multipart/form-data} media type. + */ + public static final MediaType MULTIPART_FORM_DATA = MediaTypeEnum.MULTIPART_FORM_DATA; + /** + * {@code multipart/byte-ranges} media type. + */ + public static final MediaType MULTIPART_BYTERANGES = MediaTypeEnum.MULTIPART_BYTERANGES; + /** + * {@code application/octet-stream} media type. + */ + public static final MediaType APPLICATION_OCTET_STREAM = MediaTypeEnum.APPLICATION_OCTET_STREAM; + /** + * {@code tet/plain} media type. + */ + public static final MediaType TEXT_PLAIN = MediaTypeEnum.TEXT_PLAIN; + /** + * {@code text/xml} media type. + */ + public static final MediaType TEXT_XML = MediaTypeEnum.TEXT_XML; + /** + * {@code text/html} media type. + */ + public static final MediaType TEXT_HTML = MediaTypeEnum.TEXT_HTML; + /** + * {@code application/vnd.oai.openapi} media type. + */ + public static final MediaType APPLICATION_OPENAPI_YAML = MediaTypeEnum.APPLICATION_OPENAPI_YAML; + /** + * {@code application/vnd.oai.openapi+json} media type. + */ + public static final MediaType APPLICATION_OPENAPI_JSON = MediaTypeEnum.APPLICATION_OPENAPI_JSON; + /** + * {@code application/x-yaml} media type. + */ + public static final MediaType APPLICATION_X_YAML = MediaTypeEnum.APPLICATION_X_YAML; + /** + * {@code application/yaml} media type. + */ + public static final MediaType APPLICATION_YAML = MediaTypeEnum.APPLICATION_YAML; + /** + * {@code text/x-yaml} media type. + */ + public static final MediaType TEXT_X_YAML = MediaTypeEnum.TEXT_X_YAML; + /** + * {@code text/yaml} media type. + */ + public static final MediaType TEXT_YAML = MediaTypeEnum.TEXT_YAML; + /** + * {@code application/javascript} media type. + */ + public static final MediaType APPLICATION_JAVASCRIPT = MediaTypeEnum.APPLICATION_JAVASCRIPT; + /** + * {@code text/event-stream} media type. + */ + public static final MediaType TEXT_EVENT_STREAM = MediaTypeEnum.TEXT_EVENT_STREAM; + /** + * {@code application/x-ndjson} media type. + */ + public static final MediaType APPLICATION_X_NDJSON = MediaTypeEnum.APPLICATION_X_NDJSON; + /** + * {@code application/hocon} media type. + */ + public static final MediaType APPLICATION_HOCON = MediaTypeEnum.APPLICATION_HOCON; // prevent instantiation of utility class private MediaTypes() { } + /** + * Create media type from the type and subtype. + * + * @param type type + * @param subtype subtype + * @return media type for the instance + */ + public static MediaType create(String type, String subtype) { + MediaTypeEnum mediaTypeEnum = MediaTypeEnum.find(type + "/" + subtype); + if (mediaTypeEnum == null) { + return new MediaTypeImpl(type, + subtype, + type + "/" + subtype); + } else { + return mediaTypeEnum; + } + } + + /** + * Create a new media type from the full media type string. + * + * @param fullType media type string, such as {@code application/json} + * @return media type for the string + */ + public static MediaType create(String fullType) { + MediaTypeEnum types = MediaTypeEnum.find(fullType); + return types == null ? MediaTypeImpl.parse(fullType) : types; + } + /** * Detect media type based on URL. * As there may be an infinite number of urls used in a system, the results are NOT cached. @@ -59,11 +167,8 @@ private MediaTypes() { * @param url to determine media type for * @return media type or empty if none found */ - public static Optional detectType(URL url) { - return DETECTORS.stream() - .map(mtd -> mtd.detectType(url)) - .flatMap(Optional::stream) - .findFirst(); + public static Optional detectType(URL url) { + return Detectors.detectType(url); } /** @@ -73,11 +178,8 @@ public static Optional detectType(URL url) { * @param uri to determine media type for * @return media type or empty if none found */ - public static Optional detectType(URI uri) { - return DETECTORS.stream() - .map(mtd -> mtd.detectType(uri)) - .flatMap(Optional::stream) - .findFirst(); + public static Optional detectType(URI uri) { + return Detectors.detectType(uri); } /** @@ -87,11 +189,8 @@ public static Optional detectType(URI uri) { * @param file file on a file system * @return media type or empty if none found */ - public static Optional detectType(Path file) { - return DETECTORS.stream() - .map(mtd -> mtd.detectType(file)) - .flatMap(Optional::stream) - .findFirst(); + public static Optional detectType(Path file) { + return Detectors.detectType(file); } /** @@ -105,11 +204,8 @@ public static Optional detectType(Path file) { * @see #detectType(java.net.URL) * @see #detectType(java.nio.file.Path) */ - public static Optional detectType(String fileName) { - return DETECTORS.stream() - .map(mtd -> mtd.detectType(fileName)) - .flatMap(Optional::stream) - .findFirst(); + public static Optional detectType(String fileName) { + return Detectors.detectType(fileName); } /** @@ -119,11 +215,7 @@ public static Optional detectType(String fileName) { * @param fileSuffix suffix of a file, such as {@code txt}, {@code properties}, or {@code jpeg}. Without the leading dot. * @return media type for the file suffix or empty if none found */ - public static Optional detectExtensionType(String fileSuffix) { - return CACHE.computeIfAbsent(fileSuffix, it -> - DETECTORS.stream() - .map(mtd -> mtd.detectExtensionType(fileSuffix)) - .flatMap(Optional::stream) - .findFirst()); + public static Optional detectExtensionType(String fileSuffix) { + return Detectors.detectExtensionType(fileSuffix); } } diff --git a/common/media-type/src/main/java/io/helidon/common/media/type/package-info.java b/common/media-type/src/main/java/io/helidon/common/media/type/package-info.java index 14fe78624d3..5077bb8b321 100644 --- a/common/media-type/src/main/java/io/helidon/common/media/type/package-info.java +++ b/common/media-type/src/main/java/io/helidon/common/media/type/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,5 +16,6 @@ /** * Support for mapping resources to media types, be it files, URIs or URLs. * @see io.helidon.common.media.type.MediaTypes + * @see io.helidon.common.media.type.MediaType */ package io.helidon.common.media.type; diff --git a/common/media-type/src/main/java/io/helidon/common/media/type/spi/MediaTypeDetector.java b/common/media-type/src/main/java/io/helidon/common/media/type/spi/MediaTypeDetector.java index df91c8b4804..b52a08113f8 100644 --- a/common/media-type/src/main/java/io/helidon/common/media/type/spi/MediaTypeDetector.java +++ b/common/media-type/src/main/java/io/helidon/common/media/type/spi/MediaTypeDetector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.nio.file.Path; import java.util.Optional; +import io.helidon.common.media.type.MediaType; + /** * Detect media type. * Minimal implementation checks result based on file suffix. @@ -36,7 +38,7 @@ public interface MediaTypeDetector { * @param url to find media type from * @return media type if detected */ - default Optional detectType(URL url) { + default Optional detectType(URL url) { return detectType(url.getPath()); } @@ -47,7 +49,7 @@ default Optional detectType(URL url) { * @param uri to find media type from * @return media type if detected */ - default Optional detectType(URI uri) { + default Optional detectType(URI uri) { return detectType(uri.getPath()); } @@ -59,7 +61,7 @@ default Optional detectType(URI uri) { * @param file to find media type from * @return media type if detected */ - default Optional detectType(Path file) { + default Optional detectType(Path file) { Path fileName = file.getFileName(); if (null == fileName) { return Optional.empty(); @@ -76,7 +78,7 @@ default Optional detectType(Path file) { * @param fileString path, or name of the file to analyze * @return media type if detected */ - default Optional detectType(String fileString) { + default Optional detectType(String fileString) { // file string - we are interested in last . index int index = fileString.lastIndexOf('.'); @@ -100,5 +102,5 @@ default Optional detectType(String fileString) { * @param fileSuffix suffix (extension) of a file, without the leading dot * @return media type if detected */ - Optional detectExtensionType(String fileSuffix); + Optional detectExtensionType(String fileSuffix); } diff --git a/common/media-type/src/test/java/io/helidon/common/media/type/CustomTypeDetector.java b/common/media-type/src/test/java/io/helidon/common/media/type/CustomTypeDetector.java index 8203b4eadff..71e3a1ae437 100644 --- a/common/media-type/src/test/java/io/helidon/common/media/type/CustomTypeDetector.java +++ b/common/media-type/src/test/java/io/helidon/common/media/type/CustomTypeDetector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,11 +25,11 @@ */ public class CustomTypeDetector implements MediaTypeDetector { static final String SUFFIX = "mine"; - static final String MEDIA_TYPE = "application/mine"; - static final String MEDIA_TYPE_HTTP = "application/http-mine"; + static final MediaType MEDIA_TYPE = MediaTypes.create("application/mine"); + static final MediaType MEDIA_TYPE_HTTP = MediaTypes.create("application/http-mine"); @Override - public Optional detectType(URL url) { + public Optional detectType(URL url) { if (url.getPath().endsWith("." + SUFFIX)) { if (url.getProtocol().equals("http")) { return Optional.of(MEDIA_TYPE_HTTP); @@ -41,7 +41,7 @@ public Optional detectType(URL url) { } @Override - public Optional detectExtensionType(String fileSuffix) { + public Optional detectExtensionType(String fileSuffix) { if (SUFFIX.equals(fileSuffix)) { return Optional.of(MEDIA_TYPE); } diff --git a/common/media-type/src/test/java/io/helidon/common/media/type/DockerfileTypeDetector.java b/common/media-type/src/test/java/io/helidon/common/media/type/DockerfileTypeDetector.java index d7c07e64b2e..1c3e241c278 100644 --- a/common/media-type/src/test/java/io/helidon/common/media/type/DockerfileTypeDetector.java +++ b/common/media-type/src/test/java/io/helidon/common/media/type/DockerfileTypeDetector.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,13 +24,12 @@ * Example implementing a base name handling. */ public class DockerfileTypeDetector implements MediaTypeDetector { - static String MEDIA_TYPE = "application/dockerfile"; - private static final Pattern DOCKERFILE_PATTERN = Pattern.compile(".*Dockerfile(\\.\\w+)?$"); + static MediaType MEDIA_TYPE = MediaTypes.create("application/dockerfile"); @Override - public Optional detectType(String fileString) { + public Optional detectType(String fileString) { if (DOCKERFILE_PATTERN.matcher(fileString).matches()) { return Optional.of(MEDIA_TYPE); } @@ -38,7 +37,7 @@ public Optional detectType(String fileString) { } @Override - public Optional detectExtensionType(String fileSuffix) { + public Optional detectExtensionType(String fileSuffix) { return Optional.empty(); } } diff --git a/common/media-type/src/test/java/io/helidon/common/media/type/MediaTypesTest.java b/common/media-type/src/test/java/io/helidon/common/media/type/MediaTypesTest.java index 8f2aa2e40c6..4f0bdf1d4db 100644 --- a/common/media-type/src/test/java/io/helidon/common/media/type/MediaTypesTest.java +++ b/common/media-type/src/test/java/io/helidon/common/media/type/MediaTypesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,79 +17,129 @@ package io.helidon.common.media.type; import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.nio.file.Paths; +import java.util.LinkedHashSet; import java.util.Optional; import java.util.Properties; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; import org.junit.jupiter.api.Test; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalPresent; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.CoreMatchers.containsString; -import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; +import static org.hamcrest.collection.IsEmptyCollection.emptyCollectionOf; +import static org.junit.jupiter.api.Assertions.assertAll; /** * Unit test for {@link MediaTypes}. */ class MediaTypesTest { + private static final Class clazz = MediaTypes.class; + private static final Set constants = Stream.of(clazz.getDeclaredFields()) + .filter(it -> Modifier.isStatic(it.getModifiers())) + .filter(it -> Modifier.isFinal(it.getModifiers())) + .filter(it -> Modifier.isPublic(it.getModifiers())) + .map(Field::getName) + .collect(Collectors.toSet()); + + @Test + void testAllEnumValuesHaveConstants() { + MediaTypeEnum[] expectedNames = MediaTypeEnum.values(); + + Set missing = new LinkedHashSet<>(); + + for (MediaTypeEnum expectedName : expectedNames) { + String name = expectedName.name(); + if (!constants.contains(name)) { + missing.add(name); + } + } + + assertThat(missing, emptyCollectionOf(String.class)); + } + + @Test + void testAllConstantsAreValid() throws NoSuchFieldException, IllegalAccessException { + // this is to test correct initialization (there may be an issue when the constants + // are defined on the interface and implemented by enum outside of it) + for (String constant : constants) { + MediaType value = (MediaType) clazz.getField(constant) + .get(null); + + assertAll( + () -> assertThat(value, notNullValue()), + () -> assertThat(value.fullType(), notNullValue()), + () -> assertThat(value.subtype(), notNullValue()), + () -> assertThat(value.type(), notNullValue()) + ); + + } + } @Test void testBuiltIn() throws MalformedURLException { - Optional expected = Optional.of("application/x-yaml"); // file suffix - Optional yml = MediaTypes.detectExtensionType("yml"); - assertThat(yml, is(expected)); + Optional yml = MediaTypes.detectExtensionType("yml"); + assertThat(yml, optionalValue(is(MediaTypes.APPLICATION_X_YAML))); // URI yml = MediaTypes.detectType(URI.create("http://localhost:8080/static/test.yml")); - assertThat(yml, is(expected)); + assertThat(yml, optionalValue(is(MediaTypes.APPLICATION_X_YAML))); // URL yml = MediaTypes.detectType(new URL("http://localhost:8080/static/test.yml")); - assertThat(yml, is(expected)); + assertThat(yml, optionalValue(is(MediaTypes.APPLICATION_X_YAML))); // Path object yml = MediaTypes.detectType(Paths.get("/home/config.yml")); - assertThat(yml, is(expected)); + assertThat(yml, optionalValue(is(MediaTypes.APPLICATION_X_YAML))); // Path string yml = MediaTypes.detectType("some path/forward\\back\\config.yml"); - assertThat(yml, is(expected)); + assertThat(yml, optionalValue(is(MediaTypes.APPLICATION_X_YAML))); } @Test void testCustom() { - Optional hocon = MediaTypes.detectExtensionType("json"); + Optional hocon = MediaTypes.detectExtensionType("json"); - assertThat(hocon, is(Optional.of("application/hocon"))); + assertThat(hocon, optionalValue(is(MediaTypes.APPLICATION_HOCON))); } @Test void testService() throws MalformedURLException { - Optional type = MediaTypes.detectExtensionType(CustomTypeDetector.SUFFIX); - assertThat(type, is(Optional.of(CustomTypeDetector.MEDIA_TYPE))); + Optional type = MediaTypes.detectExtensionType(CustomTypeDetector.SUFFIX); + assertThat(type, optionalValue(is(CustomTypeDetector.MEDIA_TYPE))); type = MediaTypes.detectType(new URL("http", "localhost", 80, "/test/path.mine")); - assertThat(type, is(Optional.of(CustomTypeDetector.MEDIA_TYPE_HTTP))); + assertThat(type, optionalValue(is(CustomTypeDetector.MEDIA_TYPE_HTTP))); type = MediaTypes.detectType(URI.create("http://localhost/files/file.mine")); - assertThat(type, is(Optional.of(CustomTypeDetector.MEDIA_TYPE))); + assertThat(type, optionalValue(is(CustomTypeDetector.MEDIA_TYPE))); } @Test void testServiceDockerfile() throws MalformedURLException { - Optional type = MediaTypes.detectType(new URL("http", "localhost", 80, "/test/Dockerfile.native")); - assertThat(type, is(Optional.of(DockerfileTypeDetector.MEDIA_TYPE))); + Optional type = MediaTypes.detectType(new URL("http", "localhost", 80, "/test/Dockerfile.native")); + assertThat(type, optionalValue(is(DockerfileTypeDetector.MEDIA_TYPE))); type = MediaTypes.detectType(URI.create("http://localhost/files/Dockerfile")); - assertThat(type, is(Optional.of(DockerfileTypeDetector.MEDIA_TYPE))); + assertThat(type, optionalValue(is(DockerfileTypeDetector.MEDIA_TYPE))); type = MediaTypes.detectType("some text pointing to a file: Dockerfile"); - assertThat(type, is(Optional.of(DockerfileTypeDetector.MEDIA_TYPE))); + assertThat(type, optionalValue(is(DockerfileTypeDetector.MEDIA_TYPE))); } @Test @@ -98,12 +148,11 @@ void testAllTypes() throws IOException { all.load(MediaTypes.class.getResourceAsStream("default-media-types.properties")); for (String propertyName : all.stringPropertyNames()) { - Optional detected = MediaTypes.detectExtensionType(propertyName); + Optional detected = MediaTypes.detectExtensionType(propertyName); - assertThat("We should find a mapping for all properties", detected, not(Optional.empty())); + assertThat("We should find a mapping for all properties", detected, optionalPresent()); - String mediaType = detected.get(); - assertThat(mediaType, containsString("/")); + assertThat(detected.map(MediaType::text), optionalValue(containsString("/"))); } } } \ No newline at end of file From 9fa2832973d85ad61439c4cb34195af7953e2962 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 21:41:14 +0200 Subject: [PATCH 03/54] Use specific module instead of a bundle. --- common/configurable/pom.xml | 4 ++-- common/key-util/pom.xml | 9 ++------- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/common/configurable/pom.xml b/common/configurable/pom.xml index 04e78e5aa3e..d24c67d23c9 100644 --- a/common/configurable/pom.xml +++ b/common/configurable/pom.xml @@ -59,8 +59,8 @@ true - io.helidon.bundles - helidon-bundles-config + io.helidon.config + helidon-config-yaml test diff --git a/common/key-util/pom.xml b/common/key-util/pom.xml index bc0f200d8ce..0fa8b701a7f 100644 --- a/common/key-util/pom.xml +++ b/common/key-util/pom.xml @@ -47,8 +47,8 @@ true - io.helidon.bundles - helidon-bundles-config + io.helidon.config + helidon-config-yaml test @@ -61,11 +61,6 @@ hamcrest-core test - - org.mockito - mockito-core - test - From 30ec40af5eaa9fa7041b9ae53b5b65c0c6a5381b Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 21:44:18 +0200 Subject: [PATCH 04/54] New data buffers to serve as base for HTTP stream processing --- bom/pom.xml | 5 + common/buffers/pom.xml | 52 ++ .../java/io/helidon/common/buffers/Ascii.java | 171 +++++ .../io/helidon/common/buffers/BufferData.java | 645 ++++++++++++++++++ .../io/helidon/common/buffers/BufferUtil.java | 148 ++++ .../java/io/helidon/common/buffers/Bytes.java | 70 ++ .../buffers/CompositeArrayBufferData.java | 263 +++++++ .../common/buffers/CompositeBufferData.java | 30 + .../buffers/CompositeListBufferData.java | 269 ++++++++ .../helidon/common/buffers/DataListener.java | 44 ++ .../io/helidon/common/buffers/DataReader.java | 479 +++++++++++++ .../common/buffers/FixedBufferData.java | 231 +++++++ .../common/buffers/GrowingBufferData.java | 244 +++++++ .../io/helidon/common/buffers/LazyString.java | 67 ++ .../common/buffers/ReadOnlyArrayData.java | 161 +++++ .../common/buffers/ReadOnlyBufferData.java | 54 ++ .../helidon/common/buffers/package-info.java | 20 + common/buffers/src/main/java/module-info.java | 24 + .../io/helidon/common/buffers/AsciiTest.java | 108 +++ .../common/buffers/BufferDataTest.java | 299 ++++++++ .../buffers/CompositeBufferDataTest.java | 63 ++ .../common/buffers/ReadOnlyArrayDataTest.java | 40 ++ common/pom.xml | 1 + 23 files changed, 3488 insertions(+) create mode 100644 common/buffers/pom.xml create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/Ascii.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/BufferData.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/BufferUtil.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/Bytes.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/CompositeArrayBufferData.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/CompositeBufferData.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/CompositeListBufferData.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/DataListener.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/DataReader.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/FixedBufferData.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/GrowingBufferData.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/LazyString.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/ReadOnlyArrayData.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/ReadOnlyBufferData.java create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/package-info.java create mode 100644 common/buffers/src/main/java/module-info.java create mode 100644 common/buffers/src/test/java/io/helidon/common/buffers/AsciiTest.java create mode 100644 common/buffers/src/test/java/io/helidon/common/buffers/BufferDataTest.java create mode 100644 common/buffers/src/test/java/io/helidon/common/buffers/CompositeBufferDataTest.java create mode 100644 common/buffers/src/test/java/io/helidon/common/buffers/ReadOnlyArrayDataTest.java diff --git a/bom/pom.xml b/bom/pom.xml index 833e1473225..70be6388961 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -636,6 +636,11 @@ helidon-common-parameters ${helidon.version} + + io.helidon.common + helidon-common-buffers + ${helidon.version} + io.helidon.common.testing helidon-common-testing-junit5 diff --git a/common/buffers/pom.xml b/common/buffers/pom.xml new file mode 100644 index 00000000000..b84bf126d9d --- /dev/null +++ b/common/buffers/pom.xml @@ -0,0 +1,52 @@ + + + + + 4.0.0 + + io.helidon.common + helidon-common-project + 4.0.0-SNAPSHOT + + helidon-common-buffers + Helidon Common Buffers + + + + io.helidon.common + helidon-common + + + org.junit.jupiter + junit-jupiter-api + test + + + org.junit.jupiter + junit-jupiter-params + test + + + org.hamcrest + hamcrest-all + test + + + diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/Ascii.java b/common/buffers/src/main/java/io/helidon/common/buffers/Ascii.java new file mode 100644 index 00000000000..35e0ea897a9 --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/Ascii.java @@ -0,0 +1,171 @@ +/* + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +/** + * Extracted from Guava. + *

+ * Static methods pertaining to ASCII characters (those in the range of values {@code 0x00} through + * {@code 0x7F}), and to strings containing such characters. + * + * original author Craig Berry + * original author Gregory Kick + * original since 7.0 + */ +public final class Ascii { + + private Ascii() { + } + + /** + * Returns a copy of the input string in which all {@linkplain #isUpperCase(char) uppercase ASCII + * characters} have been converted to lowercase. All other characters are copied without + * modification. + * + * @param string string to lower case + * @return lower cased string + */ + public static String toLowerCase(String string) { + int length = string.length(); + for (int i = 0; i < length; i++) { + if (isUpperCase(string.charAt(i))) { + char[] chars = string.toCharArray(); + for (; i < length; i++) { + char c = chars[i]; + if (isUpperCase(c)) { + chars[i] = (char) (c ^ 0x20); + } + } + return String.valueOf(chars); + } + } + return string; + } + + /** + * Returns a copy of the input character sequence in which all {@linkplain #isLowerCase(char) + * lowercase ASCII characters} have been converted to uppercase. All other characters are copied + * without modification. + * + * @param chars character sequence to upper case + * @return uppercase case value + * original since 14.0 + */ + public static String toUpperCase(CharSequence chars) { + if (chars instanceof String string) { + return toUpperCase(string); + } + char[] newChars = new char[chars.length()]; + for (int i = 0; i < newChars.length; i++) { + newChars[i] = toUpperCase(chars.charAt(i)); + } + return String.valueOf(newChars); + } + + /** + * Returns a copy of the input string in which all {@linkplain #isLowerCase(char) lowercase ASCII + * characters} have been converted to uppercase. All other characters are copied without + * modification. + * + * @param string string to upper case + * @return upper cased string + */ + public static String toUpperCase(String string) { + int length = string.length(); + for (int i = 0; i < length; i++) { + if (isLowerCase(string.charAt(i))) { + char[] chars = string.toCharArray(); + for (; i < length; i++) { + char c = chars[i]; + if (isLowerCase(c)) { + chars[i] = (char) (c ^ 0x20); + } + } + return String.valueOf(chars); + } + } + return string; + } + + /** + * Returns a copy of the input character sequence in which all {@linkplain #isUpperCase(char) + * uppercase ASCII characters} have been converted to lowercase. All other characters are copied + * without modification. + * + * @param chars character sequence to lower case + * @return lower case value + * original since 14.0 + */ + public static String toLowerCase(CharSequence chars) { + if (chars instanceof String) { + return toLowerCase((String) chars); + } + char[] newChars = new char[chars.length()]; + for (int i = 0; i < newChars.length; i++) { + newChars[i] = toLowerCase(chars.charAt(i)); + } + return String.valueOf(newChars); + } + + /** + * If the argument is an {@linkplain #isUpperCase(char) uppercase ASCII character} returns the + * lowercase equivalent. Otherwise returns the argument. + * + * @param c character + * @return character as a lower case + */ + public static char toLowerCase(char c) { + return isUpperCase(c) ? (char) (c ^ 0x20) : c; + } + + /** + * Indicates whether {@code c} is one of the twenty-six lowercase ASCII alphabetic characters + * between {@code 'a'} and {@code 'z'} inclusive. All others (including non-ASCII characters) + * return {@code false}. + * + * @param c character to check + * @return whether the character is lower case + */ + public static boolean isLowerCase(char c) { + // Note: This was benchmarked against the alternate expression "(char)(c - 'a') < 26" (Nov '13) + // and found to perform at least as well, or better. + return (c >= 'a') && (c <= 'z'); + } + + /** + * If the argument is a {@linkplain #isLowerCase(char) lowercase ASCII character} returns the + * uppercase equivalent. Otherwise returns the argument. + * @param c character + * @return character as a lower case + */ + public static char toUpperCase(char c) { + return isLowerCase(c) ? (char) (c ^ 0x20) : c; + } + + /** + * Indicates whether {@code c} is one of the twenty-six uppercase ASCII alphabetic characters + * between {@code 'A'} and {@code 'Z'} inclusive. All others (including non-ASCII characters) + * return {@code false}. + * + * @param c character to check + * @return whether the character is upper case + */ + public static boolean isUpperCase(char c) { + return (c >= 'A') && (c <= 'Z'); + } + +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/BufferData.java b/common/buffers/src/main/java/io/helidon/common/buffers/BufferData.java new file mode 100644 index 00000000000..ece3389b41b --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/BufferData.java @@ -0,0 +1,645 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.List; +import java.util.Objects; +import java.util.function.Function; + +/** + * Wrapper around a byte array. + */ +public interface BufferData { + /** + * Empty byte array. + */ + byte[] EMPTY_BYTES = new byte[0]; + + /** + * Fixed size buffer data. + * + * @param length length of the underlying buffer + * @return new byte array buffer data + */ + static BufferData create(int length) { + return new FixedBufferData(length); + } + + /** + * Fixed size buffer data fully written. + * @param bytes byte array + * @return new byte array buffer data with write position moved to the last byte + */ + static BufferData create(byte[] bytes) { + return new FixedBufferData(bytes); + } + + /** + * Fixed size buffer data fully written. + * + * @param bytes byte array + * @param position position within the byte array + * @param length number of bytes from position that contain the data + * @return new byte array buffer data read to be read + */ + static BufferData create(byte[] bytes, int position, int length) { + return new FixedBufferData(bytes, position, length); + } + + /** + * Fixed size buffer data fully written. + * @param bytes byte array + * @param offset offset within the byte array + * @param length length + * @return new byte array buffer data that are read only + */ + static BufferData createReadOnly(byte[] bytes, int offset, int length) { + return new ReadOnlyArrayData(bytes, offset, length); + } + + /** + * Growing buffer data. + * The buffer will grow when necessary to accommodate more bytes. + * @param initialLength initial buffer length + * @return growing buffer data + */ + static BufferData growing(int initialLength) { + return new GrowingBufferData(initialLength); + } + + /** + * Buffer data mapping multiple buffers. + * @param data data to wrap + * @return composite buffer data + */ + static BufferData create(BufferData... data) { + if (data.length == 1) { + return data[0]; + } + if (data.length == 0) { + return BufferUtil.EMPTY_BUFFER; + } + + return new CompositeArrayBufferData(data); + } + + /** + * Composite buffer data that are mutable. + * @return composite buffer + */ + static CompositeBufferData createComposite() { + return new CompositeListBufferData(); + } + + /** + * Composite buffer data that are mutable with initial value. + * + * @param first first buffer to be added to the composite buffer + * @return composite buffer + */ + static CompositeBufferData createComposite(BufferData first) { + return new CompositeListBufferData(first); + } + + /** + * Create composite buffer data from a list. + * + * @param data list of buffers to use + * @return composite buffer + */ + static BufferData create(List data) { + if (data.size() == 1) { + return data.iterator().next(); + } + if (data.isEmpty()) { + return BufferUtil.EMPTY_BUFFER; + } + return new CompositeListBufferData(data); + } + + /** + * Integer to binary string. + * + * @param value integer to print as binary + * @return binary value + */ + static String toBinaryString(int value) { + return BufferUtil.toBinaryString(value); + } + + /** + * Get 31 bits of an integer, ignoring the first bit. + * @param value integer value (32 bits) + * @return integer value (31 bits) + */ + static int toInt31(int value) { + return value & 0x7FFFFFFF; + } + + /** + * Empty buffer data. + * @return empty buffer + */ + static BufferData empty() { + return BufferUtil.EMPTY_BUFFER; + } + + /** + * Create buffer data from a string. + * @param stringValue UTF-8 string + * @return buffer data with bytes of the string + */ + static BufferData create(String stringValue) { + Objects.requireNonNull(stringValue); + if (stringValue.isEmpty()) { + return empty(); + } + return create(stringValue.getBytes(StandardCharsets.UTF_8)); + } + + /** + * Reset read and write position of this buffer. + * Does not impact length. + * + * @return this instance + */ + BufferData reset(); + + /** + * Set read position to 0, so others may read the same data from the start. + * + * @return this instance + */ + BufferData rewind(); + + /** + * Reset read and write position and make the buffer empty. + * Growing buffer length is set to 0 + * + * @return this instance + */ + BufferData clear(); + + /** + * Write all available bytes of this buffer to the output stream. + * + * @param out output stream + */ + void writeTo(OutputStream out); + + /** + * Read bytes from the input stream. + * Reads at least 1 byte. + * @param in input stream + * @return number of bytes read + */ + int readFrom(InputStream in); + + /** + * Read a single byte from this buffer. + * @return next byte + */ + int read(); + + /** + * Read bytes from this buffer into the provided buffer. + * @param bytes buffer to write to + * @return number of bytes actually written to the provided buffer + */ + default int read(byte[] bytes) { + return read(bytes, 0, bytes.length); + } + + /** + * Read bytes from this buffer into the provided buffer. + * + * @param bytes buffer to write to + * @param position position in the buffer + * @param length length that can be written + * @return actual number of bytes written + */ + int read(byte[] bytes, int position, int length); + + /** + * Do an operation on each byte in this buffer. + * + * @param length number of bytes to go through + * @param consumer function that consumes a byte and returns {@code true} if we should proceed to the next byte + */ + default void forEach(int length, Function consumer) { + for (int i = 0; i < length; i++) { + if (!consumer.apply((byte) read())) { + break; + } + } + } + + /** + * Read a UTF-8 string from this buffer. + * + * @param length number of bytes to read + * @return string from the bytes + */ + default String readString(int length) { + return readString(length, StandardCharsets.UTF_8); + } + + /** + * Read a string from this buffer. + * + * @param length number of bytes to read + * @param charset charset of the string, such as {@link java.nio.charset.StandardCharsets#UTF_8} or {@link java.nio.charset.StandardCharsets#US_ASCII} + * @return string from the bytes + */ + String readString(int length, Charset charset); + + /** + * Whether this buffer is fully consumed (all available bytes were read). + * @return if this buffer is consumed + */ + boolean consumed(); + + /** + * Read 16-bit integer. + * + * @return integer from the next 2 bytes + */ + default int readInt16() { + int ch1 = read(); + int ch2 = read(); + + return (ch1 << 8) + ch2; + } + + /** + * Read a 24-bit integer. + * + * @return integer from the next 3 bytes + */ + default int readInt24() { + int ch1 = read(); + int ch2 = read(); + int ch3 = read(); + + return (ch1 << 16) + (ch2 << 8) + ch3; + } + + /** + * Read 32-bit integer. + * + * @return integer from the next 4 bytes + */ + default int readInt32() { + int ch1 = read(); + int ch2 = read(); + int ch3 = read(); + int ch4 = read(); + + return (ch1 << 24) + (ch2 << 16) + (ch3 << 8) + ch4; + } + + /** + * Read 32-bit unsigned integer (must be represented as a long, as Java integer is 32-bit signed). + * + * @return long from the next 4 bytes + */ + default long readUnsignedInt32() { + int ch1 = read(); + int ch2 = read(); + int ch3 = read(); + int ch4 = read(); + + return ((long) ch1 << 24) + ((long) ch2 << 16) + ((long) ch3 << 8) + ch4; + } + + /** + * Read 64-bit long. + * + * @return long from the next 8 bytes + */ + default long readLong() { + return ((long) read() << 56) + + ((long) (read() & 255) << 48) + + ((long) (read() & 255) << 40) + + ((long) (read() & 255) << 32) + + ((long) (read() & 255) << 24) + + ((read() & 255) << 16) + + ((read() & 255) << 8) + + ((read() & 255)); + } + + /** + * Write 8-bit integer. + * + * @param number integer to write as a single byte + * @return this buffer + */ + default BufferData writeInt8(int number) { + return write(number); + } + + /** + * Write 16-bit integer. + * + * @param number integer to write as 2 bytes + * @return this buffer + */ + default BufferData writeInt16(int number) { + write((byte) (number >>> 8)); + write((byte) number); + return this; + } + + /** + * Write 24-bit integer. + * + * @param number integer to write as 3 bytes + * @return this buffer + */ + default BufferData writeInt24(int number) { + write((byte) (number >>> 16)); + write((byte) (number >>> 8)); + write((byte) number); + return this; + } + + /** + * Write 32-bit integer. + * + * @param number integer to write as 4 bytes + * @return this buffer + */ + default BufferData writeInt32(int number) { + write((byte) (number >>> 24)); + write((byte) (number >>> 16)); + write((byte) (number >>> 8)); + write((byte) number); + return this; + } + + /** + * Write 32-bit unsigned integer. + * + * @param number long to write as 4 bytes + * @return this buffer + */ + default BufferData writeUnsignedInt32(long number) { + write((byte) (number >>> 24)); + write((byte) (number >>> 16)); + write((byte) (number >>> 8)); + write((byte) number); + return this; + } + + /** + * Write a byte. + * + * @param value value + * @return this buffer + */ + BufferData write(int value); + + /** + * Write n bytes from this buffer to the provided buffer. + * + * @param writeBuffer buffer to write to + * @param length number of bytes to write + * @return number of bytes actually written + */ + int writeTo(ByteBuffer writeBuffer, int length); + + /** + * Write the byte array to this buffer. + * + * @param bytes byte to write + */ + default void write(byte[] bytes) { + write(bytes, 0, bytes.length); + } + + /** + * Write the byte array to this buffer. + * + * @param bytes bytes to write + * @param offset offset within the array + * @param length number of bytes to write (from the offset) + */ + void write(byte[] bytes, int offset, int length); + + /** + * Write the provided buffer to this buffer. + * @param toWrite buffer to write + */ + default void write(BufferData toWrite) { + write(toWrite, toWrite.available()); + } + + /** + * Write n bytes from the provided buffer to this buffer. + * @param toWrite buffer to write + * @param length number of bytes to write + */ + void write(BufferData toWrite, int length); + + /** + * HPack integer value (may be 1 or more bytes). + * + * TODO enforce limit that the hpack int is max 4 bytes (as otherwise we overflow int and this may be an attack) + * TODO enforce limit to string values (not here, but on headers processing) + * + * @param originalValue value (only bitsOfPrefix are used, bits before that are ignored) + * @param bitsOfPrefix number of bits significant in the value + * @return integer value + */ + default int readHpackInt(int originalValue, int bitsOfPrefix) { + // significant bits of the value + + int value = originalValue & (0b11111111 >> (8 - bitsOfPrefix)); + /* + The value is computed as + max + (read integer from the next x 7 bits, where next 7 bits are read if the first bit of the octet is 1) + */ + int max = (1 << bitsOfPrefix) - 1; + if (value < max) { // 31 is the max number of 5 bits + // value fits into the prefix, no need to read additional bytes + // System.out.println("Resolved original value " + Integer.toBinaryString(originalValue) + " to " + value); + return value; + } + //System.out.println("Original value " + Integer.toBinaryString(originalValue) + " has more than one byte, carry on: " + // + value); + int shiftBy = 0; + while (true) { + int next = read(); + //System.out.println("Read additional byte " + Integer.toBinaryString(next)); + value += (next & 0b01111111) << shiftBy; // add all valid bits to the number and continue next cycle + shiftBy += 7; // the next iteration must be shifted by 7 additional bits + + if ((next & 0b10000000) == 0) { + // last byte + //System.out.println("Resolved values to " + value); + return value; + } + } + } + + /** + * Write hpack integer to this buffer. + * + * @param value the full value we want to write + * @param prefixedInt value to store in the other bits of the first byte + * @param bitPrefix bits reserved for our value in the first byte + * @return this instance + */ + default BufferData writeHpackInt(int value, int prefixedInt, int bitPrefix) { + // we do not want possible garbage from wrong value + int prefixedValue = prefixedInt & (~(0b11111111 >> (8 - bitPrefix))); + + int max = (1 << bitPrefix) - 1; + if (value < max) { + write(value | prefixedValue); + return this; + } + write(max | prefixedValue); + + // now encode the rest of the value + int remainingValue = value - max; + while (true) { + if (remainingValue < (1 << 7)) { + write(remainingValue); + return this; + } + // write the seven bits + the first bit to mark this continues + int toWrite = (remainingValue & 0b01111111) | 0b10000000; + write(toWrite); + + // shift to the right by seven bits + remainingValue = remainingValue >> 7; + } + } + + /** + * Debug this buffer as binary string. + * + * @return binary string debug data (including headers) + */ + String debugDataBinary(); + + /** + * Debug this buffer as hex string. + * + * @param fullBuffer whether to debug all bytes in this buffer ({@code true}), or only unread bytes {@code false}) + * @return hex debug data (including headers) + */ + String debugDataHex(boolean fullBuffer); + + /** + * Debug the full buffer. + * @return hex debug data (including headers) + */ + default String debugDataHex() { + return debugDataHex(true); + } + + /** + * Copy the underlying data into a new buffer that does not retain any reference. + * Reads this buffer fully and creates a new instance that is not completed. + * + * @return copy of data + */ + default BufferData copy() { + byte[] copy = new byte[available()]; + read(copy, 0, available()); + return BufferData.create(copy); + } + + /** + * Number of bytes available for reading. + * + * @return available byte + */ + int available(); + + /** + * Skip the next n bytes (move read position). + * @param length number of bytes to skip + */ + void skip(int length); + + /** + * Find index of the provided byte from the current position. + * + * @param aByte byte to find + * @return index of the byte, or {@code -1} if not found + */ + int indexOf(byte aByte); + + /** + * Find last index of the provided byte from the current position. + * + * @param aByte byte to find + * @return index of the byte, or {@code -1} if not found + */ + default int lastIndexOf(byte aByte) { + return lastIndexOf(aByte, available()); + } + + /** + * Find last index of the provided byte from the current position. + * + * @param aByte byte to find + * @param length maximal length to search for (e.g. this will be the last byte before reaching the length) + * @return index of the byte, or {@code -1} if not found + */ + int lastIndexOf(byte aByte, int length); + + /** + * Trim the last x bytes from a buffer (remove them). + * + * @param x trim by this number of bytes + * @return this instance + */ + BufferData trim(int x); + + /** + * Number of bytes that can be written to this instance. + * + * @return capacity of this buffer + */ + int capacity(); + + /** + * Write ascii string to this buffer. + * @param text ascii string to write + */ + default void writeAscii(String text) { + write(text.getBytes(StandardCharsets.US_ASCII)); + } + + /** + * Get byte at index (current read position + index). + * Does not modify any position. + * + * @param index index to get + * @return byte at the index + */ + int get(int index); +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/BufferUtil.java b/common/buffers/src/main/java/io/helidon/common/buffers/BufferUtil.java new file mode 100644 index 00000000000..06051247119 --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/BufferUtil.java @@ -0,0 +1,148 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +final class BufferUtil { + static final BufferData EMPTY_BUFFER = new FixedBufferData(0); + + private static final String ZEROES = "00000000"; + private static final String BINARY_LINE = + "+--------+----------+\n"; + private static final String BINARY_HEADER = + BINARY_LINE + + "| index | 01234567 |\n" + + BINARY_LINE; + private static final String HEX_LINE = + "+--------+-------------------------------------------------+----------------+\n"; + private static final String HEX_HEADER = + HEX_LINE + + "| index| 0 1 2 3 4 5 6 7 8 9 a b c d e f | data|\n" + + HEX_LINE; + + private BufferUtil() { + } + + static String toBinaryString(int value) { + String binary = Integer.toBinaryString(value); + return ZEROES.substring(binary.length()) + binary; + } + + static String debugDataBinary(byte[] bytes, int position, int length) { + StringBuilder stringBuilder = new StringBuilder(BINARY_LINE.length() * length // each byte + + BINARY_LINE.length() // last line + + BINARY_HEADER.length()); //first line + + stringBuilder.append(BINARY_HEADER); + + int counter = 0; + for (int i = position; i < length; i++) { + byte toPrint = bytes[i]; + + stringBuilder.append("|") + .append(toIndexString(counter)) + .append("| ") + .append(toBinaryString(toPrint & 0xFF)) + .append(" |\n"); + counter++; + } + stringBuilder.append(BINARY_LINE); + return stringBuilder.toString(); + } + + static String debugDataHex(byte[] bytes, int position, int length) { + /* + +-------------------------------------------------+ + | 0 1 2 3 4 5 6 7 8 9 a b c d e f | ++--------+-------------------------------------------------+----------------+ +|00000000| 47 45 54 20 2f 6c 6f 6f 6d 2f 71 75 69 63 6b 20 |GET /loom/quick | +|00000010| 48 54 54 50 2f 31 2e 31 0d 0a 68 6f 73 74 3a 20 |HTTP/1.1..host: | + ++--------+-------------------------------------------------+----------------+ +| index| 0 1 2 3 4 5 6 7 8 9 a b c d e f | data| ++--------+-------------------------------------------------+----------------+ +| 0| 55 |U | ++--------+-------------------------------------------------+----------------+ + */ + StringBuilder stringBuilder = new StringBuilder(HEX_LINE.length() * (length / 16) // each 16 bytes + + 1 // to cover the first line + + HEX_LINE.length() // last line + + HEX_LINE.length()); //first line + + stringBuilder.append(HEX_HEADER); + StringBuilder line = new StringBuilder(16); + + int counter = 0; + for (int i = position; i < length; i++) { + // | position | 0 1 2 3 4 5 6 7 8 9 a b c d e f |text....| + + if (counter % 16 == 0) { + line.setLength(0); + stringBuilder.append("|") + .append(toIndexString(counter)) + .append("| "); + } + + byte toPrint = bytes[i]; + int toPrintInt = toPrint & 0xFF; + String hex = Integer.toHexString(toPrintInt); + hex = ZEROES.substring(hex.length() + 6) + hex; + stringBuilder.append(hex) + .append(" "); + appendChar(line, toPrintInt); + + counter++; + if (counter % 16 == 0) { + stringBuilder.append("|"); + stringBuilder.append(line); + stringBuilder.append("|\n"); + } + } + // we need to finish the line (find out how many missing + int missing = 16 - (counter % 16); + if (counter != 0 && missing != 16) { + stringBuilder.append(" ".repeat(missing)); + line.append(" ".repeat(missing)); + stringBuilder.append("|"); + stringBuilder.append(line); + stringBuilder.append("|\n"); + } + + stringBuilder.append(HEX_LINE); + return stringBuilder.toString(); + } + + private static String toIndexString(int index) { + String positionString = Integer.toHexString(index); + int len = positionString.length(); + if (len >= 8) { + return positionString; + } + return ZEROES.substring(len) + positionString; + } + + private static void appendChar(StringBuilder line, int codePoint) { + Character.UnicodeBlock block = Character.UnicodeBlock.of(codePoint); + + if (!Character.isISOControl(codePoint) + && block != null + && block != Character.UnicodeBlock.SPECIALS) { + line.append((char) codePoint); + } else { + line.append('.'); + } + } +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/Bytes.java b/common/buffers/src/main/java/io/helidon/common/buffers/Bytes.java new file mode 100644 index 00000000000..379d8ef975b --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/Bytes.java @@ -0,0 +1,70 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +/** + * Bytes commonly used in HTTP. + */ +public final class Bytes { + /** + * {@code :} byte. + */ + public static final byte COLON_BYTE = (byte) ':'; + /** + * {@code } (space) byte. + */ + public static final byte SPACE_BYTE = (byte) ' '; + /** + * {@code \n} (new line) byte. + */ + public static final byte LF_BYTE = (byte) '\n'; + /** + * {@code \r} (carriage return) byte. + */ + public static final byte CR_BYTE = (byte) '\r'; + /** + * {@code /} byte. + */ + public static final byte SLASH_BYTE = (byte) '/'; + /** + * {@code ;} byte. + */ + public static final byte SEMICOLON_BYTE = (byte) ';'; + /** + * {@code ?} byte. + */ + public static final byte QUESTION_MARK_BYTE = (byte) '?'; + /** + * {@code #} byte. + */ + public static final byte HASH_BYTE = (byte) '#'; + /** + * {@code =} byte. + */ + public static final byte EQUALS_BYTE = (byte) '='; + /** + * {@code &} byte. + */ + public static final byte AMPERSAND_BYTE = (byte) '&'; + /** + * {@code %} byte. + */ + public static final byte PERCENT_BYTE = (byte) '%'; + + private Bytes() { + } +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/CompositeArrayBufferData.java b/common/buffers/src/main/java/io/helidon/common/buffers/CompositeArrayBufferData.java new file mode 100644 index 00000000000..c9d33a6cccd --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/CompositeArrayBufferData.java @@ -0,0 +1,263 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +class CompositeArrayBufferData extends ReadOnlyBufferData { + private final BufferData[] data; + + CompositeArrayBufferData(BufferData[] data) { + this.data = data; + } + + @Override + public BufferData rewind() { + for (BufferData datum : data) { + datum.rewind(); + } + return this; + } + + @Override + public void writeTo(OutputStream out) { + copy().writeTo(out); + } + + @Override + public int readFrom(InputStream in) { + for (BufferData datum : data) { + if (datum.capacity() > 0) { + return datum.readFrom(in); + } + } + throw new IllegalStateException("The composite buffer is fully written, cannot write additional bytes"); + } + + @Override + public int read() { + for (BufferData datum : data) { + if (!datum.consumed()) { + return datum.read(); + } + } + + throw new ArrayIndexOutOfBoundsException("This buffer has no more bytes"); + } + + @Override + public int read(byte[] buffer, int position, int length) { + int myPosition = position; + int remaining = length; + + for (BufferData datum : data) { + if (datum.available() > 0) { + int read = datum.read(buffer, myPosition, remaining); + myPosition += read; + remaining -= read; + if (remaining == 0) { + break; + } + } + } + + return myPosition - position; + } + + @Override + public String readString(int length, Charset charset) { + if (length > available()) { + throw new ArrayIndexOutOfBoundsException("Requested " + length + " bytes, but only " + available() + + " were available\n" + debugDataHex(false)); + } + byte[] buffer = new byte[length]; + + int read = 0; + int current; + while (read != length && available() > 0) { + current = read(buffer, 0, length); + read += current; + if (current == 0) { + throw new IllegalStateException("Read 0 bytes when available: " + available() + "\n" + debugDataHex(false)); + } + } + if (read != length) { + throw new ArrayIndexOutOfBoundsException("Requested " + length + " bytes, but only " + read + " were available"); + } + + return new String(buffer, charset); + } + + @Override + public boolean consumed() { + boolean consumed = true; + + for (BufferData datum : data) { + if (!datum.consumed()) { + consumed = false; + } + } + + return consumed; + } + + @Override + public int writeTo(ByteBuffer writeBuffer, int limit) { + int written = 0; + + for (BufferData datum : data) { + if (datum.consumed()) { + continue; + } + int datumWrote = datum.writeTo(writeBuffer, limit - written); + if (datumWrote == 0) { + // not consumed and wrote 0 -> full + break; + } + written += datumWrote; + } + + return written; + } + + @Override + public String debugDataBinary() { + StringBuilder result = new StringBuilder(); + + for (BufferData datum : data) { + result.append(datum.debugDataBinary()); + } + + return result.toString(); + } + + @Override + public String debugDataHex(boolean fullBuffer) { + StringBuilder result = new StringBuilder(); + + for (BufferData datum : data) { + result.append(datum.debugDataHex(fullBuffer)); + } + + return result.toString(); + } + + @Override + public int available() { + int available = 0; + for (BufferData datum : data) { + available += datum.available(); + } + return available; + } + + @Override + public void skip(int length) { + int remaining = length; + for (BufferData datum : data) { + int toSkip = Math.min(datum.available(), remaining); + datum.skip(toSkip); + remaining -= toSkip; + if (remaining <= 0) { + return; + } + } + } + + @Override + public int indexOf(byte aByte) { + int index; + int indexPrefix = 0; + + for (BufferData datum : data) { + index = datum.indexOf(aByte); + if (index > -1) { + return indexPrefix + index; + } + indexPrefix += datum.available(); + } + + return -1; + } + + @Override + public int lastIndexOf(byte aByte, int length) { + int index; + int lengthRemaining = length; + + for (int i = data.length - 1; i >= 0; i--) { + BufferData datum = data[i]; + index = datum.lastIndexOf(aByte, Math.min(lengthRemaining, datum.available())); + if (index > -1) { + return index; + } + lengthRemaining -= datum.available(); + if (lengthRemaining <= 0) { + break; + } + } + return -1; + } + + @Override + public BufferData trim(int x) { + if (available() < x) { + throw new IllegalArgumentException("Trimming more bytes than available"); + } + int toRemove = x; + for (int i = data.length - 1; i > -1; i--) { + if (toRemove == 0) { + return this; + } + BufferData datum = data[i]; + if (datum.available() > 0) { + int removed = Math.min(datum.available(), toRemove); + toRemove -= removed; + datum.trim(removed); + } + } + if (toRemove == 0) { + return this; + } + + throw new IllegalStateException("Could not trim buffer by " + x + " bytes"); + } + + @Override + public int get(int index) { + int inDataIndex = index; + + for (BufferData datum : data) { + int available = datum.available(); + + if (available <= inDataIndex) { + inDataIndex -= available; + continue; + } + return datum.get(inDataIndex); + } + throw new ArrayIndexOutOfBoundsException("Invalid index to get: " + index); + } + + @Override + public String toString() { + return "comp-array: a=" + available(); + } +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/CompositeBufferData.java b/common/buffers/src/main/java/io/helidon/common/buffers/CompositeBufferData.java new file mode 100644 index 00000000000..5e0c5909ea0 --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/CompositeBufferData.java @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +/** + * Composite (mutable) buffer data. + */ +public interface CompositeBufferData extends BufferData { + /** + * Add this buffer to the list of buffers already in this composite buffer. + * + * @param bufferData buffer data to add to this composite buffer + * @return this instance + */ + CompositeBufferData add(BufferData bufferData); +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/CompositeListBufferData.java b/common/buffers/src/main/java/io/helidon/common/buffers/CompositeListBufferData.java new file mode 100644 index 00000000000..bf5fcdaefec --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/CompositeListBufferData.java @@ -0,0 +1,269 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; + +class CompositeListBufferData extends ReadOnlyBufferData implements CompositeBufferData { + private final List data; + + CompositeListBufferData() { + this.data = new LinkedList<>(); + } + + CompositeListBufferData(BufferData initial) { + this.data = new LinkedList<>(); + this.data.add(initial); + } + + CompositeListBufferData(List data) { + this.data = new ArrayList<>(data); + } + + @Override + public CompositeBufferData add(BufferData bufferData) { + data.add(bufferData); + return this; + } + + @Override + public BufferData rewind() { + for (BufferData datum : data) { + datum.rewind(); + } + return this; + } + + @Override + public void writeTo(OutputStream out) { + copy().writeTo(out); + } + + @Override + public int readFrom(InputStream in) { + for (BufferData datum : data) { + if (datum.capacity() > 0) { + return datum.readFrom(in); + } + } + throw new IllegalStateException("The composite buffer is fully written, cannot write additional bytes"); + } + + @Override + public int read() { + for (BufferData datum : data) { + if (!datum.consumed()) { + return datum.read(); + } + } + + throw new ArrayIndexOutOfBoundsException("This buffer has no more bytes"); + } + + @Override + public int read(byte[] buffer, int position, int length) { + int myPosition = position; + int remaining = length; + + for (BufferData datum : data) { + if (datum.available() > 0) { + int read = datum.read(buffer, myPosition, remaining); + myPosition += read; + remaining -= read; + if (remaining == 0) { + break; + } + } + } + + return myPosition - position; + } + + @Override + public String readString(int length, Charset charset) { + byte[] buffer = new byte[length]; + + int read = read(buffer, 0, length); + if (read != length) { + throw new ArrayIndexOutOfBoundsException("Requested " + length + "bytes, but only " + read + " were available"); + } + return new String(buffer, charset); + } + + @Override + public boolean consumed() { + boolean consumed = true; + + for (BufferData datum : data) { + if (!datum.consumed()) { + consumed = false; + } + } + + return consumed; + } + + @Override + public int writeTo(ByteBuffer writeBuffer, int limit) { + int written = 0; + + for (BufferData datum : data) { + if (datum.consumed()) { + continue; + } + int datumWrote = datum.writeTo(writeBuffer, limit - written); + if (datumWrote == 0) { + // not consumed and wrote 0 -> full + break; + } + written += datumWrote; + } + + return written; + } + + @Override + public String debugDataBinary() { + StringBuilder result = new StringBuilder(); + + for (BufferData datum : data) { + result.append(datum.debugDataBinary()); + } + + return result.toString(); + } + + @Override + public String debugDataHex(boolean fullBuffer) { + StringBuilder result = new StringBuilder(); + + for (BufferData datum : data) { + result.append(datum.debugDataHex(fullBuffer)); + } + + return result.toString(); + } + + @Override + public int available() { + int available = 0; + for (BufferData datum : data) { + available += datum.available(); + } + return available; + } + + @Override + public void skip(int length) { + int remaining = length; + for (BufferData datum : data) { + int toSkip = Math.min(datum.available(), remaining); + datum.skip(toSkip); + remaining -= toSkip; + if (remaining <= 0) { + return; + } + } + } + + @Override + public int indexOf(byte aByte) { + int index; + int indexPrefix = 0; + + for (BufferData datum : data) { + index = datum.indexOf(aByte); + if (index > -1) { + return indexPrefix + index; + } + indexPrefix += datum.available(); + } + + return -1; + } + + @Override + public int lastIndexOf(byte aByte, int length) { + int index; + int lengthRemaining = length; + + for (int i = data.size() - 1; i >= 0; i--) { + BufferData datum = data.get(i); + index = datum.lastIndexOf(aByte, Math.min(lengthRemaining, datum.available())); + if (index > -1) { + return index; + } + lengthRemaining -= datum.available(); + if (lengthRemaining <= 0) { + break; + } + } + return -1; + } + + @Override + public BufferData trim(int x) { + if (available() < x) { + throw new IllegalArgumentException("Trimming more bytes than available"); + } + int toRemove = x; + List bufferList = new ArrayList<>(data); + for (int i = bufferList.size() - 1; i > -1; i--) { + if (toRemove == 0) { + return this; + } + BufferData datum = bufferList.get(i); + if (datum.available() > 0) { + int removed = Math.min(datum.available(), toRemove); + toRemove -= removed; + datum.trim(removed); + } + } + if (toRemove == 0) { + return this; + } + + throw new IllegalStateException("Could not trim buffer by " + x + " bytes"); + } + + @Override + public int get(int index) { + int inDataIndex = index; + + for (BufferData datum : data) { + int available = datum.available(); + + if (available <= inDataIndex) { + inDataIndex -= available; + continue; + } + return datum.get(inDataIndex); + } + throw new ArrayIndexOutOfBoundsException("Invalid index to get: " + index); + } + + @Override + public String toString() { + return "comp-list: a=" + available(); + } +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/DataListener.java b/common/buffers/src/main/java/io/helidon/common/buffers/DataListener.java new file mode 100644 index 00000000000..60110c1701a --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/DataListener.java @@ -0,0 +1,44 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +/** + * A listener for data. + * + * @param type of context + */ +public interface DataListener { + /** + * Data received or sent. + * + * @param ctx context + * @param data data + */ + default void data(T ctx, BufferData data) { + } + + /** + * Data received or sent. + * + * @param ctx context + * @param data byte array + * @param position position within the byte array + * @param offset number of bytes + */ + default void data(T ctx, byte[] data, int position, int offset) { + } +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/DataReader.java b/common/buffers/src/main/java/io/helidon/common/buffers/DataReader.java new file mode 100644 index 00000000000..6d278a95743 --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/DataReader.java @@ -0,0 +1,479 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Supplier; + +/** + * Data reader that can pull additional data. + */ +public class DataReader { + private final Supplier bytesSupplier; + private final boolean ignoreLoneEol; + private Node head; + private Node tail; + private DataListener listener; + private Object context; + + /** + * Data reader from a supplier of bytes. + * + * @param bytesSupplier supplier that can be pulled for more data + */ + public DataReader(Supplier bytesSupplier) { + this.ignoreLoneEol = false; + this.bytesSupplier = bytesSupplier; + // we cannot block until data is actually ready to be consumed + this.head = new Node(BufferData.EMPTY_BYTES); + this.tail = this.head; + } + + /** + * Data reader from a supplier of bytes. + * + * @param bytesSupplier supplier that can be pulled for more data + * @param ignoreLoneEol ignore LF without CR and CR without LF + */ + public DataReader(Supplier bytesSupplier, boolean ignoreLoneEol) { + this.ignoreLoneEol = ignoreLoneEol; + this.bytesSupplier = bytesSupplier; + // we cannot block until data is actually ready to be consumed + this.head = new Node(BufferData.EMPTY_BYTES); + this.tail = this.head; + } + + /** + * Number of bytes available in the currently pulled data. + * + * @return number of bytes available + */ + public int available() { + int a = 0; + for (Node n = head; n != null; n = n.next) { + a += n.available(); + } + return a; + } + + /** + * Pull next data. + */ + public void pullData() { + byte[] bytes = bytesSupplier.get(); + if (bytes == null) { + throw new InsufficientDataAvailableException(); + } + Node n = new Node(bytes); + tail.next = n; + tail = n; + } + + /** + * Skip n bytes. + * + * @param lenToSkip number of bytes to skip (must be less or equal to current capacity) + */ + public void skip(int lenToSkip) { + while (lenToSkip > 0) { + ensureAvailable(); + lenToSkip = head.skip(lenToSkip); + } + } + + /** + * Ensure we have at least one byte available. + */ + // remove consumed head of the list + // make sure that head has available + // may block to read + public void ensureAvailable() { + while (!head.hasAvailable()) { + if (head.next == null) { + pullData(); + } + head = head.next; + } + } + + /** + * Read 1 byte. + * + * @return next byte + */ + public byte read() { + ensureAvailable(); + return head.bytes[head.position++]; + } + + /** + * Look at the next byte (does not modify position). + * + * @return next byte + */ + public byte lookup() { + ensureAvailable(); + return head.bytes[head.position]; + } + + /** + * Does the data start with a new line (CRLF). + * + * @return whether the data starts with a new line (will pull data to have at least two bytes available) + */ + public boolean startsWithNewLine() { + ensureAvailable(); + byte[] bytes = head.bytes; + int pos = head.position; + if (bytes[pos] == Bytes.CR_BYTE && ((pos + 1 < bytes.length) ? bytes[pos + 1] : head.next().peek()) == Bytes.LF_BYTE) { + return true; + } + return false; + } + + /** + * Does the current data start with the prefix. + * + * @param prefix prefix to find, will pull data to have at least prefix.length bytes available + * @return whether the data starts with the provided prefix + */ + public boolean startsWith(byte[] prefix) { + ensureAvailable(); // we have at least 1 byte + if (prefix.length <= head.available()) { // fast case + return Arrays.equals(head.bytes, head.position, head.position + prefix.length, prefix, 0, prefix.length); + } else { + int offset = 0; + int remaining = prefix.length; + for (Node n = head; remaining > 0; n = n.next) { + int toCmp = Math.min(remaining, n.available()); + if (!Arrays.equals(n.bytes, n.position, n.position + toCmp, prefix, offset, offset + toCmp)) { + return false; + } + remaining -= toCmp; + offset += toCmp; + if (remaining > 0 && n.next == null) { + pullData(); + } + } + return true; + } + } + + /** + * Read next buffer. + * Will read {@link #available()} number of bytes into a buffer and move position. + * + * @return buffer data wrapping the available bytes + */ + public BufferData readBuffer() { + ensureAvailable(); + int size = head.available(); + BufferData result = BufferData.create(head.bytes, head.position, size); + skip(size); + return result; + } + + /** + * Read next buffer of defined size. Will pull additional data if length is not available. + * Will move position. + * + * @param length length of data to read + * @return buffer data with the length requested + */ + public BufferData readBuffer(int length) { + BufferData data = getBuffer(length); // TODO optimization - merge getChunk and skip into one loop; if required + skip(length); + return data; + } + + /** + * Get the next buffer of the requested size without moving position. + * + * @param length bytes to read + * @return buffer data with the length requested + */ + public BufferData getBuffer(int length) { + ensureAvailable(); // we have at least 1 byte + if (length <= head.available()) { // fast case + return new ReadOnlyArrayData(head.bytes, head.position, length); + } else { + List data = new ArrayList<>(); + int remaining = length; + for (Node n = head; remaining > 0; n = n.next) { + int toAdd = Math.min(remaining, n.available()); + data.add(new ReadOnlyArrayData(n.bytes, n.position, toAdd)); + remaining -= toAdd; + if (remaining > 0 && n.next == null) { + pullData(); + } + } + return BufferData.create(data); + } + } + + /** + * Read the next {@code len} bytes as a {@link LazyString}. + * This should be used for example for headers, where we want to materialize the string only when needed. + * + * @param charset character set to use + * @param len number of bytes of the string + * @return lazy string + */ + public LazyString readLazyString(Charset charset, int len) { + ensureAvailable(); // we have at least 1 byte + if (len <= head.available()) { // fast case + LazyString s = new LazyString(head.bytes, head.position, len, charset); + head.position += len; + return s; + } else { + byte[] b = new byte[len]; + int remaining = len; + for (Node n = head; remaining > 0; n = n.next) { + ensureAvailable(); + int toAdd = Math.min(remaining, n.available()); + System.arraycopy(n.bytes, n.position, b, len - remaining, toAdd); + remaining -= toAdd; + n.position += toAdd; + if (remaining > 0 && n.next == null) { + pullData(); + } + } + return new LazyString(b, charset); + } + } + + /** + * Read ascii string. + * + * @param len number of bytes of the string + * @return string value + */ + public String readAsciiString(int len) { + ensureAvailable(); // we have at least 1 byte + if (len <= head.available()) { // fast case + String s = new String(head.bytes, head.position, len, StandardCharsets.US_ASCII); + head.position += len; + return s; + } else { + byte[] b = new byte[len]; + int remaining = len; + for (Node n = head; remaining > 0; n = n.next) { + ensureAvailable(); + int toAdd = Math.min(remaining, n.available()); + System.arraycopy(n.bytes, n.position, b, len - remaining, toAdd); + remaining -= toAdd; + n.position += toAdd; + if (remaining > 0 && n.next == null) { + pullData(); + } + } + return new String(b, StandardCharsets.US_ASCII); + } + } + + /** + * Read an ascii string until new line. + * + * @return string with the next line + * @throws io.helidon.common.buffers.DataReader.IncorrectNewLineException when new line cannot be found + */ + public String readLine() throws IncorrectNewLineException { + int i = findNewLine(Integer.MAX_VALUE); + String s = readAsciiString(i); + skip(2); + return s; + } + + /** + * Find the byte or next new line. + * + * @param b - byte to find + * @param max - search limit + * @return i > 0 - index; + * i == max - not found; + * i < 0 - new line found at (-i-1) position + * @throws io.helidon.common.buffers.DataReader.IncorrectNewLineException in case new line was incorrect (such as CR not before LF) + */ + public int findOrNewLine(byte b, int max) throws IncorrectNewLineException { + ensureAvailable(); + int idx = 0; + Node n = head; + while (true) { + byte[] barr = n.bytes; + for (int i = n.position; i < barr.length && idx < max; i++, idx++) { + if (barr[i] == Bytes.LF_BYTE && !ignoreLoneEol) { + throw new IncorrectNewLineException("Found LF (" + idx + ") without preceding CR. :\n" + this.debugDataHex()); + } else if (barr[i] == Bytes.CR_BYTE) { + byte nextByte; + if (i + 1 < barr.length) { + nextByte = barr[i + 1]; + } else { + nextByte = n.next().peek(); + } + if (nextByte == Bytes.LF_BYTE) { + return -idx - 1; + } + if (!ignoreLoneEol) { + throw new IncorrectNewLineException("Found CR (" + idx + + ") without following LF. :\n" + this.debugDataHex()); + } + } else if (barr[i] == b) { + return idx; + } + } + if (idx == max) { + return max; + } + n = n.next(); + } + } + + /** + * Debug data as a hex string. + * + * @return hex string, including headers + */ + public String debugDataHex() { + return getBuffer(available()).debugDataHex(true); + } + + /** + * Find new line with the next n bytes. + * + * @param max length to search + * @return index of the new line, or max if not found + * @throws io.helidon.common.buffers.DataReader.IncorrectNewLineException + */ + public int findNewLine(int max) throws IncorrectNewLineException { + ensureAvailable(); + int idx = 0; + Node n = head; + while (true) { + byte[] barr = n.bytes; + for (int i = n.position; i < barr.length && idx < max; i++, idx++) { + if (barr[i] == Bytes.LF_BYTE && !ignoreLoneEol) { + throw new IncorrectNewLineException("Found LF (" + idx + ") without preceding CR. :\n" + this.debugDataHex()); + } else if (barr[i] == Bytes.CR_BYTE) { + byte nextByte; + if (i + 1 < barr.length) { + nextByte = barr[i + 1]; + } else { + nextByte = n.next().peek(); + } + if (nextByte == Bytes.LF_BYTE) { + return idx; + } + if (!ignoreLoneEol) { + throw new IncorrectNewLineException("Found CR (" + idx + + ") without following LF. :\n" + this.debugDataHex()); + } + } + } + if (idx == max) { + return max; + } + n = n.next(); + } + } + + /** + * Configure data listener. + * + * @param listener listener to write information to + * @param context context + * @param type of the context + */ + public void listener(DataListener listener, T context) { + this.listener = listener; + this.context = context; + } + + /** + * New line not valid. + */ + public static class IncorrectNewLineException extends RuntimeException { + /** + * Incorrect new line. + * + * @param message descriptive message + */ + public IncorrectNewLineException(String message) { + super(message); + } + } + + /** + * Not enough data available to finish the requested operation. + */ + public static class InsufficientDataAvailableException extends RuntimeException { + } + + private class Node { + private final byte[] bytes; + private int position; + private Node next; + + Node(byte[] bytes) { + this.bytes = bytes; + } + + @Override + public String toString() { + return position + " of " + Arrays.toString(bytes); + } + + int available() { + return bytes.length - position; + } + + boolean hasAvailable() { + return position < bytes.length; + } + + /* + * returns number of skipped bytes + */ + int skip(int lenToSkip) { + int newPos = position + lenToSkip; + if (newPos <= bytes.length) { + position = newPos; + return 0; + } else { + lenToSkip -= (bytes.length - position); + position = bytes.length; + return lenToSkip; + } + } + + Node next() { + if (this.next == null) { + assert this == tail; + pullData(); + assert this.next != null; + } + return this.next; + } + + byte peek() { + return bytes[position]; + } + } +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/FixedBufferData.java b/common/buffers/src/main/java/io/helidon/common/buffers/FixedBufferData.java new file mode 100644 index 00000000000..fcf1dd7e2db --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/FixedBufferData.java @@ -0,0 +1,231 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +class FixedBufferData implements BufferData { + private final byte[] bytes; + private final int length; + private int writePosition; + private int readPosition; + + FixedBufferData(int length) { + this.bytes = new byte[length]; + this.length = length; + } + + FixedBufferData(byte[] bytes) { + this.bytes = bytes; + this.length = bytes.length; + this.writePosition = this.length; + } + + FixedBufferData(byte[] bytes, int position, int length) { + this.bytes = bytes; + this.length = length; + this.writePosition = length; + this.readPosition = position; + } + + @Override + public FixedBufferData reset() { + this.writePosition = 0; + this.readPosition = 0; + return this; + } + + @Override + public BufferData rewind() { + this.readPosition = 0; + return this; + } + + @Override + public BufferData clear() { + return reset(); + } + + @Override + public void writeTo(OutputStream out) { + try { + out.write(bytes, readPosition, writePosition); + readPosition = writePosition; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public int readFrom(InputStream in) { + int toRead = length - writePosition; + int read; + try { + read = in.read(bytes, writePosition, toRead); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + if (read == -1) { + return read; + } + writePosition += read; + return read; + } + + @Override + public int read() { + if (readPosition >= writePosition) { + throw new ArrayIndexOutOfBoundsException("This buffer has " + length + + " bytes, requested to read at " + readPosition); + } + return bytes[readPosition++] & 0xFF; + } + + @Override + public int read(byte[] bytes, int position, int length) { + int available = this.writePosition - readPosition; + int toRead = Math.min(length, available); + + System.arraycopy(this.bytes, readPosition, bytes, position, toRead); + + readPosition += toRead; + return toRead; + } + + @Override + public String readString(int length, Charset charset) { + String result = new String(bytes, readPosition, length, charset); + readPosition += length; + return result; + } + + public boolean consumed() { + return readPosition == writePosition; + } + + public FixedBufferData write(int value) { + this.bytes[writePosition++] = (byte) value; + return this; + } + + @Override + public int writeTo(ByteBuffer writeBuffer, int length) { + int toWrite = Math.min(writeBuffer.limit() - writeBuffer.position(), this.length - readPosition); + toWrite = Math.min(toWrite, length); + if (toWrite == 0) { + return 0; + } + writeBuffer.put(this.bytes, readPosition, toWrite); + readPosition += toWrite; + return toWrite; + } + + public void write(byte[] bytes, int offset, int length) { + System.arraycopy(bytes, offset, this.bytes, writePosition, length); + writePosition += length; + } + + @Override + public void write(BufferData toWrite) { + byte[] buffer = new byte[length - writePosition]; + int read = toWrite.read(buffer); + System.arraycopy(buffer, 0, this.bytes, writePosition, read); + writePosition += read; + } + + @Override + public void write(BufferData toWrite, int length) { + byte[] buffer = new byte[length]; + int read = toWrite.read(buffer); + System.arraycopy(buffer, 0, this.bytes, writePosition, read); + writePosition += read; + } + + @Override + public String debugDataBinary() { + return BufferUtil.debugDataBinary(bytes, 0, writePosition); + } + + @Override + public String debugDataHex(boolean fullBuffer) { + if (fullBuffer) { + return BufferUtil.debugDataHex(bytes, 0, writePosition); + } else { + return BufferUtil.debugDataHex(bytes, readPosition, writePosition); + } + } + + @Override + public int available() { + return writePosition - readPosition; + } + + @Override + public void skip(int length) { + readPosition += length; + } + + @Override + public int indexOf(byte aByte) { + for (int i = readPosition; i < (readPosition + available()); i++) { + if (aByte == bytes[i]) { + return i - readPosition; + } + } + return -1; + } + + @Override + public int lastIndexOf(byte aByte, int length) { + for (int i = (readPosition + length) - 1; i >= readPosition; i--) { + byte b = bytes[i]; + if (b == aByte) { + return i - readPosition; + } + } + return -1; + } + + @Override + public BufferData trim(int x) { + if (available() < x) { + throw new IllegalArgumentException("Trimming more bytes than available"); + } + writePosition -= x; + return this; + } + + @Override + public int capacity() { + return length - writePosition; + } + + @Override + public int get(int index) { + return bytes[readPosition + index]; + } + + @Override + public String toString() { + return "fixed: l=" + length + ", r=" + readPosition + ", w=" + writePosition; + } +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/GrowingBufferData.java b/common/buffers/src/main/java/io/helidon/common/buffers/GrowingBufferData.java new file mode 100644 index 00000000000..b5b42b4bb39 --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/GrowingBufferData.java @@ -0,0 +1,244 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; +import java.util.Arrays; + +class GrowingBufferData implements BufferData { + private byte[] bytes; + private int length; + private int writePosition; + private int readPosition; + + GrowingBufferData(int initialLength) { + int usedInitial = Math.max(initialLength, 256); + this.bytes = new byte[usedInitial]; + this.length = 0; + } + + public boolean ready() { + return writePosition > readPosition; + } + + @Override + public GrowingBufferData reset() { + this.writePosition = 0; + this.readPosition = 0; + return this; + } + + @Override + public BufferData rewind() { + this.readPosition = 0; + return this; + } + + @Override + public BufferData clear() { + reset(); + length = 0; + return this; + } + + @Override + public void writeTo(OutputStream out) { + try { + out.write(bytes, readPosition, writePosition - readPosition); + readPosition = writePosition; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public int readFrom(InputStream in) { + try { + int read = in.read(bytes, writePosition, bytes.length - writePosition); + writePosition += read; + return read; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public int read() { + if (readPosition >= writePosition) { + throw new ArrayIndexOutOfBoundsException("This buffer has " + length + + " bytes, requested to read at " + readPosition); + } + return bytes[readPosition++] & 0xFF; + } + + public int read(byte[] bytes, int position, int length) { + int available = writePosition - readPosition; + int toRead = Math.min(length, available); + + System.arraycopy(this.bytes, readPosition, bytes, position, toRead); + + readPosition += toRead; + return toRead; + } + + @Override + public String readString(int length, Charset charset) { + String result = new String(bytes, readPosition, length, charset); + readPosition += length; + return result; + } + + public boolean consumed() { + return readPosition == writePosition; + } + + public GrowingBufferData write(int value) { + ensureSize(1); + this.bytes[writePosition++] = (byte) value; + this.length = Math.max(length, writePosition); + return this; + } + + public int writeTo(ByteBuffer writeBuffer, int limit) { + int toWrite = Math.min(writeBuffer.capacity(), length - readPosition); + toWrite = Math.min(toWrite, limit); + if (toWrite == 0) { + return 0; + } + writeBuffer.put(this.bytes, readPosition, toWrite); + readPosition += toWrite; + return toWrite; + } + + @Override + public void write(byte[] bytes, int offset, int length) { + ensureSize(length); + System.arraycopy(bytes, offset, this.bytes, writePosition, length); + writePosition += length; + this.length = Math.max(this.length, writePosition); + } + + @Override + public void write(BufferData toWrite) { + ensureSize(toWrite.available()); + byte[] buffer = new byte[toWrite.available()]; + int read = toWrite.read(buffer); + System.arraycopy(buffer, 0, this.bytes, writePosition, read); + writePosition += read; + } + + @Override + public void write(BufferData toWrite, int length) { + ensureSize(length); + byte[] buffer = new byte[length]; + int read = toWrite.read(buffer); + System.arraycopy(buffer, 0, this.bytes, writePosition, read); + writePosition += read; + } + + @Override + public String debugDataBinary() { + return BufferUtil.debugDataBinary(bytes, 0, writePosition); + } + + @Override + public String debugDataHex(boolean fullBuffer) { + return BufferUtil.debugDataHex(bytes, fullBuffer ? 0 : readPosition, writePosition); + } + + @Override + public int available() { + return writePosition - readPosition; + } + + @Override + public void skip(int length) { + readPosition += length; + } + + @Override + public int indexOf(byte aByte) { + for (int i = readPosition; i < (readPosition + available()); i++) { + if (bytes[i] == aByte) { + return i - readPosition; + } + } + return -1; + } + + @Override + public int lastIndexOf(byte aByte, int length) { + for (int i = length - 1; i >= readPosition; i--) { + byte b = bytes[i]; + if (b == aByte) { + return i - readPosition; + } + } + return -1; + } + + @Override + public BufferData trim(int x) { + if (available() < x) { + throw new IllegalArgumentException("Trimming more bytes than available"); + } + writePosition -= x; + length -= x; + return this; + } + + @Override + public int capacity() { + return length - writePosition; + } + + @Override + public int get(int index) { + return bytes[readPosition + index]; + } + + @Override + public String toString() { + return "grow: l=" + length + ", r=" + readPosition + ", w=" + writePosition + ", c=" + bytes.length; + } + + byte[] bytes() { + return Arrays.copyOfRange(bytes, 0, length); + } + + private void ensureSize(int i) { + if (this.bytes.length > writePosition + i) { + return; + } + + byte[] current = this.bytes; + int currentLength = current.length; + int newLength = currentLength * 2; + newLength = Math.max(newLength, writePosition + i); + if (newLength < currentLength) { + // int overflow + throw new IllegalStateException("Growing buffer too big, cannot increase size"); + } + this.bytes = new byte[newLength]; + System.arraycopy(current, 0, this.bytes, 0, length); + } +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/LazyString.java b/common/buffers/src/main/java/io/helidon/common/buffers/LazyString.java new file mode 100644 index 00000000000..418064814cd --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/LazyString.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +import java.nio.charset.Charset; + +/** + * String that materializes only when requested. + */ +public class LazyString { + private final byte[] buffer; + private final int offset; + private final int length; + private final Charset charset; + + private String stringValue; + + /** + * New instance. + * + * @param buffer buffer to use + * @param offset offset within the buffer + * @param length length + * @param charset character set to construct the string + */ + public LazyString(byte[] buffer, int offset, int length, Charset charset) { + this.buffer = buffer; + this.offset = offset; + this.length = length; + this.charset = charset; + } + + /** + * New instance. + * + * @param buffer buffer to use (all bytes) + * @param charset character set to construct the string + */ + public LazyString(byte[] buffer, Charset charset) { + this.buffer = buffer; + this.offset = 0; + this.length = buffer.length; + this.charset = charset; + } + + @Override + public String toString() { + if (stringValue == null) { + stringValue = new String(buffer, offset, length, charset); + } + return stringValue; + } +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/ReadOnlyArrayData.java b/common/buffers/src/main/java/io/helidon/common/buffers/ReadOnlyArrayData.java new file mode 100644 index 00000000000..82f0284a4d6 --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/ReadOnlyArrayData.java @@ -0,0 +1,161 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.nio.ByteBuffer; +import java.nio.charset.Charset; + +class ReadOnlyArrayData extends ReadOnlyBufferData { + private final byte[] bytes; + private final int offset; + private final int length; + private int position; + + ReadOnlyArrayData(byte[] bytes, int offset, int length) { + this.bytes = bytes; + this.offset = offset; + this.length = length; + this.position = 0; + } + + @Override + public BufferData rewind() { + position = 0; + return this; + } + + @Override + public void writeTo(OutputStream out) { + try { + out.write(bytes, offset + position, length); + position = length; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + } + + @Override + public int readFrom(InputStream in) { + throw new UnsupportedOperationException(); + } + + @Override + public int read() { + if (position >= length) { + throw new ArrayIndexOutOfBoundsException("This buffer has " + length + " bytes, requested to read at " + position); + } + return bytes[offset + position++] & 0xFF; + + } + + @Override + public int read(byte[] bytes, int position, int length) { + int available = this.length - this.position; + int toRead = Math.min(length, available); + System.arraycopy(this.bytes, this.offset + this.position, bytes, position, toRead); + this.position += toRead; + return toRead; + + } + + @Override + public String readString(int length, Charset charset) { + String result = new String(bytes, offset + position, length, charset); + position += length; + return result; + } + + @Override + public boolean consumed() { + return position == length; + } + + @Override + public int writeTo(ByteBuffer writeBuffer, int length) { + int toWrite = Math.min(writeBuffer.limit() - writeBuffer.position(), this.length - this.position); + toWrite = Math.min(toWrite, length); + if (toWrite == 0) { + return 0; + } + writeBuffer.put(this.bytes, offset + position, toWrite); + position += toWrite; + return toWrite; + + } + + @Override + public String debugDataBinary() { + return BufferUtil.debugDataBinary(bytes, offset + position, length - position); + } + + @Override + public String debugDataHex(boolean fullBuffer) { + if (fullBuffer) { + return BufferUtil.debugDataHex(bytes, offset, offset + length); + } else { + return BufferUtil.debugDataHex(bytes, offset + position, offset + length - position); + } + + } + + @Override + public int available() { + return length - position; + } + + @Override + public void skip(int length) { + position = Math.min(this.length, position + length); + } + + @Override + public int indexOf(byte aByte) { + for (int i = position; i < length; i++) { + if (aByte == bytes[offset + i]) { + return i - position; + } + } + return -1; + + } + + @Override + public int lastIndexOf(byte aByte, int length) { + for (int i = length - 1; i >= position; i--) { + byte b = bytes[offset + i]; + if (b == aByte) { + return i - position; + } + } + return -1; + } + + @Override + public BufferData trim(int x) { + throw new UnsupportedOperationException(); + } + + @Override + public int get(int index) { + return bytes[offset + position + index] & 0xFF; + } +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/ReadOnlyBufferData.java b/common/buffers/src/main/java/io/helidon/common/buffers/ReadOnlyBufferData.java new file mode 100644 index 00000000000..511b8e2d909 --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/ReadOnlyBufferData.java @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +abstract class ReadOnlyBufferData implements BufferData { + @Override + public BufferData reset() { + throw new UnsupportedOperationException("Reset not supported for " + getClass().getSimpleName() + " buffer"); + } + + @Override + public BufferData clear() { + throw new UnsupportedOperationException("Clear not supported for " + getClass().getSimpleName() + " buffer"); + } + + @Override + public BufferData write(int value) { + throw new UnsupportedOperationException("Write not supported for " + getClass().getSimpleName() + " buffer"); + } + + @Override + public void write(byte[] bytes, int offset, int length) { + throw new UnsupportedOperationException("Write not supported for " + getClass().getSimpleName() + " buffer"); + } + + @Override + public void write(BufferData toWrite) { + throw new UnsupportedOperationException("Write not supported for " + getClass().getSimpleName() + " buffer"); + } + + @Override + public void write(BufferData toWrite, int length) { + throw new UnsupportedOperationException("Write not supported for " + getClass().getSimpleName() + " buffer"); + } + + @Override + public int capacity() { + throw new UnsupportedOperationException("Write not supported for " + getClass().getSimpleName() + " buffer"); + } +} diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/package-info.java b/common/buffers/src/main/java/io/helidon/common/buffers/package-info.java new file mode 100644 index 00000000000..cce8cb12c08 --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Byte buffers and byte operations. + */ +package io.helidon.common.buffers; diff --git a/common/buffers/src/main/java/module-info.java b/common/buffers/src/main/java/module-info.java new file mode 100644 index 00000000000..b971a6ad0fd --- /dev/null +++ b/common/buffers/src/main/java/module-info.java @@ -0,0 +1,24 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Byte buffers and byte operations. + */ +module io.helidon.common.buffers { + requires io.helidon.common; + + exports io.helidon.common.buffers; +} \ No newline at end of file diff --git a/common/buffers/src/test/java/io/helidon/common/buffers/AsciiTest.java b/common/buffers/src/test/java/io/helidon/common/buffers/AsciiTest.java new file mode 100644 index 00000000000..4ae366d9fa2 --- /dev/null +++ b/common/buffers/src/test/java/io/helidon/common/buffers/AsciiTest.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.common.buffers; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +class AsciiTest { + @Test + void testIsLowerCaseOne() { + assertFalse(Ascii.isLowerCase('{')); + } + + @Test + void testIsLowerCaseReturningTrue() { + assertTrue(Ascii.isLowerCase('o')); + } + + @Test + void testIsLowerCaseTwo() { + assertFalse(Ascii.isLowerCase('\"')); + } + + @Test + void testToLowerCaseChar() { + assertThat(Ascii.toLowerCase('A'), is('a')); + assertThat(Ascii.toLowerCase('Z'), is('z')); + assertThat(Ascii.toLowerCase('a'), is('a')); + assertThat(Ascii.toLowerCase('z'), is('z')); + assertThat(Ascii.toLowerCase('5'), is('5')); + } + + @Test + void testToLowerCaseTakingCharSequenceOne() { + StringBuilder stringBuilder = new StringBuilder("uhho^s} b'jdwtym"); + + assertEquals("uhho^s} b'jdwtym", Ascii.toLowerCase(stringBuilder)); + } + + @Test + void testToLowerCaseTakingCharSequenceTwo() { + assertEquals("uhho^s} b'jdwtym", Ascii.toLowerCase((CharSequence) "uHHO^S} b'jDwTYM")); + } + + @Test + void testToLowerCaseTakingString() { + assertEquals("", Ascii.toLowerCase("")); + } + + @Test + void testIsUpperCaseOne() { + assertFalse(Ascii.isUpperCase('{')); + } + + @Test + void testIsUpperCaseReturningTrue() { + assertTrue(Ascii.isUpperCase('O')); + } + + @Test + void testIsUpperCaseTwo() { + assertFalse(Ascii.isUpperCase('\"')); + } + + @Test + void testToUpperCaseChar() { + assertThat(Ascii.toUpperCase('a'), is('A')); + assertThat(Ascii.toUpperCase('z'), is('Z')); + assertThat(Ascii.toUpperCase('A'), is('A')); + assertThat(Ascii.toUpperCase('Z'), is('Z')); + assertThat(Ascii.toUpperCase('5'), is('5')); + } + + @Test + void testToUpperCaseTakingCharSequenceOne() { + StringBuilder stringBuilder = new StringBuilder("UhHO^S} B'JDWTYM"); + + assertEquals("UHHO^S} B'JDWTYM", Ascii.toUpperCase(stringBuilder)); + } + + @Test + void testToUpperCaseTakingCharSequenceTwo() { + assertEquals("UHHO^S} B'JDWTYM", Ascii.toUpperCase((CharSequence) "uHHO^S} b'jDwTYM")); + } + + @Test + void testToUpperCaseTakingString() { + assertEquals("UHHO^S} B'JDWTYM", Ascii.toUpperCase("uHHO^S} b'jDwTYM")); + } +} \ No newline at end of file diff --git a/common/buffers/src/test/java/io/helidon/common/buffers/BufferDataTest.java b/common/buffers/src/test/java/io/helidon/common/buffers/BufferDataTest.java new file mode 100644 index 00000000000..a4eb3c0e97b --- /dev/null +++ b/common/buffers/src/test/java/io/helidon/common/buffers/BufferDataTest.java @@ -0,0 +1,299 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +import java.util.HexFormat; +import java.util.stream.Stream; + +import org.hamcrest.Matchers; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class BufferDataTest { + static Stream initParams() { + return Stream.of(new TestContext("fixed", BufferData.create(1024)), + new TestContext("growing", BufferData.growing(0)), + new TestContext("byte[]", BufferData.create(new byte[1024]).clear())); + } + + @ParameterizedTest + @MethodSource("initParams") + void indexOf(TestContext context) { + BufferData b = context.bufferData(); + b.writeAscii("Hello: test: not: test: end"); + + int index = b.indexOf(Bytes.COLON_BYTE); + + assertThat(index, is(5)); + + String nextString = b.readString(index); + assertThat(nextString, is("Hello")); + b.skip(2); // skip the delimiter and space + index = b.indexOf(Bytes.COLON_BYTE); + + assertThat(index, is(4)); + nextString = b.readString(index); + assertThat(nextString, is("test")); + } + + @ParameterizedTest + @MethodSource("initParams") + void lastIndexOf(TestContext context) { + BufferData b = context.bufferData(); + b.writeAscii("Hello: test: not: test: end"); + int index = b.lastIndexOf((byte) ':'); + + assertThat(index, is(22)); + } + + @ParameterizedTest + @MethodSource("initParams") + void testWriteHpackInt(TestContext context) { + int value = 10; + + BufferData bufferData = context.bufferData() + .writeHpackInt(value, 0b10100000, 5); + + assertThat(bufferData.available(), is(1)); + assertThat(bufferData.read(), is((0b10100000 | value))); + + value = 1337; + byte[] expected = new byte[] {(byte) 0b10111111, + (byte) 0b10011010, + (byte) 0b00001010}; + + bufferData.clear(); + bufferData.writeHpackInt(value, 0b10100000, 5); + + assertThat(bufferData.available(), is(expected.length)); + byte[] actual = new byte[expected.length]; + int read = bufferData.read(actual); + assertThat(read, is(expected.length)); + + for (int i = 0; i < actual.length; i++) { + byte actualByte = actual[i]; + byte expectedByte = expected[i]; + + assertThat("Byte at position " + i, + Integer.toBinaryString(expectedByte & 0xFF), + is(Integer.toBinaryString(actualByte & 0xFF))); + } + } + + @ParameterizedTest + @MethodSource("initParams") + void testReadInt(TestContext context) { + // https://www.rfc-editor.org/rfc/rfc7541.html#appendix-C.1.1 + int expected = 10; + BufferData buffer = context.bufferData(); + assertThat(buffer.readHpackInt(10, 5), is(expected)); + + // https://www.rfc-editor.org/rfc/rfc7541.html#appendix-C.1.2 + expected = 1337; + buffer.clear(); + buffer.write((byte) 0b10011010).write((byte) 0b00001010); + assertThat(buffer.readHpackInt(31, 5), is(expected)); + + // https://www.rfc-editor.org/rfc/rfc7541.html#appendix-C.1.3 + expected = 42; + buffer.clear(); + assertThat(buffer.readHpackInt(42, 8), is(expected)); + } + + @ParameterizedTest + @MethodSource("initParams") + void testBinaryOutput(TestContext context) { + BufferData bd = context.bufferData(); + bd.write(0b01010101); + String expected = """ + +--------+----------+ + | index | 01234567 | + +--------+----------+ + |00000000| 01010101 | + +--------+----------+ + """; + assertThat(bd.debugDataBinary(), is(expected)); + + bd.clear(); + bd.write(0b00000000); + bd.write(0b11111111); + expected = """ + +--------+----------+ + | index | 01234567 | + +--------+----------+ + |00000000| 00000000 | + |00000001| 11111111 | + +--------+----------+ + """; + assertThat(bd.debugDataBinary(), is(expected)); + + bd.clear(); + bd.writeAscii("GET / HTTP/1.1\r\nhost: localhost:8080\r\n\r\n"); + + expected = """ + +--------+----------+ + | index | 01234567 | + +--------+----------+ + |00000000| 01000111 | + |00000001| 01000101 | + |00000002| 01010100 | + |00000003| 00100000 | + |00000004| 00101111 | + |00000005| 00100000 | + |00000006| 01001000 | + |00000007| 01010100 | + |00000008| 01010100 | + |00000009| 01010000 | + |0000000a| 00101111 | + |0000000b| 00110001 | + |0000000c| 00101110 | + |0000000d| 00110001 | + |0000000e| 00001101 | + |0000000f| 00001010 | + |00000010| 01101000 | + |00000011| 01101111 | + |00000012| 01110011 | + |00000013| 01110100 | + |00000014| 00111010 | + |00000015| 00100000 | + |00000016| 01101100 | + |00000017| 01101111 | + |00000018| 01100011 | + |00000019| 01100001 | + |0000001a| 01101100 | + |0000001b| 01101000 | + |0000001c| 01101111 | + |0000001d| 01110011 | + |0000001e| 01110100 | + |0000001f| 00111010 | + |00000020| 00111000 | + |00000021| 00110000 | + |00000022| 00111000 | + |00000023| 00110000 | + |00000024| 00001101 | + |00000025| 00001010 | + |00000026| 00001101 | + |00000027| 00001010 | + +--------+----------+ + """; + assertThat(bd.debugDataBinary(), is(expected)); + } + + @ParameterizedTest + @MethodSource("initParams") + void testHexOutput16bytes(TestContext context) { + BufferData bd = context.bufferData(); + bd.write(dataFromHex("82 86 84 41 8a 08 9d 5c 0b 81 70 dc 78 0f 03 ba")); + String hex = bd.debugDataHex(true); + String expected = """ + +--------+-------------------------------------------------+----------------+ + | index| 0 1 2 3 4 5 6 7 8 9 a b c d e f | data| + +--------+-------------------------------------------------+----------------+ + |00000000| 82 86 84 41 8a 08 9d 5c 0b 81 70 dc 78 0f 03 ba |...A...\\..pÜx..º| + +--------+-------------------------------------------------+----------------+ + """; + assertThat(hex, is(expected)); + bd.skip(8); + hex = bd.debugDataHex(true); + assertThat(hex, is(expected)); + + hex = bd.debugDataHex(false); + expected = """ + +--------+-------------------------------------------------+----------------+ + | index| 0 1 2 3 4 5 6 7 8 9 a b c d e f | data| + +--------+-------------------------------------------------+----------------+ + |00000000| 0b 81 70 dc 78 0f 03 ba |..pÜx..º | + +--------+-------------------------------------------------+----------------+ + """; + assertThat(hex, is(expected)); + } + + @ParameterizedTest + @MethodSource("initParams") + void testReadInt4bytes(TestContext context) { + long maxValue = 0xFFFFFFFFL; + BufferData bd = context.bufferData(); + bd.write(dataFromHex("FF FF FF FF")); + long number = bd.readUnsignedInt32(); + assertThat(number, Matchers.greaterThan(0L)); + assertThat(number, is(maxValue)); + } + + @ParameterizedTest + @MethodSource("initParams") + void testHexOutput(TestContext context) { + BufferData bd = context.bufferData(); + bd.writeAscii("GET / HTTP/1.1\r\nhost: localhost:8080\r\n\r\n"); + String expected = """ + +--------+-------------------------------------------------+----------------+ + | index| 0 1 2 3 4 5 6 7 8 9 a b c d e f | data| + +--------+-------------------------------------------------+----------------+ + |00000000| 47 45 54 20 2f 20 48 54 54 50 2f 31 2e 31 0d 0a |GET / HTTP/1.1..| + |00000010| 68 6f 73 74 3a 20 6c 6f 63 61 6c 68 6f 73 74 3a |host: localhost:| + |00000020| 38 30 38 30 0d 0a 0d 0a |8080.... | + +--------+-------------------------------------------------+----------------+ + """; + assertThat(bd.debugDataHex(true), is(expected)); + + bd.clear(); + bd.write(0b01010101); + expected = """ + +--------+-------------------------------------------------+----------------+ + | index| 0 1 2 3 4 5 6 7 8 9 a b c d e f | data| + +--------+-------------------------------------------------+----------------+ + |00000000| 55 |U | + +--------+-------------------------------------------------+----------------+ + """; + assertThat(bd.debugDataHex(true), is(expected)); + + bd.clear(); + bd.write(0b00000000); + bd.write(0b11111111); + expected = """ + +--------+-------------------------------------------------+----------------+ + | index| 0 1 2 3 4 5 6 7 8 9 a b c d e f | data| + +--------+-------------------------------------------------+----------------+ + |00000000| 00 ff |.ÿ | + +--------+-------------------------------------------------+----------------+ + """; + assertThat(bd.debugDataHex(true), is(expected)); + + bd.clear(); + expected = """ + +--------+-------------------------------------------------+----------------+ + | index| 0 1 2 3 4 5 6 7 8 9 a b c d e f | data| + +--------+-------------------------------------------------+----------------+ + +--------+-------------------------------------------------+----------------+ + """; + assertThat(bd.debugDataHex(true), is(expected)); + } + + BufferData dataFromHex(String hexEncoded) { + byte[] bytes = HexFormat.of().parseHex(hexEncoded.replace(" ", "")); + return BufferData.create(bytes); + } + + private record TestContext(String name, BufferData bufferData) { + @Override + public String toString() { + return name; + } + } +} \ No newline at end of file diff --git a/common/buffers/src/test/java/io/helidon/common/buffers/CompositeBufferDataTest.java b/common/buffers/src/test/java/io/helidon/common/buffers/CompositeBufferDataTest.java new file mode 100644 index 00000000000..4b110c20701 --- /dev/null +++ b/common/buffers/src/test/java/io/helidon/common/buffers/CompositeBufferDataTest.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +import java.util.List; +import java.util.stream.Stream; + +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.MethodSource; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class CompositeBufferDataTest { + static Stream initParams() { + BufferData array = BufferData.create(BufferData.create("0123456"), BufferData.create("7890")); + BufferData list = BufferData.create(List.of(BufferData.create("0123456"), BufferData.create("7890"))); + + return Stream.of(new TestContext("array", array), + new TestContext("list", list)); + } + + @ParameterizedTest + @MethodSource("initParams") + void testGet(TestContext context) { + char[] expected = "01234567890".toCharArray(); + + for (int i = 0; i < 11; i++) { + char found = (char) context.bufferData().get(i); + assertThat(found, is(expected[i])); + } + } + + @ParameterizedTest + @MethodSource("initParams") + void testReadString(TestContext context) { + BufferData combined = context.bufferData(); + combined.read(); + String result = combined.readString(combined.indexOf((byte) '0')); + assertThat(result, is("123456789")); + } + + private record TestContext(String name, BufferData bufferData) { + @Override + public String toString() { + return name; + } + } +} \ No newline at end of file diff --git a/common/buffers/src/test/java/io/helidon/common/buffers/ReadOnlyArrayDataTest.java b/common/buffers/src/test/java/io/helidon/common/buffers/ReadOnlyArrayDataTest.java new file mode 100644 index 00000000000..477712de57b --- /dev/null +++ b/common/buffers/src/test/java/io/helidon/common/buffers/ReadOnlyArrayDataTest.java @@ -0,0 +1,40 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.MatcherAssert.assertThat; + +class ReadOnlyArrayDataTest { + @Test + void testDebugData() { + byte[] test = "Hello World!".getBytes(StandardCharsets.UTF_8); + ReadOnlyArrayData rad = new ReadOnlyArrayData(test, 1, test.length - 1); + String debugged = rad.debugDataHex(); + List lines = debugged.lines().toList(); + assertThat(lines, hasItems("+--------+-------------------------------------------------+----------------+", + "| index| 0 1 2 3 4 5 6 7 8 9 a b c d e f | data|", + "+--------+-------------------------------------------------+----------------+", + "|00000000| 65 6c 6c 6f 20 57 6f 72 6c 64 21 |ello World! |", + "+--------+-------------------------------------------------+----------------+")); + } +} \ No newline at end of file diff --git a/common/pom.xml b/common/pom.xml index 3836b474235..2f4bccaf5e7 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -41,6 +41,7 @@ media-type crypto parameters + buffers testing From 01daac83bfa45ea0b9bc92d1eea261d7f2cbe73f Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 21:50:15 +0200 Subject: [PATCH 05/54] New socket abstraction to handle peer information and read/writes. --- .../io/helidon/common/buffers/DataWriter.java | 52 ++++ common/pom.xml | 1 + common/socket/pom.xml | 47 ++++ .../helidon/common/socket/HelidonSocket.java | 65 +++++ .../io/helidon/common/socket/PeerInfo.java | 62 +++++ .../helidon/common/socket/PeerInfoImpl.java | 106 +++++++++ .../io/helidon/common/socket/PlainSocket.java | 184 ++++++++++++++ .../helidon/common/socket/SocketContext.java | 104 ++++++++ .../helidon/common/socket/SocketOptions.java | 225 ++++++++++++++++++ .../helidon/common/socket/SocketWriter.java | 157 ++++++++++++ .../common/socket/SocketWriterException.java | 31 +++ .../io/helidon/common/socket/TlsSocket.java | 113 +++++++++ .../helidon/common/socket/package-info.java | 20 ++ common/socket/src/main/java/module-info.java | 25 ++ 14 files changed, 1192 insertions(+) create mode 100644 common/buffers/src/main/java/io/helidon/common/buffers/DataWriter.java create mode 100644 common/socket/pom.xml create mode 100644 common/socket/src/main/java/io/helidon/common/socket/HelidonSocket.java create mode 100644 common/socket/src/main/java/io/helidon/common/socket/PeerInfo.java create mode 100644 common/socket/src/main/java/io/helidon/common/socket/PeerInfoImpl.java create mode 100644 common/socket/src/main/java/io/helidon/common/socket/PlainSocket.java create mode 100644 common/socket/src/main/java/io/helidon/common/socket/SocketContext.java create mode 100644 common/socket/src/main/java/io/helidon/common/socket/SocketOptions.java create mode 100644 common/socket/src/main/java/io/helidon/common/socket/SocketWriter.java create mode 100644 common/socket/src/main/java/io/helidon/common/socket/SocketWriterException.java create mode 100644 common/socket/src/main/java/io/helidon/common/socket/TlsSocket.java create mode 100644 common/socket/src/main/java/io/helidon/common/socket/package-info.java create mode 100644 common/socket/src/main/java/module-info.java diff --git a/common/buffers/src/main/java/io/helidon/common/buffers/DataWriter.java b/common/buffers/src/main/java/io/helidon/common/buffers/DataWriter.java new file mode 100644 index 00000000000..7626b69a75d --- /dev/null +++ b/common/buffers/src/main/java/io/helidon/common/buffers/DataWriter.java @@ -0,0 +1,52 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.buffers; + +/** + * Write data to the underlying transport (most likely a socket). + * Do not combine {@link #write(io.helidon.common.buffers.BufferData)} and {@link #writeNow(io.helidon.common.buffers.BufferData)} + * to a single underlying transport, unless you can guarantee there will not be a race between these two methods. + */ +public interface DataWriter { + /** + * Write buffers, may delay writing and may write on a different thread. + * This method also may combine multiple calls into a single write to the underlying transport. + * @param buffers buffers to write + */ + void write(BufferData... buffers); + + /** + * Write buffer, may delay writing and may write on a different thread. + * This method also may combine multiple calls into a single write to the underlying transport. + * @param buffer buffer to write + */ + void write(BufferData buffer); + + /** + * Write buffers to underlying transport blocking until the buffers are written. + * + * @param buffers buffers to write + */ + void writeNow(BufferData... buffers); + + /** + * Write buffer to underlying transport blocking until the buffer is written. + * + * @param buffer buffer to write + */ + void writeNow(BufferData buffer); +} diff --git a/common/pom.xml b/common/pom.xml index 2f4bccaf5e7..bea3a8c9179 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -42,6 +42,7 @@ crypto parameters buffers + socket testing diff --git a/common/socket/pom.xml b/common/socket/pom.xml new file mode 100644 index 00000000000..c4bac63e437 --- /dev/null +++ b/common/socket/pom.xml @@ -0,0 +1,47 @@ + + + + + 4.0.0 + + io.helidon.common + helidon-common-project + 4.0.0-SNAPSHOT + + helidon-common-socket + Helidon Common Socket + + + + io.helidon.common + helidon-common-buffers + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + diff --git a/common/socket/src/main/java/io/helidon/common/socket/HelidonSocket.java b/common/socket/src/main/java/io/helidon/common/socket/HelidonSocket.java new file mode 100644 index 00000000000..f823a18626a --- /dev/null +++ b/common/socket/src/main/java/io/helidon/common/socket/HelidonSocket.java @@ -0,0 +1,65 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.socket; + +import java.util.NoSuchElementException; +import java.util.function.Supplier; + +import io.helidon.common.buffers.BufferData; + +/** + * Socket abstraction to allow usage of TLS or even non-socket transport. + */ +public interface HelidonSocket extends SocketContext, Supplier { + /** + * Close the underlying socket. + */ + void close(); + + /** + * Read bytes from the socket. This method blocks until at least 1 byte is available. + * + * @param buffer buffer to read to + * @return number of bytes read + */ + int read(BufferData buffer); + + /** + * Write teh buffer to the underlying socket. This method blocks until all bytes are written. + * + * @param buffer buffer to write + */ + void write(BufferData buffer); + + /** + * Whether a protocol was negotiated by the socket (such as ALPN when using TLS). + * @return whether a protocol was negotiated + */ + default boolean protocolNegotiated() { + return false; + } + + /** + * Protocol that was negotiated. + * + * @return protocol name + * @throws java.util.NoSuchElementException in case there is no negotiated protocol + */ + default String protocol() { + throw new NoSuchElementException("No protocol negotiated"); + } +} diff --git a/common/socket/src/main/java/io/helidon/common/socket/PeerInfo.java b/common/socket/src/main/java/io/helidon/common/socket/PeerInfo.java new file mode 100644 index 00000000000..a47fd12a148 --- /dev/null +++ b/common/socket/src/main/java/io/helidon/common/socket/PeerInfo.java @@ -0,0 +1,62 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.socket; + +import java.net.SocketAddress; +import java.security.Principal; +import java.security.cert.Certificate; +import java.util.Optional; + +/** + * Information about one side of this communication (either local or remote). + */ +public interface PeerInfo { + /** + * Socket address of the peer. + * + * @return address + */ + SocketAddress address(); + + /** + * Host of the peer. + * + * @return host + */ + String host(); + + /** + * Port of the peer. + * + * @return port + */ + int port(); + + /** + * TLS principal (from certificate) of the peer. + * + * @return principal of the peer, or empty if not a TLS connection, or this peer does not provide principal + */ + Optional tlsPrincipal(); + + /** + * TLS certificate chain of the peer. + * + * @return certificate chain of the peer, or empty if not a TLS connection, or this peer does not provide certificates + */ + Optional tlsCertificates(); +} diff --git a/common/socket/src/main/java/io/helidon/common/socket/PeerInfoImpl.java b/common/socket/src/main/java/io/helidon/common/socket/PeerInfoImpl.java new file mode 100644 index 00000000000..238f45d0604 --- /dev/null +++ b/common/socket/src/main/java/io/helidon/common/socket/PeerInfoImpl.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.socket; + +import java.net.SocketAddress; +import java.security.Principal; +import java.security.cert.Certificate; +import java.util.Optional; +import java.util.function.Supplier; + +import io.helidon.common.LazyValue; + +class PeerInfoImpl implements PeerInfo { + private final LazyValue socketAddress; + private final LazyValue host; + private final LazyValue port; + private final Supplier> principalSupplier; + private final Supplier> certificateSupplier; + + private PeerInfoImpl(LazyValue socketAddress, + LazyValue host, + LazyValue port, + Supplier> principalSupplier, + Supplier> certificateSupplier) { + this.socketAddress = socketAddress; + this.host = host; + this.port = port; + this.principalSupplier = principalSupplier; + this.certificateSupplier = certificateSupplier; + } + + static PeerInfo createLocal(PlainSocket socket) { + return new PeerInfoImpl(LazyValue.create(socket::localSocketAddress), + LazyValue.create(socket::localHost), + LazyValue.create(socket::localPort), + Optional::empty, + Optional::empty); + } + + static PeerInfoImpl createLocal(TlsSocket socket) { + // remote socket - all lazy values, as they cannot change (and they require creating another object) + // tls related - there can be re-negotiation of tls, to be safe I use a supplier + return new PeerInfoImpl(LazyValue.create(socket::localSocketAddress), + LazyValue.create(socket::localHost), + LazyValue.create(socket::localPort), + socket::tlsPrincipal, + socket::tlsCertificates); + } + + static PeerInfoImpl createRemote(TlsSocket socket) { + // remote socket - all lazy values, as they cannot change (and they require creating another object) + // tls related - there can be re-negotiation of tls, to be safe I use a supplier + return new PeerInfoImpl(LazyValue.create(socket::remoteSocketAddress), + LazyValue.create(socket::remoteHost), + LazyValue.create(socket::remotePort), + socket::tlsPeerPrincipal, + socket::tlsPeerCertificates); + } + + static PeerInfoImpl createRemote(PlainSocket socket) { + return new PeerInfoImpl(LazyValue.create(socket::remoteSocketAddress), + LazyValue.create(socket::remoteHost), + LazyValue.create(socket::remotePort), + Optional::empty, + Optional::empty); + } + + @Override + public SocketAddress address() { + return socketAddress.get(); + } + + @Override + public String host() { + return host.get(); + } + + @Override + public int port() { + return port.get(); + } + + @Override + public Optional tlsPrincipal() { + return principalSupplier.get(); + } + + @Override + public Optional tlsCertificates() { + return certificateSupplier.get(); + } +} diff --git a/common/socket/src/main/java/io/helidon/common/socket/PlainSocket.java b/common/socket/src/main/java/io/helidon/common/socket/PlainSocket.java new file mode 100644 index 00000000000..b3ec4eac0a3 --- /dev/null +++ b/common/socket/src/main/java/io/helidon/common/socket/PlainSocket.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.socket; + +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.net.SocketAddress; +import java.util.Arrays; + +import io.helidon.common.buffers.BufferData; + +/** + * Helidon socket that is based on plaintext. + */ +public sealed class PlainSocket implements HelidonSocket permits TlsSocket { + private static final int BUFFER_LENGTH = 8 * 1024; + + private final byte[] readBuffer = new byte[BUFFER_LENGTH]; + + private final Socket delegate; + private final String childSocketId; + private final String socketId; + private final InputStream inputStream; + private final OutputStream outputStream; + + /** + * Plain socket. + * + * @param delegate delegate socket + * @param childSocketId channel id + * @param socketId server channel id + */ + protected PlainSocket(Socket delegate, String childSocketId, String socketId) { + this.delegate = delegate; + this.childSocketId = childSocketId; + this.socketId = socketId; + try { + this.inputStream = delegate.getInputStream(); + this.outputStream = delegate.getOutputStream(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Create a new server socket. + * + * @param delegate underlying socket + * @param channelId channel id + * @param serverChannelId server channel id + * @return a new plain socket + */ + public static PlainSocket server(Socket delegate, String channelId, String serverChannelId) { + return new PlainSocket(delegate, channelId, serverChannelId); + } + + /** + * Create a new client socket. + * + * @param delegate underlying socket + * @param channelId channel id + * @return a new plain socket + */ + public static PlainSocket client(Socket delegate, String channelId) { + return new PlainSocket(delegate, channelId, "client"); + } + + @Override + public PeerInfo remotePeer() { + return PeerInfoImpl.createRemote(this); + } + + @Override + public PeerInfo localPeer() { + return PeerInfoImpl.createLocal(this); + } + + @Override + public boolean isSecure() { + return false; + } + + @Override + public String socketId() { + return socketId; + } + + @Override + public String childSocketId() { + return childSocketId; + } + + @Override + public void close() { + try { + delegate.close(); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + @Override + public int read(BufferData buffer) { + return buffer.readFrom(inputStream); + } + + @Override + public void write(BufferData buffer) { + buffer.writeTo(outputStream); + } + + @Override + public byte[] get() { + try { + int r = inputStream.read(readBuffer); + if (r == -1) { + return null; // end of data + } else if (r == 0) { + throw new IllegalStateException("Read 0 bytes, this should never happen with blocking socket"); + } + return Arrays.copyOf(readBuffer, r); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + SocketAddress localSocketAddress() { + return delegate.getLocalSocketAddress(); + } + + SocketAddress remoteSocketAddress() { + return delegate.getRemoteSocketAddress(); + } + + String localHost() { + SocketAddress localSocketAddress = localSocketAddress(); + if (localSocketAddress instanceof InetSocketAddress inet) { + return inet.getHostString(); + } + throw new IllegalStateException("Local host is not an instance of InetSocketAddress"); + } + + String remoteHost() { + SocketAddress remoteSocketAddress = delegate.getRemoteSocketAddress(); + if (remoteSocketAddress instanceof InetSocketAddress inet) { + return inet.getHostString(); + } + throw new IllegalStateException("Remote host is not an instance of InetSocketAddress"); + } + + int localPort() { + SocketAddress localSocketAddress = localSocketAddress(); + if (localSocketAddress instanceof InetSocketAddress inet) { + return inet.getPort(); + } + throw new IllegalStateException("Local host is not an instance of InetSocketAddress"); + } + + int remotePort() { + SocketAddress remoteSocketAddress = delegate.getRemoteSocketAddress(); + if (remoteSocketAddress instanceof InetSocketAddress inet) { + return inet.getPort(); + } + throw new IllegalStateException("Remote host is not an instance of InetSocketAddress"); + } +} diff --git a/common/socket/src/main/java/io/helidon/common/socket/SocketContext.java b/common/socket/src/main/java/io/helidon/common/socket/SocketContext.java new file mode 100644 index 00000000000..7066258b9da --- /dev/null +++ b/common/socket/src/main/java/io/helidon/common/socket/SocketContext.java @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.socket; + +/** + * Information available for a connected socket. + */ +public interface SocketContext { + /** + * Remote peer information. + * + * @return peer info + */ + PeerInfo remotePeer(); + + /** + * Local peer information. + * + * @return peer info + */ + PeerInfo localPeer(); + + /** + * Whether the request is secure. + * + * @return whether secure + */ + boolean isSecure(); + + /** + * Main socket id. + * This is the server socket id for server side, connection id for client side. + * @return socket id + */ + String socketId(); + + /** + * Child socket id. + * This is the connection socket id for server side. For client side, this may be additional + * identification of a request (pipeline id, stream id). + * @return child socket id, never null + */ + String childSocketId(); + + /** + * Log a message with the current {@link #socketId()} and {@link #childSocketId()} to have + * consistent logs mappable to sockets. + * + * @param logger logger to use + * @param level log level to use + * @param format format (can use string format pattern for the variables provided) + * @param variables variables of the format + */ + default void log(System.Logger logger, + System.Logger.Level level, + String format, + Object... variables) { + if (logger.isLoggable(level)) { + logger.log(level, message(format, variables)); + } + } + + /** + * Log a message with the current {@link #socketId()} and {@link #childSocketId()} to have + * consistent logs mappable to sockets. + * + * @param logger logger to use + * @param level log level to use + * @param format format (can use string format pattern for the variables provided) + * @param t throwable to log in addition to the message + * @param variables variables of the format + */ + default void log(System.Logger logger, + System.Logger.Level level, + String format, + Throwable t, + Object... variables) { + if (logger.isLoggable(level)) { + logger.log(level, message(format, variables), t); + } + } + + private String message(String format, Object... variables) { + Object[] newVars = new Object[2 + variables.length]; + newVars[0] = socketId(); + newVars[1] = childSocketId(); + System.arraycopy(variables, 0, newVars, 2, variables.length); + return String.format("[%s %s] " + format, newVars); + } +} diff --git a/common/socket/src/main/java/io/helidon/common/socket/SocketOptions.java b/common/socket/src/main/java/io/helidon/common/socket/SocketOptions.java new file mode 100644 index 00000000000..4431b30c8bb --- /dev/null +++ b/common/socket/src/main/java/io/helidon/common/socket/SocketOptions.java @@ -0,0 +1,225 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.socket; + +import java.io.IOException; +import java.io.UncheckedIOException; +import java.net.Socket; +import java.net.SocketOption; +import java.net.StandardSocketOptions; +import java.time.Duration; +import java.util.HashMap; +import java.util.Map; + +/** + * Socket options. + */ +public class SocketOptions { + private final Map socketOptions; + private final Duration connectTimeout; + private final Duration readTimeout; + + private SocketOptions(Map socketOptions, Duration connectTimeout, Duration readTimeout) { + this.socketOptions = new HashMap(socketOptions); + this.connectTimeout = connectTimeout; + this.readTimeout = readTimeout; + } + + /** + * A new fluent API builder. + * + * @return a new builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Configure socket with defined socket options. + * + * @param socket socket to update + */ + public void configureSocket(Socket socket) { + for (Map.Entry entry : socketOptions.entrySet()) { + try { + socket.setOption(entry.getKey(), entry.getValue()); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + } + + /** + * Socket connect timeout. + * + * @return connect timeout duration + */ + public Duration connectTimeout() { + return connectTimeout; + } + + /** + * Socket read timeout. + * + * @return read timeout duration + */ + public Duration readTimeout() { + return readTimeout; + } + + @Override + public String toString() { + return "SocketOptions{" + + "socketOptions=" + socketOptions + + ", connectTimeout=" + connectTimeout + + ", readTimeout=" + readTimeout + + '}'; + } + + /** + * Fluent API builder for {@link io.helidon.common.socket.SocketOptions}. + */ + public static class Builder implements io.helidon.common.Builder { + private static final int DEFAULT_SO_BUFFER_SIZE = 32768; + + private final Map socketOptions = new HashMap<>(); + private int socketReceiveBufferSize = DEFAULT_SO_BUFFER_SIZE; + private int socketSendBufferSize = DEFAULT_SO_BUFFER_SIZE; + private boolean socketReuseAddress = true; + private boolean socketKeepAlive = true; + private boolean tcpNoDelay = false; + private Duration connectTimeout = Duration.ofSeconds(10); + private Duration readTimeout = Duration.ofSeconds(30); + + private Builder() { + } + + @Override + public final SocketOptions build() { + socketOptions.put(StandardSocketOptions.SO_RCVBUF, socketReceiveBufferSize); + socketOptions.put(StandardSocketOptions.SO_SNDBUF, socketSendBufferSize); + socketOptions.put(StandardSocketOptions.SO_REUSEADDR, socketReuseAddress); + socketOptions.put(StandardSocketOptions.SO_KEEPALIVE, socketKeepAlive); + socketOptions.put(StandardSocketOptions.TCP_NODELAY, tcpNoDelay); + + return new SocketOptions(socketOptions, connectTimeout, readTimeout); + } + + /** + * Socket receive buffer size. + * Default is {@value #DEFAULT_SO_BUFFER_SIZE}. + * + * @param socketReceiveBufferSize buffer size, in bytes + * @return updated builder + * @see java.net.StandardSocketOptions#SO_RCVBUF + */ + public Builder socketReceiveBufferSize(int socketReceiveBufferSize) { + this.socketReceiveBufferSize = socketReceiveBufferSize; + return this; + } + + /** + * Socket send buffer size. + * Default is {@value #DEFAULT_SO_BUFFER_SIZE}. + * + * @param socketSendBufferSize buffer size, in bytes + * @return updated builder + * @see java.net.StandardSocketOptions#SO_SNDBUF + */ + public Builder socketSendBufferSize(int socketSendBufferSize) { + this.socketSendBufferSize = socketSendBufferSize; + return this; + } + + /** + * Socket reuse address. + * Default is {@code true}. + * + * @param socketReuseAddress whether to reuse address + * @return updated builder + * @see java.net.StandardSocketOptions#SO_REUSEADDR + */ + public Builder socketReuseAddress(boolean socketReuseAddress) { + this.socketReuseAddress = socketReuseAddress; + return this; + } + + /** + * Configure socket keep alive. + * Default is {@code true}. + * + * @param socketKeepAlive keep alive + * @return updated builder + * @see java.net.StandardSocketOptions#SO_KEEPALIVE + */ + public Builder socketKeepAlive(boolean socketKeepAlive) { + this.socketKeepAlive = socketKeepAlive; + return this; + } + + /** + * This option may improve performance on some systems. + * Default is {@code false}. + * + * @param tcpNoDelay whether to use TCP_NODELAY, defaults to {@code false} + * @return updated builder + * @see java.net.StandardSocketOptions#TCP_NODELAY + */ + public Builder tcpNoDelay(boolean tcpNoDelay) { + this.tcpNoDelay = tcpNoDelay; + return this; + } + + /** + * Set an arbitrary option. + * + * @param option option to set + * @param value option value + * @param option type + * @return updated builder + * @see java.net.StandardSocketOptions + */ + public Builder setOption(SocketOption option, O value) { + socketOptions.put(option, value); + return this; + } + + /** + * Read timeout. + * Default is 30 seconds. + * + * @param readTimeout read timeout + * @return updated builder + */ + public Builder readTimeout(Duration readTimeout) { + this.readTimeout = readTimeout; + return this; + } + + /** + * Connect timeout. + * Default is 10 seconds. + * + * @param connectTimeout connect timeout + * @return updated builder + */ + public Builder connectTimeout(Duration connectTimeout) { + this.connectTimeout = connectTimeout; + return this; + } + } +} diff --git a/common/socket/src/main/java/io/helidon/common/socket/SocketWriter.java b/common/socket/src/main/java/io/helidon/common/socket/SocketWriter.java new file mode 100644 index 00000000000..77e091a5a3e --- /dev/null +++ b/common/socket/src/main/java/io/helidon/common/socket/SocketWriter.java @@ -0,0 +1,157 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.socket; + +import java.util.concurrent.ArrayBlockingQueue; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicBoolean; + +import io.helidon.common.buffers.BufferData; +import io.helidon.common.buffers.CompositeBufferData; +import io.helidon.common.buffers.DataWriter; + +/** + * Socket writer (possibly) used from multiple threads, takes care of writing to a single + * socket. + */ +public class SocketWriter implements DataWriter { + private static final System.Logger LOGGER = System.getLogger(SocketWriter.class.getName()); + private static final BufferData CLOSING_TOKEN = BufferData.empty(); + private final ExecutorService executor; + private final HelidonSocket socket; + private final ArrayBlockingQueue writeQueue; + private final CountDownLatch cdl = new CountDownLatch(1); + private final String channelId; + private final AtomicBoolean started = new AtomicBoolean(false); + private volatile Throwable caught; + private volatile boolean run = true; + private Thread thread; + + /** + * A new socket writer. + * + * @param executor executor used to create a thread for asynchronous writes + * @param socket socket to write to + * @param channelId channel id of this connection + * @param writeQueueLength maximal number of queued writes, write operation will block if the queue is full + */ + public SocketWriter(ExecutorService executor, HelidonSocket socket, String channelId, int writeQueueLength) { + this.executor = executor; + this.socket = socket; + this.channelId = channelId; + this.writeQueue = new ArrayBlockingQueue<>(writeQueueLength); + } + + @Override + public void write(BufferData... buffers) { + for (BufferData buffer : buffers) { + write(buffer); + } + } + + @Override + public void write(BufferData buffer) { + checkRunning(); + try { + if (!writeQueue.offer(buffer, 10, TimeUnit.SECONDS)) { + checkRunning(); + throw new IllegalStateException("Failed to write data to queue, timed out"); + } + } catch (InterruptedException e) { + throw new IllegalStateException("Interrupted while trying to write to a queue", e); + } + } + + @Override + public void writeNow(BufferData... buffers) { + BufferData composite = BufferData.create(buffers); + writeNow(composite); + } + + @Override + public void writeNow(BufferData buffer) { + socket.write(buffer); + } + + /** + * Close this writer. Will attempt to write all enqueued buffers and will stop the thread if created. + */ + public void close() { + run = false; + if (!started.get()) { + // thread never started + return; + } + try { + writeQueue.put(CLOSING_TOKEN); // wake up blocked take() operation + if (cdl.await(1000, TimeUnit.MILLISECONDS)) { + // reads finished because we set run to false + BufferData available; + while ((available = writeQueue.poll()) != null) { + try { + writeNow(available); + } catch (Exception e) { + LOGGER.log(System.Logger.Level.TRACE, "Failed to write last buffers during writer shutdown", e); + // in case we fail to write to socket when closing, it is probably because it is already closed + // we still need to release all buffers + } + } + } + if (thread != null) { + // fail blocked writers + thread.interrupt(); + } + } catch (InterruptedException e) { // failed to get + } + + } + + private void run() { + this.thread = Thread.currentThread(); + try { + while (run) { + CompositeBufferData toWrite = BufferData.createComposite(writeQueue.take()); // wait if the queue is empty + // we only want to read a certain amount of data, if somebody writes huge amounts + // we could spin here forever and run out of memory + for (int i = 0; i < 1000; i++) { + BufferData newBuf = writeQueue.poll(); // drain ~all elements from the queue, don't wait. + if (newBuf == null) { + break; + } + toWrite.add(newBuf); + } + writeNow(toWrite); + } + cdl.countDown(); + } catch (Throwable e) { + this.caught = e; + this.run = false; + } + } + + private void checkRunning() { + if (started.compareAndSet(false, true)) { + // start writer on first asynchronous write + executor.submit(this::run); + } + if (!run) { + throw new SocketWriterException(caught); + } + } +} diff --git a/common/socket/src/main/java/io/helidon/common/socket/SocketWriterException.java b/common/socket/src/main/java/io/helidon/common/socket/SocketWriterException.java new file mode 100644 index 00000000000..9449e77075f --- /dev/null +++ b/common/socket/src/main/java/io/helidon/common/socket/SocketWriterException.java @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.socket; + +/** + * Socket write failed. + */ +public class SocketWriterException extends RuntimeException { + /** + * Socket write failed. + * + * @param cause original throwable + */ + public SocketWriterException(Throwable cause) { + super(cause); + } +} diff --git a/common/socket/src/main/java/io/helidon/common/socket/TlsSocket.java b/common/socket/src/main/java/io/helidon/common/socket/TlsSocket.java new file mode 100644 index 00000000000..9a423911b36 --- /dev/null +++ b/common/socket/src/main/java/io/helidon/common/socket/TlsSocket.java @@ -0,0 +1,113 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.socket; + +import java.security.Principal; +import java.security.cert.Certificate; +import java.util.Optional; + +import javax.net.ssl.SSLPeerUnverifiedException; +import javax.net.ssl.SSLSocket; + +/** + * TLS socket. + */ +public final class TlsSocket extends PlainSocket { + private final SSLSocket sslSocket; + + private TlsSocket(SSLSocket socket, String channelId, String serverChannelId) { + super(socket, channelId, serverChannelId); + + this.sslSocket = socket; + } + + /** + * Create a server TLS socket. + * + * @param delegate underlying socket + * @param channelId listener channel id + * @param serverChannelId connection channel id + * @return a new TLS socket + */ + public static TlsSocket server(SSLSocket delegate, + String channelId, + String serverChannelId) { + return new TlsSocket(delegate, channelId, serverChannelId); + } + + /** + * Create a client TLS socket. + * + * @param delegate underlying socket + * @param channelId channel id + * @return a new TLS socket + */ + public static TlsSocket client(SSLSocket delegate, + String channelId) { + return new TlsSocket(delegate, channelId, "client"); + } + + @Override + public PeerInfo remotePeer() { + return PeerInfoImpl.createRemote(this); + } + + @Override + public PeerInfo localPeer() { + return PeerInfoImpl.createLocal(this); + } + + @Override + public boolean isSecure() { + return true; + } + + @Override + public boolean protocolNegotiated() { + String protocol = sslSocket.getApplicationProtocol(); + return protocol != null && !protocol.isBlank(); + } + + @Override + public String protocol() { + return sslSocket.getApplicationProtocol(); + } + + Optional tlsPeerPrincipal() { + try { + return Optional.of(sslSocket.getSession().getPeerPrincipal()); + } catch (SSLPeerUnverifiedException e) { + return Optional.empty(); + } + } + + Optional tlsPeerCertificates() { + try { + return Optional.of(sslSocket.getSession().getPeerCertificates()); + } catch (SSLPeerUnverifiedException e) { + return Optional.empty(); + } + } + + Optional tlsPrincipal() { + return Optional.of(sslSocket.getSession().getLocalPrincipal()); + } + + Optional tlsCertificates() { + return Optional.of(sslSocket.getSession().getLocalCertificates()); + } +} diff --git a/common/socket/src/main/java/io/helidon/common/socket/package-info.java b/common/socket/src/main/java/io/helidon/common/socket/package-info.java new file mode 100644 index 00000000000..9458b8b6de2 --- /dev/null +++ b/common/socket/src/main/java/io/helidon/common/socket/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Socket abstraction and data writing. + */ +package io.helidon.common.socket; diff --git a/common/socket/src/main/java/module-info.java b/common/socket/src/main/java/module-info.java new file mode 100644 index 00000000000..a0edd917ba9 --- /dev/null +++ b/common/socket/src/main/java/module-info.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Socket abstraction and data writing. + */ +module io.helidon.common.socket { + requires io.helidon.common; + requires transitive io.helidon.common.buffers; + + exports io.helidon.common.socket; +} \ No newline at end of file From 9cad6cd81424874f39dc9c8873136ef4aba31b77 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 21:55:18 +0200 Subject: [PATCH 06/54] New URI abstraction to handle resource requests --- common/pom.xml | 1 + common/uri/README.md | 72 ++++ common/uri/pom.xml | 47 +++ .../io/helidon/common/uri/UriEncoding.java | 377 ++++++++++++++++++ .../io/helidon/common/uri/UriFragment.java | 102 +++++ .../java/io/helidon/common/uri/UriPath.java | 120 ++++++ .../io/helidon/common/uri/UriPathHelper.java | 58 +++ .../io/helidon/common/uri/UriPathNoParam.java | 110 +++++ .../io/helidon/common/uri/UriPathParam.java | 51 +++ .../helidon/common/uri/UriPathParameters.java | 143 +++++++ .../io/helidon/common/uri/UriPathSegment.java | 71 ++++ .../common/uri/UriPathSegmentImpl.java | 53 +++ .../java/io/helidon/common/uri/UriQuery.java | 80 ++++ .../io/helidon/common/uri/UriQueryEmpty.java | 85 ++++ .../io/helidon/common/uri/UriQueryImpl.java | 209 ++++++++++ .../helidon/common/uri/UriQueryWriteable.java | 84 ++++ .../common/uri/UriQueryWriteableImpl.java | 218 ++++++++++ .../io/helidon/common/uri/package-info.java | 20 + common/uri/src/main/java/module-info.java | 25 ++ .../helidon/common/uri/UriPathHelperTest.java | 51 +++ .../common/uri/UriPathParametersTest.java | 37 ++ .../common/uri/UriPathSegmentTest.java | 71 ++++ .../io/helidon/common/uri/UriQueryTest.java | 57 +++ 23 files changed, 2142 insertions(+) create mode 100644 common/uri/README.md create mode 100644 common/uri/pom.xml create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriEncoding.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriFragment.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriPath.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriPathHelper.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriPathNoParam.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriPathParam.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriPathParameters.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriPathSegment.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriPathSegmentImpl.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriQuery.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriQueryEmpty.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriQueryImpl.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteable.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteableImpl.java create mode 100644 common/uri/src/main/java/io/helidon/common/uri/package-info.java create mode 100644 common/uri/src/main/java/module-info.java create mode 100644 common/uri/src/test/java/io/helidon/common/uri/UriPathHelperTest.java create mode 100644 common/uri/src/test/java/io/helidon/common/uri/UriPathParametersTest.java create mode 100644 common/uri/src/test/java/io/helidon/common/uri/UriPathSegmentTest.java create mode 100644 common/uri/src/test/java/io/helidon/common/uri/UriQueryTest.java diff --git a/common/pom.xml b/common/pom.xml index bea3a8c9179..34e605d15d0 100644 --- a/common/pom.xml +++ b/common/pom.xml @@ -43,6 +43,7 @@ parameters buffers socket + uri testing diff --git a/common/uri/README.md b/common/uri/README.md new file mode 100644 index 00000000000..34613c1d3ff --- /dev/null +++ b/common/uri/README.md @@ -0,0 +1,72 @@ +URI +---- +URI abstraction as required for routing and request handling. + +# URI Encoding +URI has a limited set of allowed characters, with some characters having special meaning +(such as `#`, `/`, `;`), also the URI is expected to be ASCII string. + +Special characters (and UTF-8) can be used through encoding (using the `%##` format). +For example space character is encoded into `%20`. + +We support encoding of paths, with access to raw (encoded) and decoded values. + +Usage in components: +- Path + - raw path - encoded path including path parameters + - raw path no params - encoded path without path parameters + - decoded path - decoded path without path parameters + - decoded path parameter by name + - path segments (may have path parameters associated) - ordered sequence + of path parts (each segment is a section between `/`) +- Query + - raw query - full encoded query + - raw parameter by name + - decoded parameter by name +- Fragment + - raw fragment + - decoded fragment + +Method names - if a method returns raw (encoded) value, +it will always be prefixed with the word `raw`. All other +methods are returning decoded values. + +# URI Components +## Scheme +Scheme is not used by this module - see HTTP. + +## Authority +Authority is not used by this module. +Authority is `userinfo@server:port`, where both `userinfo@` and `port` are optional. + +## Path +Path is telling us which resource to find (hierarchical location). + +Path supports (and resolves) path parameters. +Path parameters are separated with a semicolon from path segments, values are separated +with an equals and parameter values may be separated by a comma. + +Example: `/hello;version=1.0;supper/world;version=2.0;colors=blue,green,brown` +Such a path will be resolved into: +- path: `/hello` +- parameter `version=1.0,2.0` (2 values) +- parameter `colors=blue,green,brow` (3 values) +- parameter `supper=` (1 value, empty string) + +Path parameters are resolved in order (from left to right) and values are appended +Path segments contain only parameters valid for them (expensive parsing!) + +## Query +Query provides us with additional parameters to process the request. + +Example: `/hello/world?lang=CZ` + +Such a URI will have query: +- parameter: `lang=CZ` + +## Fragment +Fragment provides a reference to a fragment of a page, such as a header (usually not used for queries to server). + +Example: `/hello/world#message` + +Fragment is `message`. \ No newline at end of file diff --git a/common/uri/pom.xml b/common/uri/pom.xml new file mode 100644 index 00000000000..6e4f274717e --- /dev/null +++ b/common/uri/pom.xml @@ -0,0 +1,47 @@ + + + + + 4.0.0 + + io.helidon.common + helidon-common-project + 4.0.0-SNAPSHOT + + helidon-common-uri + Helidon Common URI + + + + io.helidon.common + helidon-common-parameters + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriEncoding.java b/common/uri/src/main/java/io/helidon/common/uri/UriEncoding.java new file mode 100644 index 00000000000..d8a0623c4ed --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriEncoding.java @@ -0,0 +1,377 @@ +/* + * Copyright (c) 2000, 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import java.nio.ByteBuffer; +import java.nio.CharBuffer; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +/** + * Support for encoding and decoding of URI in HTTP. + */ +public final class UriEncoding { + private static final char[] HEX_DIGITS = { + '0', '1', '2', '3', '4', '5', '6', '7', + '8', '9', 'A', 'B', 'C', 'D', 'E', 'F' + }; + private static final int[] HEX_TABLE = initHexTable(); + private static final String[] SCHEME = {"0-9", "A-Z", "a-z", "+", "-", "."}; + private static final String[] UNRESERVED = {"0-9", "A-Z", "a-z", "-", ".", "_", "~"}; + private static final String[] SUB_DELIMS = {"!", "$", "&", "'", "(", ")", "*", "+", ",", ";", "="}; + private static final boolean[][] ENCODING_TABLES = initEncodingTables(); + + private UriEncoding() { + } + + /** + * Decode a URI segment. + * + * @param uriSegment URI segment with percent encoding + * @return decoded string + */ + public static String decodeUri(String uriSegment) { + if (uriSegment.length() == 0) { + return ""; + } + if (uriSegment.indexOf('%') == -1) { + return uriSegment; + } + + return decode(uriSegment); + } + + /** + * Encode a URI segment. + * + * @param uriSegment URI segment that may contain characters not allowed by the URI + * @return URI segment with percent encoding + */ + public static String encodeUri(String uriSegment) { + if (uriSegment.isEmpty()) { + return ""; + } + return encode(uriSegment); + } + + /** + * Encodes the characters of string that are either non-ASCII characters + * or are ASCII characters that are not allowed. + * + * @param s the string to be encoded + * @param t the URI component type identifying the ASCII characters that + * must be percent-encoded + * @return the encoded string + */ + public static String encode(final String s, final Type t) { + final boolean[] table = ENCODING_TABLES[t.ordinal()]; + + StringBuilder sb = null; + for (int offset = 0, codePoint; offset < s.length(); offset += Character.charCount(codePoint)) { + codePoint = s.codePointAt(offset); + + if (codePoint < 0x80 && table[codePoint]) { + if (sb != null) { + sb.append((char) codePoint); + } + } else { + if (codePoint == '%' + && offset + 2 < s.length() + && isHexCharacter(s.charAt(offset + 1)) + && isHexCharacter(s.charAt(offset + 2))) { + if (sb != null) { + sb.append('%').append(s.charAt(offset + 1)).append(s.charAt(offset + 2)); + } + offset += 2; + continue; + } + + if (sb == null) { + sb = new StringBuilder(); + sb.append(s, 0, offset); + } + + if (codePoint < 0x80) { + if (codePoint == ' ' && t == Type.QUERY_PARAM) { + sb.append('+'); + } else { + appendEscape(sb, (char) codePoint); + } + } else { + appendUTF8EncodedCharacter(sb, codePoint); + } + } + } + + return (sb == null) ? s : sb.toString(); + } + + private static String encode(String uriSegment) { + return encode(uriSegment, Type.PATH); + } + + private static void appendEscape(StringBuilder appender, int b) { + appender.append('%'); + appender.append(HEX_DIGITS[b >> 4]); + appender.append(HEX_DIGITS[b & 0x0F]); + } + + private static String decode(String string) { + + int len = string.length(); + + StringBuilder sb = new StringBuilder(len); + ByteBuffer bb = ByteBuffer.allocate(len); + + // This is not horribly efficient, but it will do for now + char c = string.charAt(0); + boolean betweenBrackets = false; + + int i = 0; + while (i < len) { + assert c == string.charAt(i); // Loop invariant + if (c == '[') { + betweenBrackets = true; + } else if (betweenBrackets && c == ']') { + betweenBrackets = false; + } + if (c != '%' || betweenBrackets) { + sb.append(c); + if (++i >= len) { + break; + } + c = string.charAt(i); + continue; + } + bb.clear(); + while (true) { + bb.put(decode(string.charAt(++i), string.charAt(++i))); + if (++i >= len) { + break; + } + c = string.charAt(i); + if (c != '%') { + break; + } + } + bb.flip(); + + CharBuffer cb = StandardCharsets.UTF_8.decode(bb); + sb.append(cb); + } + + return sb.toString(); + } + + private static byte decode(char c1, char c2) { + return (byte) (((decode(c1) & 0xf) << 4) | ((decode(c2) & 0xf))); + } + + private static int decode(char c) { + if ((c >= '0') && (c <= '9')) { + return c - '0'; + } + if ((c >= 'a') && (c <= 'f')) { + return c - 'a' + 10; + } + if ((c >= 'A') && (c <= 'F')) { + return c - 'A' + 10; + } + + return -1; + } + + private static void appendUTF8EncodedCharacter(StringBuilder sb, int codePoint) { + CharBuffer chars = CharBuffer.wrap(Character.toChars(codePoint)); + ByteBuffer bytes = StandardCharsets.UTF_8.encode(chars); + + while (bytes.hasRemaining()) { + appendEscape(sb, bytes.get() & 0xFF); + } + } + + private static int[] initHexTable() { + int[] table = new int[0x80]; + Arrays.fill(table, -1); + + for (char c = '0'; c <= '9'; c++) { + table[c] = c - '0'; + } + for (char c = 'A'; c <= 'F'; c++) { + table[c] = c - 'A' + 10; + } + for (char c = 'a'; c <= 'f'; c++) { + table[c] = c - 'a' + 10; + } + return table; + } + + private static boolean[][] initEncodingTables() { + boolean[][] tables = new boolean[Type.values().length][]; + + List l = new ArrayList(); + l.addAll(Arrays.asList(SCHEME)); + tables[Type.SCHEME.ordinal()] = initEncodingTable(l); + + l.clear(); + + l.addAll(Arrays.asList(UNRESERVED)); + tables[Type.UNRESERVED.ordinal()] = initEncodingTable(l); + + l.addAll(Arrays.asList(SUB_DELIMS)); + + tables[Type.HOST.ordinal()] = initEncodingTable(l); + + tables[Type.PORT.ordinal()] = initEncodingTable(Arrays.asList("0-9")); + + l.add(":"); + + tables[Type.USER_INFO.ordinal()] = initEncodingTable(l); + + l.add("@"); + + tables[Type.AUTHORITY.ordinal()] = initEncodingTable(l); + + tables[Type.PATH_SEGMENT.ordinal()] = initEncodingTable(l); + tables[Type.PATH_SEGMENT.ordinal()][';'] = false; + + tables[Type.MATRIX_PARAM.ordinal()] = tables[Type.PATH_SEGMENT.ordinal()].clone(); + tables[Type.MATRIX_PARAM.ordinal()]['='] = false; + + l.add("/"); + + tables[Type.PATH.ordinal()] = initEncodingTable(l); + + tables[Type.QUERY.ordinal()] = initEncodingTable(l); + tables[Type.QUERY.ordinal()]['!'] = false; + tables[Type.QUERY.ordinal()]['*'] = false; + tables[Type.QUERY.ordinal()]['\''] = false; + tables[Type.QUERY.ordinal()]['('] = false; + tables[Type.QUERY.ordinal()][')'] = false; + tables[Type.QUERY.ordinal()][';'] = false; + tables[Type.QUERY.ordinal()][':'] = false; + tables[Type.QUERY.ordinal()]['@'] = false; + tables[Type.QUERY.ordinal()]['$'] = false; + tables[Type.QUERY.ordinal()][','] = false; + tables[Type.QUERY.ordinal()]['/'] = false; + tables[Type.QUERY.ordinal()]['?'] = false; + + tables[Type.QUERY_PARAM.ordinal()] = Arrays.copyOf( + tables[Type.QUERY.ordinal()], + tables[Type.QUERY.ordinal()].length); + tables[Type.QUERY_PARAM.ordinal()]['='] = false; + tables[Type.QUERY_PARAM.ordinal()]['+'] = false; + tables[Type.QUERY_PARAM.ordinal()]['&'] = false; + + tables[Type.QUERY_PARAM_SPACE_ENCODED.ordinal()] = tables[Type.QUERY_PARAM.ordinal()]; + + tables[Type.FRAGMENT.ordinal()] = tables[Type.QUERY.ordinal()]; + + return tables; + } + + private static boolean[] initEncodingTable(List allowed) { + boolean[] table = new boolean[0x80]; + for (String range : allowed) { + if (range.length() == 1) { + table[range.charAt(0)] = true; + } else if (range.length() == 3 && range.charAt(1) == '-') { + for (int i = range.charAt(0); i <= range.charAt(2); i++) { + table[i] = true; + } + } + } + + return table; + } + + /** + * Checks whether the character {@code c} is hexadecimal character. + * + * @param c Any character + * @return The is {@code c} is a hexadecimal character (e.g. 0, 5, a, A, f, ...) + */ + private static boolean isHexCharacter(char c) { + return c < 128 && HEX_TABLE[c] != -1; + } + + /** + * The URI component type. + */ + public enum Type { + + /** + * ALPHA / DIGIT / "-" / "." / "_" / "~" characters. + */ + UNRESERVED, + /** + * The URI scheme component type. + */ + SCHEME, + /** + * The URI authority component type. + */ + AUTHORITY, + /** + * The URI user info component type. + */ + USER_INFO, + /** + * The URI host component type. + */ + HOST, + /** + * The URI port component type. + */ + PORT, + /** + * The URI path component type. + */ + PATH, + /** + * The URI path component type that is a path segment. + */ + PATH_SEGMENT, + /** + * The URI path component type that is a matrix parameter. + */ + MATRIX_PARAM, + /** + * The URI query component type, encoded using application/x-www-form-urlencoded rules. + */ + QUERY, + /** + * The URI query component type that is a query parameter, encoded using + * application/x-www-form-urlencoded rules (space character is encoded + * as {@code +}). + */ + QUERY_PARAM, + /** + * The URI query component type that is a query parameter, encoded using + * application/x-www-form-urlencoded (space character is encoded as + * {@code %20}). + */ + QUERY_PARAM_SPACE_ENCODED, + /** + * The URI fragment component type. + */ + FRAGMENT, + } + +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriFragment.java b/common/uri/src/main/java/io/helidon/common/uri/UriFragment.java new file mode 100644 index 00000000000..09de5a20555 --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriFragment.java @@ -0,0 +1,102 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +/** + * Fragment section of the URI. + */ +public class UriFragment { + private static final UriFragment EMPTY = new UriFragment(null, null); + + private final String rawFragment; + + private String decodedFragment; + + private UriFragment(String rawFragment) { + this.rawFragment = rawFragment; + } + + private UriFragment(String encoded, String fragment) { + this.rawFragment = encoded; + this.decodedFragment = fragment; + } + + /** + * Create a fragment from raw value. + * + * @param rawFragment fragment encoded value + * @return a new instance + */ + public static UriFragment create(String rawFragment) { + return new UriFragment(rawFragment); + } + + /** + * Create a fragment from decoded value. + * + * @param fragment fragment decoded value + * @return a new instance + */ + public static UriFragment createFromDecoded(String fragment) { + return new UriFragment(UriEncoding.encode(fragment, UriEncoding.Type.FRAGMENT), fragment); + } + + /** + * Empty fragment. + * @return empty instance + */ + public static UriFragment empty() { + return EMPTY; + } + + @Override + public String toString() { + if (rawFragment == null) { + return ""; + } + return value(); + } + + /** + * Whether there is a fragment. + * @return {@code true} if fragment exists + */ + public boolean hasValue() { + return rawFragment != null; + } + + /** + * Raw (encoded) value of the fragment. + * + * @return encoded fragment + */ + public String rawValue() { + return rawFragment; + } + + /** + * Value (decoded) of the fragment. + * + * @return decoded fragment + */ + public String value() { + if (decodedFragment == null) { + decodedFragment = UriEncoding.decodeUri(rawFragment); + } + return decodedFragment; + } +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriPath.java b/common/uri/src/main/java/io/helidon/common/uri/UriPath.java new file mode 100644 index 00000000000..859641b67c6 --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriPath.java @@ -0,0 +1,120 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import java.util.ArrayList; +import java.util.List; + +import io.helidon.common.parameters.Parameters; + +/** + * Abstraction of HTTP path supporting path parameters. + * Note that path parameters are ONLY available on {@link #absolute()} path + */ +public interface UriPath { + /** + * Creates a relative path from the provided path and the relative segment(s). + * + * @param uriPath used to obtain absolute path + * @param relativePath relative segment, must not contain path parameters and is expected to be decoded + * @return a new path representing the new relative path + */ + static UriPath createRelative(UriPath uriPath, String relativePath) { + return new UriPathNoParam(uriPath.absolute(), relativePath); + } + + /** + * Create a new path from its raw representation. + * + * @param rawPath raw path as received over the wire + * @return a new HTTP path + */ + static UriPath create(String rawPath) { + String rawPathNoParams = UriPathHelper.stripPathParams(rawPath); + if (rawPath.length() == rawPathNoParams.length()) { + return new UriPathNoParam(rawPath); + } + return new UriPathParam(rawPath, rawPathNoParams); + } + + /** + * Path as it was received on the wire (Server request), or path as it will be sent + * over the wire (Client request). This path may include path parameters. + * + * @return path + */ + String rawPath(); + + /** + * Path as it was received on the wire (Server request), or path as it will be sent + * over the wire (Client request) WITHOUT path parameters. + * + * @return path + */ + String rawPathNoParams(); + + /** + * Decoded path without path parameters. + * + * @return path without path parameters + * @see #pathParameters() + */ + String path(); + + /** + * Path parameters collected from the full path. + * Example of path with parameter: {@code /users;domain=work/john}. {@code domain} is the name of the parameter, + * {@code work} is value of the parameter. + * + * @return path parameters + */ + Parameters pathParameters(); + + /** + * If this instance represents a path relative to some context root then returns absolute requested path otherwise + * returns this instance. + *

+ * The absolute path also contains access to path parameters defined in context matchers. If there is + * name conflict then value represents latest matcher result. + * + * @return an absolute requested URI path + */ + UriPath absolute(); + + /** + * List of segments. + * This is the most detailed access to the underlying path that provides raw, decoded access to path, access to + * path parameters bound to each segment. + *

NOTE: this is an expensive method that requires full parsing of the path, please use with care + * + * @return list of URI path segments + */ + default List segments() { + String[] segmentStrings = rawPath().split("/"); + List segments = new ArrayList<>(segmentStrings.length); + for (String segmentString : segmentStrings) { + segments.add(UriPathSegment.create(segmentString)); + } + + return List.copyOf(segments); + } + + /** + * Validate if the raw path is valid. + */ + void validate(); +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriPathHelper.java b/common/uri/src/main/java/io/helidon/common/uri/UriPathHelper.java new file mode 100644 index 00000000000..d0f2abe0a33 --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriPathHelper.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +final class UriPathHelper { + private UriPathHelper() { + } + + /** + * This method operates on raw path string (encoded). This is to make sure we only string path parameters + * that are valid (using unencoded semicolons). The path may also contain an encoded semicolon, but that must be + * treated as part of the path. + * + * @param path raw path (may include path parameters) + * @return raw path without path parameters + */ + static String stripPathParams(String path) { + int i = path.indexOf(';'); + + if (i == -1) { + return path; + } + + StringBuilder result = new StringBuilder(); + String remainingPath = path; + while (true) { + result.append(remainingPath.substring(0, i)); + // now we need to find the next slash + remainingPath = remainingPath.substring(i + 1); + i = remainingPath.indexOf('/'); + if (i == -1) { + break; + } + remainingPath = remainingPath.substring(i); + i = remainingPath.indexOf(';'); + if (i == -1) { + result.append(remainingPath); + break; + } + } + + return result.toString(); + } +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriPathNoParam.java b/common/uri/src/main/java/io/helidon/common/uri/UriPathNoParam.java new file mode 100644 index 00000000000..6fcfd360fe6 --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriPathNoParam.java @@ -0,0 +1,110 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import java.net.URI; +import java.util.List; + +import io.helidon.common.parameters.Parameters; + +class UriPathNoParam implements UriPath { + private static final Parameters EMPTY_PARAMS = Parameters.empty("path"); + + private final UriPath absolute; + private final String rawPath; + private String decodedPath; + private List segments; + + UriPathNoParam(String rawPath) { + this.rawPath = rawPath; + this.absolute = this; + } + + UriPathNoParam(UriPath absolute, String relativePath) { + this.rawPath = relativePath; + this.decodedPath = relativePath; + this.absolute = absolute; + } + + @Override + public String rawPath() { + return rawPath; + } + + @Override + public String rawPathNoParams() { + return rawPath; + } + + @Override + public String path() { + if (decodedPath == null) { + decodedPath = decode(rawPath); + } + return decodedPath; + } + + @Override + public Parameters pathParameters() { + return EMPTY_PARAMS; + } + + @Override + public UriPath absolute() { + return absolute; + } + + @Override + public List segments() { + if (segments == null) { + segments = UriPath.super.segments(); + } + return segments; + } + + @Override + public String toString() { + return rawPath; + } + + @Override + public void validate() { + if (decodedPath == null) { + this.decodedPath = URI.create(rawPath).normalize().getPath(); + } + } + + private static String decode(String rawPath) { + /* + Raw path may: + - be encoded (%20) + - contain // that need to be resolved into / + - be relative with /./ and /../ - these need to be normalized + + we use decoded and normalized path to match routing, so we need to resolve all of that + */ + int percent = rawPath.indexOf('%'); + int dot = rawPath.indexOf("."); + int doubleSlash = rawPath.indexOf("//"); + + if (percent == -1 && doubleSlash == -1 && dot == -1) { + return rawPath; + } + + return URI.create(rawPath).normalize().getPath(); + } +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriPathParam.java b/common/uri/src/main/java/io/helidon/common/uri/UriPathParam.java new file mode 100644 index 00000000000..407e9aaf44c --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriPathParam.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import java.net.URI; + +import io.helidon.common.parameters.Parameters; + +class UriPathParam extends UriPathNoParam { + private final String rawPath; + private Parameters pathParams; + + UriPathParam(String rawPath, String noParamPath) { + super(noParamPath); + this.rawPath = rawPath; + } + + @Override + public String rawPath() { + return rawPath; + } + + @Override + public Parameters pathParameters() { + if (pathParams == null) { + pathParams = UriPathParameters.create(rawPath); + } + return pathParams; + } + + @Override + public void validate() { + //noinspection ResultOfMethodCallIgnored - this is to validate the path is correct + URI.create(rawPath); + super.validate(); + } +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriPathParameters.java b/common/uri/src/main/java/io/helidon/common/uri/UriPathParameters.java new file mode 100644 index 00000000000..ce2d229dc48 --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriPathParameters.java @@ -0,0 +1,143 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import io.helidon.common.parameters.Parameters; + +import static io.helidon.common.uri.UriEncoding.decodeUri; + +class UriPathParameters implements Parameters { + private final Map> pathParams; + + private UriPathParameters(Map> pathParams) { + this.pathParams = pathParams; + } + + static Parameters create(String rawPath) { + Map> pathParams = new HashMap<>(); + + // first split into segments + String[] segments = rawPath.split("/"); + // now for each segment, find path parameters + + // /user;domain=abc;u=a/john;u=b + // must result in path parameters domain=abc and u=b + for (String segment : segments) { + int semicolon = segment.indexOf(';'); + if (semicolon == -1) { + continue; + } + + // remaining is now just the path params + // domain=abc;u=a + String remaining = segment.substring(semicolon + 1); + + while (true) { + String param; + + semicolon = remaining.indexOf(';'); + if (semicolon == -1) { + // this segment does not contain any path parameters + if (remaining.isEmpty()) { + break; + } else { + param = remaining; + remaining = ""; + } + } else { + param = remaining.substring(0, semicolon); + remaining = remaining.substring(semicolon + 1); + } + + // param is the next parameters + // remaining is the rest of the string or empty + // param now contains the path parameter + int eq = param.indexOf('='); + if (eq == -1) { + pathParams.computeIfAbsent(decodeUri(param), it -> new ArrayList<>(1)) + .add(""); + } else { + // now each value may be comma separated + String[] values = param.substring(eq + 1).split(","); + List valueList = pathParams.computeIfAbsent(decodeUri(param.substring(0, eq)), + it -> new ArrayList<>(1)); + for (String value : values) { + valueList.add(decodeUri(value)); + } + } + } + } + + pathParams.replaceAll((name, values) -> List.copyOf(values)); + return new UriPathParameters(pathParams); + } + + @Override + public List all(String name) throws NoSuchElementException { + List value = pathParams.get(name); + if (value == null) { + throw new NoSuchElementException("This path does not contain parameter named \"" + name + "\""); + } + return value; + } + + @Override + public String value(String name) throws NoSuchElementException { + List value = pathParams.get(name); + if (value == null) { + throw new NoSuchElementException("This path does not contain parameter named \"" + name + "\""); + } + return value.get(0); + } + + @Override + public boolean contains(String name) { + return pathParams.containsKey(name); + } + + @Override + public boolean isEmpty() { + return pathParams.isEmpty(); + } + + @Override + public int size() { + return pathParams.size(); + } + + @Override + public Set names() { + return pathParams.keySet(); + } + + @Override + public String component() { + return "uri-path-parameters"; + } + + @Override + public String toString() { + return component() + ": " + pathParams; + } +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriPathSegment.java b/common/uri/src/main/java/io/helidon/common/uri/UriPathSegment.java new file mode 100644 index 00000000000..8a032ca6090 --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriPathSegment.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import io.helidon.common.parameters.Parameters; + +/** + * Segment of a path. + */ +public interface UriPathSegment { + /** + * Create a new path segment from raw (encoded) segment value that may contain path parameters. + * + * @param pathSegment raw path segment + * @return parsed path segment + */ + static UriPathSegment create(String pathSegment) { + /* + We can get: + - empty string ("/") + - text ("/plaintext") + - encoded text ("/plain%20text") + - text with path param(s) ("/plaintext;v=1.0;a;b=c,d") + */ + Parameters pathParameters = UriPathParameters.create(pathSegment); + String rawPathNoParams = UriPathHelper.stripPathParams(pathSegment); + String decoded = UriEncoding.decodeUri(rawPathNoParams); + return new UriPathSegmentImpl(pathSegment, rawPathNoParams, pathParameters, decoded); + } + + /** + * The text value of this path segment, without leading slash. This will return empty + * string for path segments that do not have any text. + * + * @return decoded value of this path segment without path parameters + */ + String value(); + + /** + * The raw text value (encoded) of this path segment. + * @return encoded value of this path segment with path parameters + */ + String rawValue(); + + /** + * The raw text value (encoded) of this path segment without path parameters. + * @return encoded value of this path segment without path parameters + */ + String rawValueNoParams(); + + /** + * All path parameters of this segment. + * + * @return path parameters + */ + Parameters pathParameters(); +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriPathSegmentImpl.java b/common/uri/src/main/java/io/helidon/common/uri/UriPathSegmentImpl.java new file mode 100644 index 00000000000..14f3c8dec6d --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriPathSegmentImpl.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import io.helidon.common.parameters.Parameters; + +class UriPathSegmentImpl implements UriPathSegment { + private final String rawPath; + private final String rawPathNoParams; + private final Parameters pathParameters; + private final String decoded; + + UriPathSegmentImpl(String rawPath, String rawPathNoParams, Parameters pathParameters, String decoded) { + this.rawPath = rawPath; + this.rawPathNoParams = rawPathNoParams; + this.pathParameters = pathParameters; + this.decoded = decoded; + } + + @Override + public String value() { + return decoded; + } + + @Override + public String rawValue() { + return rawPath; + } + + @Override + public String rawValueNoParams() { + return rawPathNoParams; + } + + @Override + public Parameters pathParameters() { + return pathParameters; + } +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriQuery.java b/common/uri/src/main/java/io/helidon/common/uri/UriQuery.java new file mode 100644 index 00000000000..1c2bdb04672 --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriQuery.java @@ -0,0 +1,80 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import java.util.List; +import java.util.NoSuchElementException; + +import io.helidon.common.parameters.Parameters; + +/** + * HTTP Query representation. + * Query is the section separated by {@code ?} character from the path, where each query parameter is separated + * by {@code &} character. + */ +public interface UriQuery extends Parameters { + /** + * Create a new HTTP query from the query string. + * + * @param query raw query string + * @return HTTP query instance + */ + static UriQuery create(String query) { + return new UriQueryImpl(query); + } + + /** + * Create an empty HTTP query. + * + * @return HTTP query instance + */ + static UriQuery empty() { + return UriQueryEmpty.INSTANCE; + } + + /** + * Query fully encoded, without the leading {@code ?} character. + * + * @return raw query to be sent (or that was received) over the wire + */ + String rawValue(); + + /** + * Query NOT encoded without the leading {@code ?} character. + * + * @return query to be sent (or that was received) over the wire but decoded + */ + String value(); + + /** + * Get raw value (undecoded) of a query parameter by its name. + * + * @param name query parameter name + * @return value of the parameter, raw + * @throws NoSuchElementException in case the parameter does not exist + */ + String getRaw(String name) throws NoSuchElementException; + + /** + * Get all raw values (undecoded) of a query parameter by its name. + * + * @param name query parameter name + * @return values of the parameter, raw + * @throws NoSuchElementException in case the parameter does not exist + */ + List getAllRaw(String name) throws NoSuchElementException; +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriQueryEmpty.java b/common/uri/src/main/java/io/helidon/common/uri/UriQueryEmpty.java new file mode 100644 index 00000000000..4fa222e46e5 --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriQueryEmpty.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import java.util.List; +import java.util.NoSuchElementException; +import java.util.Set; + +final class UriQueryEmpty implements UriQuery { + static final UriQuery INSTANCE = new UriQueryEmpty(); + + @Override + public String rawValue() { + return ""; + } + + @Override + public String value() { + return ""; + } + + @Override + public String getRaw(String name) throws NoSuchElementException { + throw new UnsupportedOperationException("Empty query"); + } + + @Override + public List getAllRaw(String name) { + throw new UnsupportedOperationException("Empty query"); + } + + @Override + public List all(String name) { + throw new UnsupportedOperationException("Empty query"); + } + + @Override + public String value(String name) { + throw new UnsupportedOperationException("Empty query"); + } + + @Override + public boolean contains(String name) { + return false; + } + + @Override + public boolean isEmpty() { + return true; + } + + @Override + public int size() { + return 0; + } + + @Override + public Set names() { + return Set.of(); + } + + @Override + public String toString() { + return ""; + } + + @Override + public String component() { + return "uri-query"; + } +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriQueryImpl.java b/common/uri/src/main/java/io/helidon/common/uri/UriQueryImpl.java new file mode 100644 index 00000000000..ae916047efa --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriQueryImpl.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; + +import static io.helidon.common.uri.UriEncoding.decodeUri; + +// must be lazily populated to prevent perf overhead when queries are ignored +final class UriQueryImpl implements UriQuery { + private final String query; + + private Map> rawQueryParams; + private Map> decodedQueryParams; + + UriQueryImpl(String query) { + this.query = query; + } + + @Override + public String rawValue() { + return query; + } + + @Override + public String value() { + ensureDecoded(); + + if (decodedQueryParams.isEmpty()) { + return ""; + } + + List params = new ArrayList<>(decodedQueryParams.size()); + + decodedQueryParams.forEach((name, values) -> { + params.add(name + "=" + String.join(",", values)); + }); + + return String.join("&", params); + } + + @Override + public String getRaw(String name) throws NoSuchElementException { + ensureRaw(); + List values = rawQueryParams.get(name); + if (values == null) { + throw new NoSuchElementException("Query parameter \"" + name + "\" is not available"); + } + return values.isEmpty() ? "" : values.iterator().next(); + } + + @Override + public List getAllRaw(String name) throws NoSuchElementException { + ensureRaw(); + List values = rawQueryParams.get(name); + if (values == null) { + throw new NoSuchElementException("Query parameter \"" + name + "\" is not available"); + } + return values; + } + + @Override + public List all(String name) throws NoSuchElementException { + ensureDecoded(); + List values = decodedQueryParams.get(name); + if (values == null) { + throw new NoSuchElementException("Query parameter \"" + name + "\" is not available"); + } + return values; + } + + @Override + public String value(String name) throws NoSuchElementException { + ensureDecoded(); + List values = decodedQueryParams.get(name); + if (values == null) { + throw new NoSuchElementException("Query parameter \"" + name + "\" is not available"); + } + return values.isEmpty() ? "" : values.iterator().next(); + } + + @Override + public boolean contains(String name) { + ensureDecoded(); + return decodedQueryParams.containsKey(name); + } + + @Override + public boolean isEmpty() { + return false; + } + + @Override + public int size() { + ensureDecoded(); + return decodedQueryParams.size(); + } + + @Override + public Set names() { + ensureDecoded(); + return decodedQueryParams.keySet(); + } + + @Override + public String component() { + return "uri-query"; + } + + @Override + public String toString() { + return "?" + rawValue(); + } + + private void ensureDecoded() { + if (decodedQueryParams == null) { + Map> newQueryParams = new HashMap<>(); + + if (query == null) { + decodedQueryParams = newQueryParams; + return; + } + + String remaining = query; + String next; + int and; + while (true) { + and = remaining.indexOf('&'); + if (and == -1) { + addDecoded(newQueryParams, remaining); + break; + } + next = remaining.substring(0, and); + remaining = remaining.substring(and + 1); + addDecoded(newQueryParams, next); + } + + decodedQueryParams = newQueryParams; + } + } + + private void addDecoded(Map> newQueryParams, String next) { + int eq = next.indexOf('='); + if (eq == -1) { + newQueryParams.putIfAbsent(decodeUri(next), new LinkedList<>()); + } else { + String name = next.substring(0, eq); + String value = next.substring(eq + 1); + newQueryParams.computeIfAbsent(decodeUri(name), it -> new LinkedList<>()).add(decodeUri(value)); + } + } + + private void ensureRaw() { + if (rawQueryParams == null) { + Map> newQueryParams = new HashMap<>(); + + if (query == null) { + rawQueryParams = newQueryParams; + return; + } + String remaining = query; + String next; + int and; + while (true) { + and = remaining.indexOf('&'); + if (and == -1) { + addRaw(newQueryParams, remaining); + break; + } + next = remaining.substring(0, and); + remaining = remaining.substring(and + 1); + addRaw(newQueryParams, next); + } + + rawQueryParams = newQueryParams; + } + } + + private void addRaw(Map> newQueryParams, String next) { + int eq = next.indexOf('='); + if (eq == -1) { + newQueryParams.putIfAbsent(next, new LinkedList<>()); + } else { + String name = next.substring(0, eq); + String value = next.substring(eq + 1); + newQueryParams.computeIfAbsent(name, it -> new LinkedList<>()).add(value); + } + } +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteable.java b/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteable.java new file mode 100644 index 00000000000..a32aab05db3 --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteable.java @@ -0,0 +1,84 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import java.util.List; +import java.util.function.Consumer; + +/** + * Mutable HTTP query. + */ +public interface UriQueryWriteable extends UriQuery { + /** + * Create a new HTTP Query to write parameter into. + * + * @return new mutable HTTP query + */ + static UriQueryWriteable create() { + return new UriQueryWriteableImpl(); + } + + /** + * Set a query parameter with values. + * + * @param name name of the parameter + * @param value value(s) of the parameter + * @return this instance + */ + UriQueryWriteable set(String name, String... value); + + /** + * Add a new query parameter or add a value to existing. + * + * @param name name of the parameter + * @param value additional value of the parameter + * @return this instance + */ + UriQueryWriteable add(String name, String value); + + /** + * Set a query parameter with values, if not already defined. + * + * @param name name of the parameter + * @param value value(s) of the parameter + * @return this instance + */ + UriQueryWriteable setIfAbsent(String name, String... value); + + /** + * Remove a query parameter. + * + * @param name name of the parameter + * @return this instance + */ + UriQueryWriteable remove(String name); + + /** + * Remove a query parameter. + * + * @param name name of the parameter + * @param removedConsumer consumer of existing values, only called if there was an existing value + * @return this instance + */ + UriQueryWriteable remove(String name, Consumer> removedConsumer); + + /** + * Update from a query string (with decoded values). + * @param queryString decoded query string to update this instance + */ + void fromQueryString(String queryString); +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteableImpl.java b/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteableImpl.java new file mode 100644 index 00000000000..6ea4979ce5e --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/UriQueryWriteableImpl.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.Set; +import java.util.function.Consumer; + +final class UriQueryWriteableImpl implements UriQueryWriteable { + private final Map> rawQueryParams = new HashMap<>(); + private final Map> decodedQueryParams = new HashMap<>(); + + UriQueryWriteableImpl() { + } + + @Override + public String rawValue() { + StringBuilder sb = new StringBuilder(); + + for (Map.Entry> entry : rawQueryParams.entrySet()) { + String name = entry.getKey(); + for (String value : entry.getValue()) { + sb.append('&'); + sb.append(name); + sb.append('='); + sb.append(value); + } + } + if (sb.isEmpty()) { + return ""; + } + sb.deleteCharAt(0); + return sb.toString(); + } + + @Override + public String value() { + if (decodedQueryParams.isEmpty()) { + return ""; + } + + List params = new ArrayList<>(decodedQueryParams.size()); + + decodedQueryParams.forEach((name, values) -> { + params.add(name + "=" + String.join(",", values)); + }); + + return String.join("&", params); + } + + @Override + public String getRaw(String name) throws NoSuchElementException { + List values = rawQueryParams.get(name); + if (values == null) { + throw new NoSuchElementException("Query parameter \"" + name + "\" is not available"); + } + return values.isEmpty() ? "" : values.iterator().next(); + } + + @Override + public List getAllRaw(String name) throws NoSuchElementException { + List values = rawQueryParams.get(name); + if (values == null) { + throw new NoSuchElementException("Query parameter \"" + name + "\" is not available"); + } + return values; + } + + @Override + public List all(String name) throws NoSuchElementException { + List values = decodedQueryParams.get(name); + if (values == null) { + throw new NoSuchElementException("Query parameter \"" + name + "\" is not available"); + } + return values; + } + + @Override + public String value(String name) throws NoSuchElementException { + List values = decodedQueryParams.get(name); + if (values == null) { + throw new NoSuchElementException("Query parameter \"" + name + "\" is not available"); + } + return values.isEmpty() ? "" : values.iterator().next(); + } + + @Override + public boolean contains(String name) { + return rawQueryParams.containsKey(name); + } + + @Override + public boolean isEmpty() { + return decodedQueryParams.isEmpty(); + } + + @Override + public int size() { + return decodedQueryParams.size(); + } + + @Override + public Set names() { + return decodedQueryParams.keySet(); + } + + @Override + public String component() { + return "uri-query"; + } + + @Override + public UriQueryWriteable set(String name, String... values) { + String encodedName = UriEncoding.encode(name, UriEncoding.Type.QUERY_PARAM_SPACE_ENCODED); + + List decodedValues = new ArrayList<>(values.length); + List encodedValues = new ArrayList<>(values.length); + + for (String value : values) { + decodedValues.add(value); + encodedValues.add(UriEncoding.encode(value, UriEncoding.Type.QUERY_PARAM_SPACE_ENCODED)); + } + + rawQueryParams.put(encodedName, encodedValues); + decodedQueryParams.put(name, decodedValues); + + return this; + } + + @Override + public UriQueryWriteable add(String name, String value) { + String encodedName = UriEncoding.encodeUri(name); + String encodedValue = UriEncoding.encodeUri(value); + + rawQueryParams.computeIfAbsent(encodedName, it -> new ArrayList<>(1)) + .add(encodedValue); + decodedQueryParams.computeIfAbsent(name, it -> new ArrayList<>(1)) + .add(value); + + return this; + } + + @Override + public UriQueryWriteable setIfAbsent(String name, String... value) { + if (rawQueryParams.containsKey(name)) { + return this; + } + return set(name, value); + } + + @Override + public UriQueryWriteable remove(String name) { + rawQueryParams.remove(name); + decodedQueryParams.remove(name); + return this; + } + + @Override + public UriQueryWriteable remove(String name, Consumer> removedConsumer) { + rawQueryParams.remove(name); + List removed = decodedQueryParams.remove(name); + if (removed != null) { + removedConsumer.accept(removed); + } + return this; + } + + @Override + public void fromQueryString(String queryString) { + String remaining = queryString; + String next; + int and; + while (true) { + and = remaining.indexOf('&'); + if (and == -1) { + addRaw(remaining); + break; + } + next = remaining.substring(0, and); + remaining = remaining.substring(and + 1); + addRaw(next); + } + } + + @Override + public String toString() { + return component() + ": decoded: " + decodedQueryParams + ", raw: " + rawQueryParams; + } + + private void addRaw(String next) { + int eq = next.indexOf('='); + if (eq == -1) { + set(next); + } else { + String name = next.substring(0, eq); + String value = next.substring(eq + 1); + set(name, value); + } + } +} diff --git a/common/uri/src/main/java/io/helidon/common/uri/package-info.java b/common/uri/src/main/java/io/helidon/common/uri/package-info.java new file mode 100644 index 00000000000..82ced9e073a --- /dev/null +++ b/common/uri/src/main/java/io/helidon/common/uri/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * URI utilities. + */ +package io.helidon.common.uri; diff --git a/common/uri/src/main/java/module-info.java b/common/uri/src/main/java/module-info.java new file mode 100644 index 00000000000..6af834ab471 --- /dev/null +++ b/common/uri/src/main/java/module-info.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * URI utilities. + */ +module io.helidon.common.uri { + // Parameters used in public API + requires transitive io.helidon.common.parameters; + + exports io.helidon.common.uri; +} \ No newline at end of file diff --git a/common/uri/src/test/java/io/helidon/common/uri/UriPathHelperTest.java b/common/uri/src/test/java/io/helidon/common/uri/UriPathHelperTest.java new file mode 100644 index 00000000000..49244316a3e --- /dev/null +++ b/common/uri/src/test/java/io/helidon/common/uri/UriPathHelperTest.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import org.junit.jupiter.api.Test; + +import static io.helidon.common.uri.UriPathHelper.stripPathParams; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class UriPathHelperTest { + + @Test + void testStripPathParams() { + String expected = "/users/user/john"; + + String actual = stripPathParams("/users;domain=helidon/user;source=cz;type=main/john;alt=John"); + assertThat(actual, is(expected)); + + // the method is expected to remove path parameters, it does not attempt + // to "fix" the path, nor does it take care of queries + assertThat(stripPathParams("/admin/list"), is("/admin/list")); + assertThat(stripPathParams("/admin;a=b"), is("/admin")); + assertThat(stripPathParams("/admin;a=b/list;c=d;e=f"), is("/admin/list")); + assertThat(stripPathParams("/admin;a=b/list;c=d;e=f;"), is("/admin/list")); + assertThat(stripPathParams("/admin;"), is("/admin")); + assertThat(stripPathParams("/admin;/list"), is("/admin/list")); + assertThat(stripPathParams("/admin/;/list/"), is("/admin//list/")); + assertThat(stripPathParams("/;a=b"), is("/")); + assertThat(stripPathParams(";a=b;c=d;"), is("")); + assertThat(stripPathParams("/admin/;"), is("/admin/")); + assertThat(stripPathParams("/admin//;"), is("/admin//")); + assertThat(stripPathParams("/;"), is("/")); + assertThat(stripPathParams(";"), is("")); + assertThat(stripPathParams(";/admin/"), is("/admin/")); + } +} \ No newline at end of file diff --git a/common/uri/src/test/java/io/helidon/common/uri/UriPathParametersTest.java b/common/uri/src/test/java/io/helidon/common/uri/UriPathParametersTest.java new file mode 100644 index 00000000000..9bf9fab2b15 --- /dev/null +++ b/common/uri/src/test/java/io/helidon/common/uri/UriPathParametersTest.java @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import io.helidon.common.parameters.Parameters; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsInAnyOrder; + +class UriPathParametersTest { + @Test + void testPathParams() { + String path = "/user;domain=abc;u=a/john;u=b"; + + Parameters parameters = UriPathParameters.create(path); + assertThat(parameters.names(), containsInAnyOrder("domain", "u")); + assertThat(parameters.all("domain"), hasItems("abc")); + assertThat(parameters.all("u"), hasItems("a", "b")); + } +} \ No newline at end of file diff --git a/common/uri/src/test/java/io/helidon/common/uri/UriPathSegmentTest.java b/common/uri/src/test/java/io/helidon/common/uri/UriPathSegmentTest.java new file mode 100644 index 00000000000..df3ea921d90 --- /dev/null +++ b/common/uri/src/test/java/io/helidon/common/uri/UriPathSegmentTest.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import io.helidon.common.parameters.Parameters; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class UriPathSegmentTest { + @Test + void testSimplePath() { + String rawPath = "plaintext"; + UriPathSegment segment = UriPathSegment.create(rawPath); + + assertThat(segment.rawValue(), is(rawPath)); + assertThat(segment.value(), is(rawPath)); + assertThat(segment.rawValueNoParams(), is(rawPath)); + assertThat(segment.pathParameters().isEmpty(), is(true)); + } + + @Test + void testPathWithParams() { + String rawPath = "plaintext;v=1.0;a=b;b=1,2;c"; + UriPathSegment segment = UriPathSegment.create(rawPath); + + assertThat(segment.rawValue(), is(rawPath)); + assertThat(segment.value(), is("plaintext")); + assertThat(segment.rawValueNoParams(), is("plaintext")); + + Parameters params = segment.pathParameters(); + assertThat(params.isEmpty(), is(false)); + assertThat(params.value("v"), is("1.0")); + assertThat(params.value("a"), is("b")); + assertThat(params.value("b"), is("1")); + assertThat(params.value("c"), is("")); + } + + @Test + void testPathWithParamsAndEncoding() { + String rawPath = "pla%20i%2Fn%3Btext;v=1.0;a=b;b=1,2;c"; + UriPathSegment segment = UriPathSegment.create(rawPath); + + assertThat(segment.rawValue(), is(rawPath)); + assertThat(segment.value(), is("pla i/n;text")); + assertThat(segment.rawValueNoParams(), is("pla%20i%2Fn%3Btext")); + + Parameters params = segment.pathParameters(); + assertThat(params.isEmpty(), is(false)); + assertThat(params.value("v"), is("1.0")); + assertThat(params.value("a"), is("b")); + assertThat(params.value("b"), is("1")); + assertThat(params.value("c"), is("")); + } +} \ No newline at end of file diff --git a/common/uri/src/test/java/io/helidon/common/uri/UriQueryTest.java b/common/uri/src/test/java/io/helidon/common/uri/UriQueryTest.java new file mode 100644 index 00000000000..70d5faa115f --- /dev/null +++ b/common/uri/src/test/java/io/helidon/common/uri/UriQueryTest.java @@ -0,0 +1,57 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.uri; + +import java.io.UnsupportedEncodingException; +import java.net.URI; +import java.net.URLEncoder; + +import org.junit.jupiter.api.Test; + +import static java.nio.charset.StandardCharsets.US_ASCII; +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.core.Is.is; + +class UriQueryTest { + @Test + void sanityParse() { + UriQuery uriQuery = UriQuery.create(URI.create("http://foo/bar?a=b&c=d&a=e").getRawQuery()); + + assertThat(uriQuery.all("a"), hasItems("b", "e")); + assertThat(uriQuery.all("c"), hasItems("d")); + assertThat(uriQuery.contains("z"), is(false)); + } + + @Test + void testEncoded() throws UnsupportedEncodingException { + UriQuery uriQuery = UriQuery.create("a=" + URLEncoder.encode("1&b=2", US_ASCII.name())); + assertThat(uriQuery.value("a"), is("1&b=2")); + } + + @Test + void testEncodedOtherChars() { + UriQuery uriQuery = UriQuery.create("a=b%26c=d&e=f&e=g&h=x%63%23e%3c"); + assertThat("The query string must remain encoded otherwise no-one could tell whether a '&' was really a '&' or '%26'", + uriQuery.rawValue(), + is("a=b%26c=d&e=f&e=g&h=x%63%23e%3c")); + + assertThat(uriQuery.all("e"), hasItems("f", "g")); + assertThat(uriQuery.value("h"), is("xc#e<")); + assertThat(uriQuery.value("a"), is("b&c=d")); + } +} \ No newline at end of file From 1fad1ee301ec834731f52de67e1805507e71453f Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 22:22:23 +0200 Subject: [PATCH 07/54] Updated HTTP common module --- common/http/pom.xml | 15 +- .../helidon/common/http/AcceptPredicate.java | 47 - .../http/AlreadyCompletedException.java | 55 - .../java/io/helidon/common/http/Ascii.java | 107 -- .../io/helidon/common/http/CharMatcher.java | 783 -------- .../java/io/helidon/common/http/Content.java | 144 -- .../io/helidon/common/http/FormBuilder.java | 37 - .../io/helidon/common/http/FormParams.java | 83 - .../helidon/common/http/FormParamsImpl.java | 79 - .../helidon/common/http/HashParameters.java | 389 ---- .../java/io/helidon/common/http/Headers.java | 179 +- .../java/io/helidon/common/http/Http.java | 1614 ++++++++++++----- .../io/helidon/common/http/HttpRequest.java | 133 -- .../io/helidon/common/http/MediaType.java | 670 ------- .../io/helidon/common/http/Parameters.java | 232 --- .../io/helidon/common/http/Preconditions.java | 208 --- .../common/http/ReadOnlyParameters.java | 158 -- .../java/io/helidon/common/http/Reader.java | 82 - .../io/helidon/common/http/SetCookie.java | 11 +- .../io/helidon/common/http/Tokenizer.java | 176 -- .../common/http/UnmodifiableParameters.java | 110 -- .../io/helidon/common/http/UriComponent.java | 97 - .../java/io/helidon/common/http/Utils.java | 14 +- common/http/src/main/java/module-info.java | 8 +- .../io/helidon/common/http/AsciiTest.java | 69 - .../helidon/common/http/FormParamsTest.java | 98 - .../java/io/helidon/common/http/HttpTest.java | 12 +- .../io/helidon/common/http/MediaTypeTest.java | 75 +- .../helidon/common/http/UriComponentTest.java | 66 - 29 files changed, 1354 insertions(+), 4397 deletions(-) delete mode 100644 common/http/src/main/java/io/helidon/common/http/AcceptPredicate.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/AlreadyCompletedException.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/Ascii.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/CharMatcher.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/Content.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/FormBuilder.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/FormParams.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/FormParamsImpl.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/HashParameters.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/HttpRequest.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/MediaType.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/Parameters.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/Preconditions.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/ReadOnlyParameters.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/Reader.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/Tokenizer.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/UnmodifiableParameters.java delete mode 100644 common/http/src/main/java/io/helidon/common/http/UriComponent.java delete mode 100644 common/http/src/test/java/io/helidon/common/http/AsciiTest.java delete mode 100644 common/http/src/test/java/io/helidon/common/http/FormParamsTest.java delete mode 100644 common/http/src/test/java/io/helidon/common/http/UriComponentTest.java diff --git a/common/http/pom.xml b/common/http/pom.xml index 066ab185a46..f5ba55251fe 100644 --- a/common/http/pom.xml +++ b/common/http/pom.xml @@ -27,19 +27,28 @@ 4.0.0 helidon-common-http Helidon Common HTTP + Common HTTP primitives used by both blocking and reactive servers io.helidon.common - helidon-common-reactive + helidon-common io.helidon.common - helidon-common + helidon-common-buffers + + + io.helidon.common + helidon-common-mapper + + + io.helidon.common + helidon-common-media-type io.helidon.common - helidon-common-context + helidon-common-uri org.junit.jupiter diff --git a/common/http/src/main/java/io/helidon/common/http/AcceptPredicate.java b/common/http/src/main/java/io/helidon/common/http/AcceptPredicate.java deleted file mode 100644 index 85cbfc7ef12..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/AcceptPredicate.java +++ /dev/null @@ -1,47 +0,0 @@ -/* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import java.util.function.Predicate; - -/** - * API to model HTTP content negotiation using {@code Accept-*} request headers. (RFC 7231 and RFC 2295) - *

- * It extends {@link Predicate} for smooth integration with standard functional APIs. - * - * @param The type of the Accept-* header value. - */ -public interface AcceptPredicate extends Predicate { - - /** - * The media type quality factor ({@value QUALITY_FACTOR_PARAMETER}) parameter name. - */ - String QUALITY_FACTOR_PARAMETER = "q"; - - /** - * The wildcard value {@value #WILDCARD_VALUE} used by standard in several headers. - */ - String WILDCARD_VALUE = "*"; - - /** - * Gets quality factor parameter ({@value QUALITY_FACTOR_PARAMETER}) as a double value. If missing, then returns {@code 1.0} - * - * @return Quality factor parameter. - */ - double qualityFactor(); - -} diff --git a/common/http/src/main/java/io/helidon/common/http/AlreadyCompletedException.java b/common/http/src/main/java/io/helidon/common/http/AlreadyCompletedException.java deleted file mode 100644 index 274bb99d9b8..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/AlreadyCompletedException.java +++ /dev/null @@ -1,55 +0,0 @@ -/* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -/** - * Signals that a mutation method has been invoked on a resource that is already completed. - * - * It is no longer possible to mute state of these objects. - */ -public class AlreadyCompletedException extends IllegalStateException { - - /** - * Constructs an {@link AlreadyCompletedException} with the specified detail message. - * - * @param s the String that contains a detailed message. - */ - public AlreadyCompletedException(String s) { - super(s); - } - - /** - * Constructs an {@link AlreadyCompletedException} with the specified detail message and cause. - * - * @param message the detail message (which is saved for later retrieval by the {@link Throwable#getMessage()} method). - * @param cause the cause (which is saved for later retrieval by the {@link Throwable#getCause()} method). - * (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) - */ - public AlreadyCompletedException(String message, Throwable cause) { - super(message, cause); - } - - /** - * Constructs an {@link AlreadyCompletedException} with the specified cause. - * - * @param cause the cause (which is saved for later retrieval by the {@link Throwable#getCause()} method). - * (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) - */ - public AlreadyCompletedException(Throwable cause) { - super(cause); - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/Ascii.java b/common/http/src/main/java/io/helidon/common/http/Ascii.java deleted file mode 100644 index 43c1e3ffd0e..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/Ascii.java +++ /dev/null @@ -1,107 +0,0 @@ -/* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -/** - * Extracted from Guava. - *

- * Static methods pertaining to ASCII characters (those in the range of values {@code 0x00} through - * {@code 0x7F}), and to strings containing such characters. - * - *

ASCII utilities also exist in other classes of this package: - *

- * - * @author Craig Berry - * @author Gregory Kick - * @since 7.0 - */ -final class Ascii { - - private Ascii() { - } - - /** - * Returns a copy of the input string in which all {@linkplain #isUpperCase(char) uppercase ASCII - * characters} have been converted to lowercase. All other characters are copied without - * modification. - */ - public static String toLowerCase(String string) { - int length = string.length(); - for (int i = 0; i < length; i++) { - if (isUpperCase(string.charAt(i))) { - char[] chars = string.toCharArray(); - for (; i < length; i++) { - char c = chars[i]; - if (isUpperCase(c)) { - chars[i] = (char) (c ^ 0x20); - } - } - return String.valueOf(chars); - } - } - return string; - } - - /** - * Returns a copy of the input character sequence in which all {@linkplain #isUpperCase(char) - * uppercase ASCII characters} have been converted to lowercase. All other characters are copied - * without modification. - * - * @since 14.0 - */ - public static String toLowerCase(CharSequence chars) { - if (chars instanceof String) { - return toLowerCase((String) chars); - } - char[] newChars = new char[chars.length()]; - for (int i = 0; i < newChars.length; i++) { - newChars[i] = toLowerCase(chars.charAt(i)); - } - return String.valueOf(newChars); - } - - /** - * If the argument is an {@linkplain #isUpperCase(char) uppercase ASCII character} returns the - * lowercase equivalent. Otherwise returns the argument. - */ - public static char toLowerCase(char c) { - return isUpperCase(c) ? (char) (c ^ 0x20) : c; - } - - /** - * Indicates whether {@code c} is one of the twenty-six lowercase ASCII alphabetic characters - * between {@code 'a'} and {@code 'z'} inclusive. All others (including non-ASCII characters) - * return {@code false}. - */ - public static boolean isLowerCase(char c) { - // Note: This was benchmarked against the alternate expression "(char)(c - 'a') < 26" (Nov '13) - // and found to perform at least as well, or better. - return (c >= 'a') && (c <= 'z'); - } - - /** - * Indicates whether {@code c} is one of the twenty-six uppercase ASCII alphabetic characters - * between {@code 'A'} and {@code 'Z'} inclusive. All others (including non-ASCII characters) - * return {@code false}. - */ - public static boolean isUpperCase(char c) { - return (c >= 'A') && (c <= 'Z'); - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/CharMatcher.java b/common/http/src/main/java/io/helidon/common/http/CharMatcher.java deleted file mode 100644 index c28177c1207..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/CharMatcher.java +++ /dev/null @@ -1,783 +0,0 @@ -/* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import java.util.Arrays; -import java.util.BitSet; -import java.util.Objects; - -/** - * Extracted from Guava. - *

- * Determines a true or false value for any Java {@code char} value, just as {@link java.util.function.Predicate} does - * for any {@link Object}. Also offers basic text processing methods based on this function. - * Implementations are strongly encouraged to be side-effect-free and immutable. - * - *

Throughout the documentation of this class, the phrase "matching character" is used to mean - * "any {@code char} value {@code c} for which {@code this.matches(c)} returns {@code true}". - * - *

Warning: This class deals only with {@code char} values; it does not understand - * supplementary Unicode code points in the range {@code 0x10000} to {@code 0x10FFFF}. Such logical - * characters are encoded into a {@code String} using surrogate pairs, and a {@code CharMatcher} - * treats these just as two separate characters. - * - *

See the Guava User Guide article on {@code CharMatcher} - * . - * - * @author Kevin Bourrillion - */ -@SuppressWarnings({"checkstyle:VisibilityModifier", "checkstyle:RedundantModifier"}) -public abstract class CharMatcher { - - /** - * Constructor for use by subclasses. When subclassing, you may want to override - * {@code toString()} to provide a useful description. - */ - protected CharMatcher() { - } - - /** - * Determines whether a character is ASCII, meaning that its code point is less than 128. - * - * @return a CharMatcher instance that matches ASCII characters - */ - public static CharMatcher ascii() { - return Ascii.INSTANCE; - } - - /** - * Returns a {@code char} matcher that matches any character except the one - * specified. - * - *

- * To negate another {@code CharMatcher}, use {@link #negate()}. - * - * @param match the character that should not match - * @return a CharMatcher instance that matches any character except the one - * specified - */ - public static CharMatcher isNot(final char match) { - return new IsNot(match); - } - - /** - * Matches any character. - * - * @return a CharMatcher that matches any character - */ - public static CharMatcher any() { - return Any.INSTANCE; - } - - /** - * Matches no character. - * - * @return a CharMatcher that matches no character - */ - public static CharMatcher none() { - return None.INSTANCE; - } - - /** - * Determines whether a character is an ISO control character as specified by - * {@link Character#isISOControl(char)}. - * - * @return a CharMatcher that matches ISO control character - */ - public static CharMatcher javaIsoControl() { - return JavaIsoControl.INSTANCE; - } - - /** - * Returns a {@code char} matcher that matches only one specified character. - * @param match the character that should match - * @return a CharMatcher that matches the one specified character - */ - public static CharMatcher is(final char match) { - return new Is(match); - } - - private static CharMatcher.IsEither isEither(char c1, char c2) { - return new CharMatcher.IsEither(c1, c2); - } - - /** - * Returns a {@code char} matcher that matches any character not present in the given character - * sequence. - * @param sequence all the characters that should not be matched - * @return a CharMatcher that matches any character not present in the given sequence - */ - public static CharMatcher noneOf(CharSequence sequence) { - return anyOf(sequence).negate(); - } - - /** - * Returns a {@code char} matcher that matches any character present in the given character - * sequence. - * @param sequence all the characters that should be matched - * @return a CharMatcher that matches any character present in the given sequence - */ - public static CharMatcher anyOf(final CharSequence sequence) { - switch (sequence.length()) { - case 0: - return none(); - case 1: - return is(sequence.charAt(0)); - case 2: - return isEither(sequence.charAt(0), sequence.charAt(1)); - default: - // TODO(lowasser): is it potentially worth just going ahead and building a precomputed - // matcher? - return new AnyOf(sequence); - } - } - - /** - * Returns the Java Unicode escape sequence for the given character, in the form "\u12AB" where - * "12AB" is the four hexadecimal digits representing the 16 bits of the UTF-16 character. - */ - private static String showCharacter(char c) { - String hex = "0123456789ABCDEF"; - char[] tmp = {'\\', 'u', '\0', '\0', '\0', '\0'}; - for (int i = 0; i < 4; i++) { - tmp[5 - i] = hex.charAt(c & 0xF); - c = (char) (c >> 4); - } - return String.copyValueOf(tmp); - } - - /** - * Determines a true or false value for the given character. - * - * @param c the character to match - * @return {@code true} if this {@code CharMatcher} instance matches the - * given character, {@code false} otherwise - */ - public abstract boolean matches(char c); - - /** - * Returns a matcher that matches any character not matched by this matcher. - * - * @return new {@code CharMatcher} instance representing the logical - * negation of this instance - */ - public CharMatcher negate() { - return new Negated(this); - } - - /** - * Returns a matcher that matches any character matched by both this matcher and {@code other}. - * @param other the other instance - * @return new {@code CharMatcher} instance representing the logical - * and of this instance and the {@code other} instance - */ - public CharMatcher and(CharMatcher other) { - return new And(this, other); - } - - /** - * Returns a matcher that matches any character matched by either this matcher or {@code other}. - * @param other the other instance - * @return new {@code CharMatcher} instance representing the logical - * and of this instance and the {@code other} instance - */ - public CharMatcher or(CharMatcher other) { - return new Or(this, other); - } - - // Abstract methods - - /** - * Sets bits in {@code table} matched by this matcher. - */ - void setBits(BitSet table) { - for (int c = Character.MAX_VALUE; c >= Character.MIN_VALUE; c--) { - if (matches((char) c)) { - table.set(c); - } - } - } - - // Non-static factories - - /** - * Returns {@code true} if a character sequence contains at least one matching character. - * Equivalent to {@code !matchesNoneOf(sequence)}. - * - *

The default implementation iterates over the sequence, invoking {@link #matches} for each - * character, until this returns {@code true} or the end is reached. - * - * @param sequence the character sequence to examine, possibly empty - * @return {@code true} if this matcher matches at least one character in the sequence - * @since 8.0 - */ - public boolean matchesAnyOf(CharSequence sequence) { - return !matchesNoneOf(sequence); - } - - /** - * Returns {@code true} if a character sequence contains only matching characters. - * - *

The default implementation iterates over the sequence, invoking {@link #matches} for each - * character, until this returns {@code false} or the end is reached. - * - * @param sequence the character sequence to examine, possibly empty - * @return {@code true} if this matcher matches every character in the sequence, including when - * the sequence is empty - */ - public boolean matchesAllOf(CharSequence sequence) { - for (int i = sequence.length() - 1; i >= 0; i--) { - if (!matches(sequence.charAt(i))) { - return false; - } - } - return true; - } - - /** - * Returns {@code true} if a character sequence contains no matching characters. Equivalent to - * {@code !matchesAnyOf(sequence)}. - * - *

The default implementation iterates over the sequence, invoking {@link #matches} for each - * character, until this returns {@code true} or the end is reached. - * - * @param sequence the character sequence to examine, possibly empty - * @return {@code true} if this matcher matches no characters in the sequence, including when - * the sequence is empty - */ - public boolean matchesNoneOf(CharSequence sequence) { - return indexIn(sequence) == -1; - } - - /** - * Returns the index of the first matching character in a character sequence, or {@code -1} if no - * matching character is present. - * - *

The default implementation iterates over the sequence in forward order calling - * {@link #matches} for each character. - * - * @param sequence the character sequence to examine from the beginning - * @return an index, or {@code -1} if no character matches - */ - public int indexIn(CharSequence sequence) { - return indexIn(sequence, 0); - } - - /** - * Returns the index of the first matching character in a character sequence, starting from a - * given position, or {@code -1} if no character matches after that position. - * - *

The default implementation iterates over the sequence in forward order, beginning at {@code - * start}, calling {@link #matches} for each character. - * - * @param sequence the character sequence to examine - * @param start the first index to examine; must be nonnegative and no greater than {@code - * sequence.length()} - * @return the index of the first matching character, guaranteed to be no less than {@code start}, - * or {@code -1} if no character matches - * @throws IndexOutOfBoundsException if start is negative or greater than {@code - * sequence.length()} - */ - public int indexIn(CharSequence sequence, int start) { - int length = sequence.length(); - Preconditions.checkPositionIndex(start, length); - for (int i = start; i < length; i++) { - if (matches(sequence.charAt(i))) { - return i; - } - } - return -1; - } - - /** - * Returns the number of matching characters found in a character sequence. - * @param sequence sequence to count the number of matching characters - * @return count of matching characters - */ - public int countIn(CharSequence sequence) { - int count = 0; - for (int i = 0; i < sequence.length(); i++) { - if (matches(sequence.charAt(i))) { - count++; - } - } - return count; - } - - /** - * Implementation of {@link #ascii()}. - */ - private static final class Ascii extends NamedFastMatcher { - - static final Ascii INSTANCE = new Ascii(); - - Ascii() { - super("CharMatcher.ascii()"); - } - - @Override - public boolean matches(char c) { - return c <= '\u007f'; - } - } - - /** - * {@link FastMatcher} which overrides {@code toString()} with a custom name. - */ - abstract static class NamedFastMatcher extends FastMatcher { - - private final String description; - - NamedFastMatcher(String description) { - this.description = Objects.requireNonNull(description); - } - - @Override - public final String toString() { - return description; - } - } - - /** - * A matcher for which pre-computation will not yield any significant benefit. - */ - abstract static class FastMatcher extends CharMatcher { - - @Override - public CharMatcher negate() { - return new NegatedFastMatcher(this); - } - } - - /** - * Negation of a {@link FastMatcher}. - */ - static class NegatedFastMatcher extends Negated { - - NegatedFastMatcher(CharMatcher original) { - super(original); - } - } - - /** - * Implementation of {@link #javaIsoControl()}. - */ - private static final class JavaIsoControl extends NamedFastMatcher { - - static final JavaIsoControl INSTANCE = new JavaIsoControl(); - - private JavaIsoControl() { - super("CharMatcher.javaIsoControl()"); - } - - @Override - public boolean matches(char c) { - return c <= '\u001f' || (c >= '\u007f' && c <= '\u009f'); - } - } - - // Text processing routines - - /** - * Implementation of {@link #negate()}. - */ - private static class Negated extends CharMatcher { - - final CharMatcher original; - - Negated(CharMatcher original) { - this.original = Objects.requireNonNull(original); - } - - @Override - public boolean matches(char c) { - return !original.matches(c); - } - - @Override - public boolean matchesAllOf(CharSequence sequence) { - return original.matchesNoneOf(sequence); - } - - @Override - public boolean matchesNoneOf(CharSequence sequence) { - return original.matchesAllOf(sequence); - } - - @Override - public int countIn(CharSequence sequence) { - return sequence.length() - original.countIn(sequence); - } - - @Override - void setBits(BitSet table) { - BitSet tmp = new BitSet(); - original.setBits(tmp); - tmp.flip(Character.MIN_VALUE, Character.MAX_VALUE + 1); - table.or(tmp); - } - - @Override - public CharMatcher negate() { - return original; - } - - @Override - public String toString() { - return original + ".negate()"; - } - } - - /** - * Implementation of {@link #and(CharMatcher)}. - */ - private static final class And extends CharMatcher { - - final CharMatcher first; - final CharMatcher second; - - And(CharMatcher a, CharMatcher b) { - first = Objects.requireNonNull(a); - second = Objects.requireNonNull(b); - } - - @Override - public boolean matches(char c) { - return first.matches(c) && second.matches(c); - } - - @Override - void setBits(BitSet table) { - BitSet tmp1 = new BitSet(); - first.setBits(tmp1); - BitSet tmp2 = new BitSet(); - second.setBits(tmp2); - tmp1.and(tmp2); - table.or(tmp1); - } - - @Override - public String toString() { - return "CharMatcher.and(" + first + ", " + second + ")"; - } - } - - /** - * Implementation of {@link #or(CharMatcher)}. - */ - private static final class Or extends CharMatcher { - - final CharMatcher first; - final CharMatcher second; - - Or(CharMatcher a, CharMatcher b) { - first = Objects.requireNonNull(a); - second = Objects.requireNonNull(b); - } - - @Override - void setBits(BitSet table) { - first.setBits(table); - second.setBits(table); - } - - @Override - public boolean matches(char c) { - return first.matches(c) || second.matches(c); - } - - @Override - public String toString() { - return "CharMatcher.or(" + first + ", " + second + ")"; - } - } - - /** - * Implementation of {@link #isNot(char)}. - */ - private static final class IsNot extends FastMatcher { - - private final char match; - - IsNot(char match) { - this.match = match; - } - - @Override - public boolean matches(char c) { - return c != match; - } - - @Override - public CharMatcher and(CharMatcher other) { - return other.matches(match) ? super.and(other) : other; - } - - @Override - public CharMatcher or(CharMatcher other) { - return other.matches(match) ? any() : this; - } - - @Override - void setBits(BitSet table) { - table.set(0, match); - table.set(match + 1, Character.MAX_VALUE + 1); - } - - @Override - public CharMatcher negate() { - return is(match); - } - - @Override - public String toString() { - return "CharMatcher.isNot('" + showCharacter(match) + "')"; - } - } - - /** - * Implementation of {@link #anyOf(CharSequence)} for three or more characters. - */ - private static final class AnyOf extends CharMatcher { - - private final char[] chars; - - public AnyOf(CharSequence chars) { - this.chars = chars.toString().toCharArray(); - Arrays.sort(this.chars); - } - - @Override - public boolean matches(char c) { - return Arrays.binarySearch(chars, c) >= 0; - } - - @Override - void setBits(BitSet table) { - for (char c : chars) { - table.set(c); - } - } - - @Override - public String toString() { - StringBuilder description = new StringBuilder("CharMatcher.anyOf(\""); - for (char c : chars) { - description.append(showCharacter(c)); - } - description.append("\")"); - return description.toString(); - } - } - - /** - * Implementation of {@link #is(char)}. - */ - private static final class Is extends FastMatcher { - - private final char match; - - Is(char match) { - this.match = match; - } - - @Override - public boolean matches(char c) { - return c == match; - } - - @Override - public CharMatcher and(CharMatcher other) { - return other.matches(match) ? this : none(); - } - - @Override - public CharMatcher or(CharMatcher other) { - return other.matches(match) ? other : super.or(other); - } - - @Override - public CharMatcher negate() { - return isNot(match); - } - - @Override - void setBits(BitSet table) { - table.set(match); - } - - @Override - public String toString() { - return "CharMatcher.is('" + showCharacter(match) + "')"; - } - } - - /** - * Implementation of {@link #any()}. - */ - private static final class Any extends NamedFastMatcher { - - static final Any INSTANCE = new Any(); - - private Any() { - super("CharMatcher.any()"); - } - - @Override - public boolean matches(char c) { - return true; - } - - @Override - public int indexIn(CharSequence sequence) { - return (sequence.length() == 0) ? -1 : 0; - } - - @Override - public int indexIn(CharSequence sequence, int start) { - int length = sequence.length(); - Preconditions.checkPositionIndex(start, length); - return (start == length) ? -1 : start; - } - - @Override - public boolean matchesAllOf(CharSequence sequence) { - Objects.requireNonNull(sequence); - return true; - } - - @Override - public boolean matchesNoneOf(CharSequence sequence) { - return sequence.length() == 0; - } - - @Override - public int countIn(CharSequence sequence) { - return sequence.length(); - } - - @Override - public CharMatcher and(CharMatcher other) { - return Objects.requireNonNull(other); - } - - @Override - public CharMatcher or(CharMatcher other) { - Objects.requireNonNull(other); - return this; - } - - @Override - public CharMatcher negate() { - return none(); - } - } - - /** - * Implementation of {@link #none()}. - */ - private static final class None extends NamedFastMatcher { - - static final None INSTANCE = new None(); - - private None() { - super("CharMatcher.none()"); - } - - @Override - public boolean matches(char c) { - return false; - } - - @Override - public int indexIn(CharSequence sequence) { - Objects.requireNonNull(sequence); - return -1; - } - - @Override - public int indexIn(CharSequence sequence, int start) { - int length = sequence.length(); - Preconditions.checkPositionIndex(start, length); - return -1; - } - - @Override - public boolean matchesAllOf(CharSequence sequence) { - return sequence.length() == 0; - } - - @Override - public boolean matchesNoneOf(CharSequence sequence) { - Objects.requireNonNull(sequence); - return true; - } - - @Override - public int countIn(CharSequence sequence) { - Objects.requireNonNull(sequence); - return 0; - } - - @Override - public CharMatcher and(CharMatcher other) { - Objects.requireNonNull(other); - return this; - } - - @Override - public CharMatcher or(CharMatcher other) { - return Objects.requireNonNull(other); - } - - @Override - public CharMatcher negate() { - return any(); - } - } - - /** - * Implementation of {@link #anyOf(CharSequence)} for exactly two characters. - */ - private static final class IsEither extends FastMatcher { - - private final char match1; - private final char match2; - - IsEither(char match1, char match2) { - this.match1 = match1; - this.match2 = match2; - } - - @Override - public boolean matches(char c) { - return c == match1 || c == match2; - } - - @Override - void setBits(BitSet table) { - table.set(match1); - table.set(match2); - } - - @Override - public String toString() { - return "CharMatcher.anyOf(\"" + showCharacter(match1) + showCharacter(match2) + "\")"; - } - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/Content.java b/common/http/src/main/java/io/helidon/common/http/Content.java deleted file mode 100644 index 95dcfb7f85b..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/Content.java +++ /dev/null @@ -1,144 +0,0 @@ -/* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import java.util.concurrent.Executor; -import java.util.concurrent.Flow; -import java.util.function.Consumer; -import java.util.function.Function; -import java.util.function.Predicate; - -import io.helidon.common.reactive.Multi; -import io.helidon.common.reactive.Single; - -/** - * Represents an HTTP entity as a {@link Multi multi} of {@link DataChunk chunks} with specific - * features. - *

Default publisher contract

- * Default publisher accepts only single subscriber. Other subscribers receives - * {@link Flow.Subscriber#onError(Throwable) onError()}. - *

- * {@link DataChunk} provided by {@link Flow.Subscriber#onNext(Object) onNext()} method must be consumed in this - * method call. Buffer can be reused by network infrastructure as soon as {@code onNext()} method returns. - * This behavior can be inconvenient yet it helps to provide excellent performance. - * - *

Publisher Overwrite.

- * It is possible to modify contract of the original publisher by registration of a new publisher using - * {@link #registerFilter(Function)} method. It can be used to wrap or replace previously registered (or default) publisher. - * - *

Entity Readers

- * It is possible to register function to convert publisher to {@link io.helidon.common.reactive.Single} of a single entity using - * {@link #registerReader(Class, Reader)} or {@link #registerReader(Predicate, Reader)} methods. It - * is then possible to use {@link #as(Class)} method to obtain such entity. - * @deprecated use {@code io.helidon.media.common.MessageBodyReadableContent} instead - */ -@Deprecated(since = "2.0.0") -public interface Content extends Multi { - /** - * If possible, adds the given Subscriber to this publisher. This publisher is effectively - * either the original publisher - * or the last publisher registered by the method {@link #registerFilter(Function)}. - *

- * Note that the original publisher allows only a single subscriber and requires the passed - * {@link DataChunk} in the {@link Flow.Subscriber#onNext(Object)} call - * to be consumed before the method completes as specified by the {@link Content Default Publisher Contract}. - * - * @param subscriber the subscriber - * @throws NullPointerException if subscriber is null - */ - @Override - void subscribe(Flow.Subscriber subscriber); - - /** - * Registers a filter that allows a control of the original publisher. - *

- * The provided function is evaluated upon calling either of {@link #subscribe(Flow.Subscriber)} - * or {@link #as(Class)}. - * The first evaluation of the function transforms the original publisher to a new publisher. - * Any subsequent evaluation receives the publisher transformed by the last previously - * registered filter. - * It is up to the implementor of the given function to respect the contract of both the original - * publisher and the previously registered ones. - * - * @param function a function that transforms a given publisher (that is either the original - * publisher or the publisher transformed by the last previously registered filter). - * @deprecated since 2.0.0, use {@code io.helidon.media.common.MessageBodyReaderContext.registerFilter} - */ - @Deprecated(since = "2.0.0") - void registerFilter(Function, Flow.Publisher> function); - - /** - * Registers a reader for a later use with an appropriate {@link #as(Class)} method call. - *

- * The reader must transform the published byte buffers into a completion stage of the - * requested type. - *

- * Upon calling {@link #as(Class)} a matching reader is searched in the same order as the - * readers were registered. If no matching reader is found, or when the function throws - * an exception, the resulting completion stage ends exceptionally. - * - * @param type the requested type the completion stage is be associated with. - * @param reader the reader as a function that transforms a publisher into completion stage. - * If an exception is thrown, the resulting completion stage of - * {@link #as(Class)} method call ends exceptionally. - * @param the requested type - * @deprecated since 2.0.0, use {@code io.helidon.media.common.MessageBodyReaderContext.registerReader} - */ - @Deprecated(since = "2.0.0") - void registerReader(Class type, Reader reader); - - /** - * Registers a reader for a later use with an appropriate {@link #as(Class)} method call. - *

- * The reader must transform the published byte buffers into a completion stage of the - * requested type. - *

- * Upon calling {@link #as(Class)} a matching reader is searched in the same order as the - * readers were registered. If no matching reader is found or when the predicate throws - * an exception, or when the function throws an exception, the resulting completion stage - * ends exceptionally. - * - * @param predicate the predicate that determines whether the registered reader can handle - * the requested type. If an exception is thrown, the resulting completion - * stage of {@link #as(Class)} method call ends exceptionally. - * @param reader the reader as a function that transforms a publisher into completion stage. - * If an exception is thrown, the resulting completion stage of - * {@link #as(Class)} method call ends exceptionally. - * @param the requested type - * @deprecated since 2.0.0, use {@code io.helidon.media.common.MessageBodyReaderContext.registerReader} - */ - @Deprecated(since = "2.0.0") - void registerReader(Predicate> predicate, Reader reader); - - /** - * Consumes and converts the request content into a completion stage of the requested type. - *

- * The conversion requires an appropriate reader to be already registered - * (see {@link #registerReader(Predicate, Reader)}). If no such reader is found, the - * resulting completion stage ends exceptionally. - *

- * Any callback related to the returned value, should not be blocking. Blocking operation could cause deadlock. - * If you need to use blocking API such as {@link java.io.InputStream} it is highly recommended to do so out of - * the scope of reactive chain, or to use methods like - * {@link java.util.concurrent.CompletionStage#thenAcceptAsync(Consumer, Executor)}. - * - * @param the requested type - * @param type the requested type class - * @return a completion stage of the requested type - */ - Single as(Class type); -} diff --git a/common/http/src/main/java/io/helidon/common/http/FormBuilder.java b/common/http/src/main/java/io/helidon/common/http/FormBuilder.java deleted file mode 100644 index 6fff0381bcf..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/FormBuilder.java +++ /dev/null @@ -1,37 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.common.http; - -import io.helidon.common.Builder; - -/** - * Form builder interface. - * - * @param type of the builder - * @param type which the builder builds - */ -public interface FormBuilder extends Builder, T> { - - /** - * Add a new values to specific content disposition name. - * - * @param name param name - * @param values param values - * @return updated builder instance - */ - B add(String name, String... values); - -} diff --git a/common/http/src/main/java/io/helidon/common/http/FormParams.java b/common/http/src/main/java/io/helidon/common/http/FormParams.java deleted file mode 100644 index df87cc77581..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/FormParams.java +++ /dev/null @@ -1,83 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.common.http; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; - -/** - * Provides access to any form parameters present in the request entity. - */ -public interface FormParams extends Parameters { - - /** - * Creates a new {@code FormParams} instance using the provided assignment string and media - * type. - * - * @param paramAssignments String containing the parameter assignments, formatted according - * to the media type specified ({@literal &} separator for - * URL-encoded, NL for text/plain) - * @param mediaType MediaType for which the parameter conversion is occurring - * @return the new {@code FormParams} instance - * @deprecated use {@link FormParams#builder()} instead or register {@code io.helidon.media.common.FormParamsBodyReader} - */ - @Deprecated(since = "2.0.2") - static FormParams create(String paramAssignments, MediaType mediaType) { - return FormParamsImpl.create(paramAssignments, mediaType); - } - - /** - * Creates a new {@link Builder} of {@code FormParams} instance. - * - * @return builder instance - */ - static Builder builder() { - return new Builder(); - } - - /** - * Builder of a new {@link FormParams} instance. - */ - class Builder implements FormBuilder { - - private final Map> params = new LinkedHashMap<>(); - - private Builder() { - } - - @Override - public FormParams build() { - return new FormParamsImpl(this); - } - - @Override - public Builder add(String name, String... values) { - Objects.requireNonNull(name); - params.computeIfAbsent(name, k -> new ArrayList<>()).addAll(Arrays.asList(values)); - return this; - } - - Map> params() { - return params; - } - - } - -} diff --git a/common/http/src/main/java/io/helidon/common/http/FormParamsImpl.java b/common/http/src/main/java/io/helidon/common/http/FormParamsImpl.java deleted file mode 100644 index 0aa3205bc25..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/FormParamsImpl.java +++ /dev/null @@ -1,79 +0,0 @@ -/* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.common.http; - -import java.net.URLDecoder; -import java.nio.charset.Charset; -import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.function.Function; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - -/** - * Implementation of the {@link FormParams} interface. - */ -class FormParamsImpl extends ReadOnlyParameters implements FormParams { - - /* - * For form params represented in text/plain (uncommon), newlines appear between name=value - * assignments. When urlencoded, ampersands separate the name=value assignments. - */ - private static final Map PATTERNS = Map.of( - MediaType.APPLICATION_FORM_URLENCODED, preparePattern("&"), - MediaType.TEXT_PLAIN, preparePattern("\n")); - - private FormParamsImpl(Map> params) { - super(params); - } - - FormParamsImpl(FormParams.Builder builder) { - super(builder.params()); - } - - private static Pattern preparePattern(String assignmentSeparator) { - return Pattern.compile(String.format("([^=]+)=([^%1$s]+)%1$s?", assignmentSeparator)); - } - - static FormParams create(String paramAssignments, MediaType mediaType) { - Charset charset = mediaType.charset().map(Charset::forName).orElse(StandardCharsets.UTF_8); - Function decoder = decoder(mediaType, charset); - final Map> params = new HashMap<>(); - Matcher m = PATTERNS.get(mediaType).matcher(paramAssignments); - while (m.find()) { - final String key = decoder.apply(m.group(1)); - final String value = m.group(2); - if (value == null) { - params.computeIfAbsent(key, k -> new ArrayList<>()); - } else { - params.computeIfAbsent(key, k -> new ArrayList<>()).add(decoder.apply(value)); - } - } - return new FormParamsImpl(params); - } - - private static Function decoder(MediaType mediaType, Charset charset) { - if (mediaType == MediaType.TEXT_PLAIN) { - return (s) -> s; - } else { - return (s) -> URLDecoder.decode(s, charset); - } - } - -} diff --git a/common/http/src/main/java/io/helidon/common/http/HashParameters.java b/common/http/src/main/java/io/helidon/common/http/HashParameters.java deleted file mode 100644 index f3bd2fc78c3..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/HashParameters.java +++ /dev/null @@ -1,389 +0,0 @@ -/* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.TreeMap; -import java.util.concurrent.ConcurrentMap; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.function.Function; - -/** - * A {@link ConcurrentSkipListMap} based {@link Parameters} implementation with - * case-insensitive keys and immutable {@link List} of values that needs to be copied on each write. - */ -public class HashParameters implements Parameters { - - private static final List EMPTY_STRING_LIST = Collections.emptyList(); - - private final ConcurrentMap> content; - - /** - * Creates a new instance. - */ - protected HashParameters() { - this((Parameters) null); - } - - /** - * Creates a new instance from provided data. - * Initial data are copied. - * - * @param initialContent initial content. - */ - protected HashParameters(Map> initialContent) { - if (initialContent == null) { - content = new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER); - } else { - content = new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER); - for (Map.Entry> entry : initialContent.entrySet()) { - content.compute( - entry.getKey(), - (key, values) -> { - if (values == null) { - return Collections.unmodifiableList(new ArrayList<>(entry.getValue())); - } else { - values.addAll(entry.getValue()); - return values; - - } - } - ); - } - } - } - - /** - * Creates a new instance from provided data. - * Initial data is copied. - * - * @param initialContent initial content. - */ - protected HashParameters(Parameters initialContent) { - this(initialContent == null ? null : initialContent.toMap()); - } - - /** - * Creates a new empty instance {@link HashParameters}. - * - * @return a new instance of {@link HashParameters}. - */ - public static HashParameters create() { - return new HashParameters(); - } - - /** - * Creates a new instance {@link HashParameters} from provided data. Initial data is copied. - * - * @param initialContent initial content. - * @return a new instance of {@link HashParameters} initialized with the given content. - */ - public static HashParameters create(Map> initialContent) { - return new HashParameters(initialContent); - } - - /** - * Creates a new instance {@link HashParameters} from provided data. Initial data is copied. - * - * @param initialContent initial content. - * @return a new instance of {@link HashParameters} initialized with the given content. - */ - public static HashParameters create(Parameters initialContent) { - return new HashParameters(initialContent); - } - - /** - * Creates new instance of {@link HashParameters} as a concatenation of provided parameters. - * Values for keys found across the provided parameters are "concatenated" into a {@link List} entry for their respective key - * in the created {@link HashParameters} instance. - * - * @param parameters parameters to concatenate. - * @return a new instance of {@link HashParameters} that represents the concatenation of the provided parameters. - */ - public static HashParameters concat(Parameters... parameters) { - if (parameters == null || parameters.length == 0) { - return new HashParameters(); - } - List>> prms = new ArrayList<>(parameters.length); - for (Parameters p : parameters) { - if (p != null) { - prms.add(p.toMap()); - } - } - return concat(prms); - } - - /** - * Creates new instance of {@link HashParameters} as a concatenation of provided parameters. - * Values for keys found across the provided parameters are "concatenated" into a {@link List} entry for their respective key - * in the created {@link HashParameters} instance. - * - * @param parameters parameters to concatenate. - * @return a new instance of {@link HashParameters} that represents the concatenation of the provided parameters. - */ - public static HashParameters concat(Iterable parameters) { - ArrayList>> prms = new ArrayList<>(); - for (Parameters p : parameters) { - if (p != null) { - prms.add(p.toMap()); - } - } - return concat(prms); - } - - private static HashParameters concat(List>> prms) { - if (prms.isEmpty()) { - return new HashParameters(); - } - if (prms.size() == 1) { - return new HashParameters(prms.get(0)); - } - - Map> composer = new HashMap<>(); - for (Map> prm : prms) { - for (Map.Entry> entry : prm.entrySet()) { - List strings = composer.computeIfAbsent(entry.getKey(), k -> new ArrayList<>(entry.getValue().size())); - strings.addAll(entry.getValue()); - } - } - return new HashParameters(composer); - } - - private List internalListCopy(String... values) { - return Optional.ofNullable(values) - .map(Arrays::asList) - .filter(l -> !l.isEmpty()) - .map(Collections::unmodifiableList) - .orElse(null); - } - - private List internalListCopy(Iterable values) { - if (values == null) { - return null; - } else { - List result; - if (values instanceof Collection) { - result = new ArrayList<>((Collection) values); - } else { - result = new ArrayList<>(); - for (String value : values) { - result.add(value); - } - } - if (result.isEmpty()) { - return null; - } else { - return Collections.unmodifiableList(result); - } - } - } - - @Override - public Optional first(String name) { - Objects.requireNonNull(name, "Parameter 'name' is null!"); - return content.getOrDefault(name, EMPTY_STRING_LIST).stream().findFirst(); - } - - @Override - public List all(String name) { - Objects.requireNonNull(name, "Parameter 'name' is null!"); - return content.getOrDefault(name, EMPTY_STRING_LIST); - } - - @Override - public List put(String key, String... values) { - List vs = internalListCopy(values); - List result; - if (vs == null) { - result = content.remove(key); - } else { - result = content.put(key, vs); - } - return result == null ? Collections.emptyList() : result; - } - - @Override - public List put(String key, Iterable values) { - List vs = internalListCopy(values); - List result; - if (vs == null) { - result = content.remove(key); - } else { - result = content.put(key, vs); - } - return result == null ? Collections.emptyList() : result; - } - - @Override - public List putIfAbsent(String key, String... values) { - List vls = internalListCopy(values); - List result; - if (vls != null) { - result = content.putIfAbsent(key, vls); - } else { - result = content.get(key); - } - return result == null ? Collections.emptyList() : result; - } - - @Override - public List putIfAbsent(String key, Iterable values) { - List vls = internalListCopy(values); - List result; - if (vls != null) { - result = content.putIfAbsent(key, vls); - } else { - result = content.get(key); - } - return result == null ? Collections.emptyList() : result; - } - - @Override - public List computeIfAbsent(String key, Function> values) { - List result = content.computeIfAbsent(key, k -> internalListCopy(values.apply(k))); - return result == null ? Collections.emptyList() : result; - } - - @Override - public List computeSingleIfAbsent(String key, Function value) { - List result = content.computeIfAbsent(key, k -> { - String v = value.apply(k); - if (v == null) { - return null; - } else { - return Collections.singletonList(v); - } - }); - return result == null ? Collections.emptyList() : result; - } - - @Override - public HashParameters putAll(Parameters parameters) { - if (parameters == null) { - return this; - } - - for (Map.Entry> entry : parameters.toMap().entrySet()) { - List values = entry.getValue(); - if (values != null && !values.isEmpty()) { - content.put(entry.getKey(), Collections.unmodifiableList(values)); - } - } - return this; - } - - @Override - public HashParameters add(String key, String... values) { - Objects.requireNonNull(key, "Parameter 'key' is null!"); - if (values == null || values.length == 0) { - // do not necessarily create an entry in the map, simply immediately return - return this; - } - - content.compute(key, (s, list) -> { - if (list == null) { - return Collections.unmodifiableList(new ArrayList<>(Arrays.asList(values))); - } else { - ArrayList newValues = new ArrayList<>(list.size() + values.length); - newValues.addAll(list); - newValues.addAll(Arrays.asList(values)); - return Collections.unmodifiableList(newValues); - } - }); - return this; - } - - @Override - public HashParameters add(String key, Iterable values) { - Objects.requireNonNull(key, "Parameter 'key' is null!"); - List vls = internalListCopy(values); - if (vls == null) { - // do not necessarily create an entry in the map, simply immediately return - return this; - } - - content.compute(key, (s, list) -> { - if (list == null) { - return Collections.unmodifiableList(vls); - } else { - ArrayList newValues = new ArrayList<>(list.size() + vls.size()); - newValues.addAll(list); - newValues.addAll(vls); - return Collections.unmodifiableList(newValues); - } - }); - return this; - } - - @Override - public HashParameters addAll(Parameters parameters) { - if (parameters == null) { - return this; - } - Map> map = parameters.toMap(); - for (Map.Entry> entry : map.entrySet()) { - add(entry.getKey(), entry.getValue()); - } - return this; - } - - @Override - public List remove(String key) { - List result = content.remove(key); - return result == null ? Collections.emptyList() : result; - } - - @Override - public Map> toMap() { - // deep copy - Map> result = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - for (Map.Entry> entry : content.entrySet()) { - result.put(entry.getKey(), new ArrayList<>(entry.getValue())); - } - return result; - } - - @Override - public String toString() { - return content.toString(); - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof HashParameters)) { - return false; - } - HashParameters that = (HashParameters) o; - return content.equals(that.content); - } - - @Override - public int hashCode() { - return content.hashCode(); - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/Headers.java b/common/http/src/main/java/io/helidon/common/http/Headers.java index d8050102e51..3f5233a18d0 100644 --- a/common/http/src/main/java/io/helidon/common/http/Headers.java +++ b/common/http/src/main/java/io/helidon/common/http/Headers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,29 +16,82 @@ package io.helidon.common.http; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Optional; +import java.util.OptionalLong; +import java.util.function.Supplier; import java.util.stream.Collectors; +import io.helidon.common.media.type.MediaType; + /** - * Extends {@link Parameters} interface by adding methods convenient for HTTP headers. + * View of HTTP Headers. + * This API is designed to support both HTTP/1 and HTTP/2. + * Note that HTTP/2 has all headers lower case (mandatory), while HTTP/1 headers are compared ignoring + * case. + * When you configure headers to be sent using HTTP/2, all names will be lowercase. + * When you configure headers to be sent using HTTP/1, names will be sent as configured. + * When you receive headers, the stored values (as can be obtained by {@link io.helidon.common.http.Http.HeaderValue#name()}) + * will be as sent on the transport. These value will be available using any cased names (though performance may be worse + * if uppercase letters are used to obtain HTTP/2 headers). */ -public interface Headers extends Parameters { +public interface Headers extends Iterable { /** - * Returns an unmodifiable {@link List} of all header fields - each element represents a value of a single header field - * in the request. Consider to use {@link #value(String)} or {@link #values(String)} method instead. + * Returns an unmodifiable {@link java.util.List} of all header fields - each element represents a value of a single header + * field + * in the request. Consider to use {@link #value(io.helidon.common.http.Http.HeaderName)} + * or {@link #all(io.helidon.common.http.Http.HeaderName, java.util.function.Supplier)} method instead. *

* Always returns a List, which may be empty if the parameter is not present. * * @param headerName the header name * @return a {@code List} of values with zero or greater size * @throws NullPointerException if {@code headerName} is {@code null} - * @see #value(String) - * @see #values(String) + * @see #value(io.helidon.common.http.Http.HeaderName) + * @see #values(io.helidon.common.http.Http.HeaderName) + * @deprecated use {@link #all(io.helidon.common.http.Http.HeaderName, java.util.function.Supplier)} instead + */ + @Deprecated(forRemoval = true) + default List all(String headerName) { + return all(Http.Header.create(headerName), List::of); + } + + /** + * Get all values of a header. + * + * @param name name of the header + * @param defaultSupplier supplier to obtain default values if the header is not present + * @return list of header values + */ + List all(Http.HeaderName name, Supplier> defaultSupplier); + + /** + * Whether these headers contain a header with the provided name. + * + * @param name header name + * @return {@code true} if the header is defined + */ + boolean contains(Http.HeaderName name); + + /** + * Whether these headers contain a header with the provided name and value. + * + * @param value value of the header + * @return {@code true} if the header is defined + */ + boolean contains(Http.HeaderValue value); + + /** + * Get a header value. + * + * @param name name of the header + * @return value if present + * @throws java.util.NoSuchElementException in case the header is not present */ - @Override - List all(String headerName); + Http.HeaderValue get(Http.HeaderName name); /** * Returns a header value as a single {@link String} potentially concatenated using comma character @@ -58,20 +111,34 @@ public interface Headers extends Parameters { * @param headerName the header name * @return all header values concatenated using comma separator * @throws NullPointerException if {@code headerName} is {@code null} - * @see #all(String) - * @see #values(String) + * @see #all(io.helidon.common.http.Http.HeaderName, java.util.function.Supplier) + * @see #values(io.helidon.common.http.Http.HeaderName) */ - default Optional value(String headerName) { - List hdrs = all(headerName); - if (hdrs.isEmpty()) { - return Optional.empty(); - } else { - return Optional.of(hdrs.stream().collect(Collectors.joining(","))); + default Optional value(Http.HeaderName headerName) { + if (contains(headerName)) { + List hdrs = all(headerName, List::of); + return Optional.of(String.join(",", hdrs)); } + return Optional.empty(); } /** - * Returns an unmodifiable {@link List} of all comma separated header value parts - Such segmentation is NOT valid for + * Returns a first header value. + * + * @param headerName the header name + * @return the first value + * @throws NullPointerException if {@code headerName} is {@code null} + */ + default Optional first(Http.HeaderName headerName) { + if (contains(headerName)) { + return Optional.of(get(headerName).value()); + } + return Optional.empty(); + } + + /** + * Returns an unmodifiable {@link java.util.List} of all comma separated header value parts - Such segmentation is NOT + * valid for * all header semantics, however it is very common. Refer to actual header semantics standard/description before use. *

* Result is composed from all header fields with requested {@code headerName} where each header value is tokenized by @@ -83,11 +150,81 @@ default Optional value(String headerName) { * @return a {@code List} of values with zero or greater size, never {@code null} * @throws NullPointerException if {@code headerName} is {@code null} * @see #all(String) - * @see #value(String) + * @see #value(io.helidon.common.http.Http.HeaderName) */ - default List values(String headerName) { - return all(headerName).stream() + default List values(Http.HeaderName headerName) { + return all(headerName, List::of).stream() .flatMap(val -> Utils.tokenize(',', "\"", true, val).stream()) .collect(Collectors.toList()); } + + /** + * Content length if defined. + * + * @return content length or empty if not defined + * @see io.helidon.common.http.Http.Header#CONTENT_LENGTH + */ + default OptionalLong contentLength() { + if (contains(HeaderEnum.CONTENT_LENGTH)) { + return OptionalLong.of(get(HeaderEnum.CONTENT_LENGTH).value(long.class)); + } + return OptionalLong.empty(); + } + + /** + * Content type (if defined). + * + * @return content type, empty if content type is not present + * @see Http.Header#CONTENT_TYPE + */ + default Optional contentType() { + if (contains(HeaderEnum.CONTENT_TYPE)) { + return Optional.of(HttpMediaType.create(get(HeaderEnum.CONTENT_TYPE) + .value())); + } + return Optional.empty(); + } + + /** + * Number of headers in these headers. + * + * @return size of these headers + */ + int size(); + + /** + * Returns a list of acceptedTypes ({@link io.helidon.common.http.Http.Header#ACCEPT} header) content types in + * quality factor order. Never {@code null}. + * Returns an empty list by default. + * + * @return A list of acceptedTypes media types. + */ + List acceptedTypes(); + + /** + * Whether this media type is accepted by these headers. + * As this method is useful only for server request headers, it returns {@code true } by default. + * + * @param mediaType media type to test + * @return {@code true} if this media type would be accepted + */ + default boolean isAccepted(MediaType mediaType) { + return true; + } + + /** + * Creates a multivalued map from these headers. + * This is extremely inefficient and should not be used. + * + * @return map of headers + * @deprecated use other methods to handle headers, preferably using pull approach + */ + @Deprecated(forRemoval = true) + default Map> toMap() { + Map> headers = new HashMap<>(); + + forEach(it -> headers.put(it.name(), it.allValues())); + + return headers; + } } diff --git a/common/http/src/main/java/io/helidon/common/http/Http.java b/common/http/src/main/java/io/helidon/common/http/Http.java index 8c5558e0ed6..fc94bb5fd52 100644 --- a/common/http/src/main/java/io/helidon/common/http/Http.java +++ b/common/http/src/main/java/io/helidon/common/http/Http.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,17 +16,26 @@ package io.helidon.common.http; +import java.nio.charset.StandardCharsets; import java.time.LocalDate; import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; import java.time.format.DateTimeParseException; import java.time.format.SignStyle; +import java.util.Collection; import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.function.Predicate; + +import io.helidon.common.buffers.Ascii; +import io.helidon.common.buffers.BufferData; +import io.helidon.common.buffers.LazyString; import static java.time.temporal.ChronoField.DAY_OF_MONTH; import static java.time.temporal.ChronoField.DAY_OF_WEEK; @@ -47,683 +56,1081 @@ public final class Http { private Http() { } + /** + * Enumeration of supported HTTP protocol versions. + */ + public enum Version { + + /** + * HTTP version {@code HTTP/1.0}. + */ + V1_0("HTTP/1.0"), + + /** + * HTTP version {@code HTTP/1.1}. + */ + V1_1("HTTP/1.1"), + + /** + * HTTP version {@code HTTP/2.0}. + */ + V2_0("HTTP/2.0"); + + private final String value; + + Version(String value) { + this.value = value; + } + + /** + * Returns HTTP version for provided parameter. + * + * @param version HTTP version. + * @return Version instance. + * @throws NullPointerException if parameter {@code version} is null. + * @throws IllegalArgumentException if it is not provided version. + */ + public static Version create(String version) { + Objects.requireNonNull(version, "Version value is null!"); + for (Version v : Version.values()) { + if (version.equals(v.value)) { + return v; + } + } + throw new IllegalArgumentException("Unknown HTTP version: " + version + "!"); + } + + /** + * Returns {@code String} representation of this {@link io.helidon.common.http.Http.Version}. + * + * @return a string representation. + */ + public String value() { + return value; + } + } + + /** + * Interface representing an HTTP request method, all standard methods are in {@link io.helidon.common.http.Http.Method} + * enumeration. + *

+ * Although the constants are instances of this class, they can be compared using instance equality, as the only + * way to obtain an instance is through method {@link #create(String)}, which ensures the same instance is returned for + * known methods. + *

+ * Methods that are not known (e.g. there is no constant for them) must be compared using {@link #equals(Object)} as usual. + * + * @see io.helidon.common.http.Http.Method + */ + public static final class Method { + private static final String GET_STRING = "GET"; + /** + * The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI. + * If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the entity + * in the response and not the source text of the process, unless that text happens to be the output of the tryProcess. + */ + public static final Method GET = new Method(GET_STRING, true); + /** + * The POST method is used to request that the origin server acceptedTypes the entity enclosed in the request + * as a new subordinate of the resource identified by the Request-URI in the Request-Line. + * The actual function performed by the POST method is determined by the server and is usually dependent on the + * Request-URI. The posted entity is subordinate to that URI in the same way that a file is subordinate to a directory + * containing it, a news article is subordinate to a newsgroup to which it is posted, or a record is subordinate + * to a database. + */ + public static final Method POST = new Method("POST", true); + /** + * The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers + * to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing + * on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being + * defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. + * If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response. + * If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate + * successful completion of the request. If the resource could not be created or modified with the Request-URI, + * an appropriate error response SHOULD be given that reflects the nature of the problem. The recipient of the entity + * MUST NOT ignore any Content-* (e.g. Content-Range) headers that it does not understand or implement and MUST return + * a 501 (Not Implemented) response in such cases. + */ + public static final Method PUT = new Method("PUT", true); + /** + * The DELETE method requests that the origin server delete the resource identified by the Request-URI. + * This method MAY be overridden by human intervention (or other means) on the origin server. The client cannot + * be guaranteed that the operation has been carried out, even if the status code returned from the origin server + * indicates that the action has been completed successfully. However, the server SHOULD NOT indicate success unless, + * at the time the response is given, it intends to delete the resource or move it to an inaccessible location. + */ + public static final Method DELETE = new Method("DELETE", true); + /** + * The HEAD method is identical to {@link #GET} except that the server MUST NOT return a message-body in the response. + * The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information + * sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied + * by the request without transferring the entity-body itself. This method is often used for testing hypertext links + * for validity, accessibility, and recent modification. + */ + public static final Method HEAD = new Method("HEAD", true); + /** + * The OPTIONS method represents a request for information about the communication options available + * on the request/response chain identified by the Request-URI. This method allows the client to determine the options + * and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action + * or initiating a resource retrieval. + */ + public static final Method OPTIONS = new Method("OPTIONS", true); + /** + * The TRACE method is used to invoke a remote, application-layer loop- back of the request message. + * The final recipient of the request SHOULD reflect the message received back to the client as the entity-body + * of a 200 (OK) response. The final recipient is either the origin server or the first proxy or gateway to receive + * a Max-Forwards value of zero (0) in the request (see section 14.31). A TRACE request MUST NOT include an entity. + */ + public static final Method TRACE = new Method("TRACE", true); + /** + * The PATCH method as described in RFC 5789 is used to perform an update to an existing resource, where the request + * payload only has to contain the instructions on how to perform the update. This is in contrast to PUT which + * requires that the payload contains the new version of the resource. + * If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate + * successful completion of the request. + */ + public static final Method PATCH = new Method("PATCH", true); + + static { + // THIS MUST BE AFTER THE LAST CONSTANT + MethodHelper.methodsDone(); + } + + private final String name; + private final int length; + + private final boolean instance; + + private Method(String name, boolean instance) { + this.name = name; + this.length = name.length(); + this.instance = instance; + + if (instance) { + MethodHelper.add(this); + } + } + + /** + * Create new HTTP request method instance from the provided name. + *

+ * In case the method name is recognized as one of the {@link io.helidon.common.http.Http.Method standard HTTP methods}, + * the respective enumeration + * value is returned. + * + * @param name the method name. Must not be {@code null} or empty and must be a legal HTTP method name string. + * @return HTTP request method instance representing an HTTP method with the provided name. + * @throws IllegalArgumentException In case of illegal method name or in case the name is empty or {@code null}. + */ + public static Method create(String name) { + if (name.equals(GET_STRING)) { + return GET; + } + + String methodName = Ascii.toUpperCase(name); + + Method method = MethodHelper.byName(methodName); + if (method == null) { + // validate that it only contains characters allowed by a method + HttpToken.validate(methodName); + return new Method(methodName, false); + } + return method; + } + + + /** + * Create a predicate for the provided methods. + * + * @param methods methods to check against + * @return a predicate that will validate the method is one of the methods provided; if methods are empty, the predicate + * will always return {@code true} + */ + public static MethodPredicate predicate(Method... methods) { + return switch (methods.length) { + case 0 -> MethodPredicates.TruePredicate.get(); + case 1 -> methods[0].instance + ? new MethodPredicates.SingleMethodEnumPredicate(methods[0]) + : new MethodPredicates.SingleMethodPredicate(methods[0]); + default -> new MethodPredicates.MethodsPredicate(methods); + }; + } + + /** + * Create a predicate for the provided methods. + * + * @param methods methods to check against + * @return a predicate that will validate the method is one of the methods provided; if methods are empty, the predicate + * will always return {@code true} + */ + public static MethodPredicate predicate(Collection methods) { + switch (methods.size()) { + case 0: + return MethodPredicates.TruePredicate.get(); + case 1: + Method first = methods.iterator().next(); + return first.instance + ? new MethodPredicates.SingleMethodEnumPredicate(first) + : new MethodPredicates.SingleMethodPredicate(first); + + default: + return new MethodPredicates.MethodsPredicate(methods.toArray(new Method[0])); + } + } + + /** + * Name of the method (such as {@code GET} or {@code POST}). + * + * @return a method name. + * @deprecated use {@link #text()} instead, this method conflicts with enum + */ + @Deprecated + public String name() { + return text(); + } + + /** + * Name of the method (such as {@code GET} or {@code POST}). + * + * @return a method name. + */ + public String text() { + return name; + } + + /** + * Number of characters. + * + * @return number of characters of this method + */ + public int length() { + return length; + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Method method = (Method) o; + return name.equals(method.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } + } + + /** + * HTTP Method predicate. + */ + public interface MethodPredicate extends Predicate { + /** + * Methods accepted by this predicate, may be empty. + * + * @return set of methods accepted + */ + Set acceptedMethods(); + } + /** * Commonly used status codes defined by HTTP, see * HTTP/1.1 documentation * for the complete list. Additional status codes can be added by applications - * by creating an implementation of {@link ResponseStatus}. + * by call {@link #create(int)} or {@link #create(int, String)} with unkown status code, or with text + * that differs from the predefined status codes. *

- * Copied from JAX-RS. + * Although the constants are instances of this class, they can be compared using instance equality, as the only + * way to obtain an instance is through methods {@link #create(int)} {@link #create(int, String)}, which ensures + * the same instance is returned for known status codes and reason phrases. */ - public enum Status implements ResponseStatus { + public static class Status { /** * 100 Continue, * see HTTP/1.1 documentations. */ - CONTINUE_100(100, "Continue"), + public static final Status CONTINUE_100 = new Status(100, "Continue", true); /** * 101 Switching Protocols, * see HTTP/1.1 documentations. */ - SWITCHING_PROTOCOLS_101(101, "Switching Protocols"), + public static final Status SWITCHING_PROTOCOLS_101 = new Status(101, "Switching Protocols", true); /** * 200 OK, see HTTP/1.1 documentation. */ - OK_200(200, "OK"), + public static final Status OK_200 = new Status(200, "OK", true); /** * 201 Created, see HTTP/1.1 documentation. */ - CREATED_201(201, "Created"), + public static final Status CREATED_201 = new Status(201, "Created", true); /** * 202 Accepted, see HTTP/1.1 documentation * . */ - ACCEPTED_202(202, "Accepted"), + public static final Status ACCEPTED_202 = new Status(202, "Accepted", true); /** * 204 No Content, see * HTTP/1.1 documentation. */ - NO_CONTENT_204(204, "No Content"), + public static final Status NO_CONTENT_204 = new Status(204, "No Content", true); /** * 205 Reset Content, see * HTTP/1.1 documentation. * * @since 2.0 */ - RESET_CONTENT_205(205, "Reset Content"), + public static final Status RESET_CONTENT_205 = new Status(205, "Reset Content", true); /** * 206 Reset Content, see * HTTP/1.1 documentation. * * @since 2.0 */ - PARTIAL_CONTENT_206(206, "Partial Content"), + public static final Status PARTIAL_CONTENT_206 = new Status(206, "Partial Content", true); /** * 301 Moved Permanently, see * HTTP/1.1 documentation. */ - MOVED_PERMANENTLY_301(301, "Moved Permanently"), + public static final Status MOVED_PERMANENTLY_301 = new Status(301, "Moved Permanently", true); /** * 302 Found, see HTTP/1.1 documentation. * * @since 2.0 */ - FOUND_302(302, "Found"), + public static final Status FOUND_302 = new Status(302, "Found", true); /** * 303 See Other, see * HTTP/1.1 documentation. */ - SEE_OTHER_303(303, "See Other"), + public static final Status SEE_OTHER_303 = new Status(303, "See Other", true); /** * 304 Not Modified, see * HTTP/1.1 documentation. */ - NOT_MODIFIED_304(304, "Not Modified"), + public static final Status NOT_MODIFIED_304 = new Status(304, "Not Modified", true); /** * 305 Use Proxy, see * HTTP/1.1 documentation. * * @since 2.0 */ - USE_PROXY_305(305, "Use Proxy"), + public static final Status USE_PROXY_305 = new Status(305, "Use Proxy", true); /** * 307 Temporary Redirect, see * HTTP/1.1 documentation. */ - TEMPORARY_REDIRECT_307(307, "Temporary Redirect"), + public static final Status TEMPORARY_REDIRECT_307 = new Status(307, "Temporary Redirect", true); /** * 400 Bad Request, see * HTTP/1.1 documentation. */ - BAD_REQUEST_400(400, "Bad Request"), + public static final Status BAD_REQUEST_400 = new Status(400, "Bad Request", true); /** * 401 Unauthorized, see * HTTP/1.1 documentation. */ - UNAUTHORIZED_401(401, "Unauthorized"), + public static final Status UNAUTHORIZED_401 = new Status(401, "Unauthorized", true); /** * 402 Payment Required, see * HTTP/1.1 documentation. * * @since 2.0 */ - PAYMENT_REQUIRED_402(402, "Payment Required"), + public static final Status PAYMENT_REQUIRED_402 = new Status(402, "Payment Required", true); /** * 403 Forbidden, see * HTTP/1.1 documentation. */ - FORBIDDEN_403(403, "Forbidden"), + public static final Status FORBIDDEN_403 = new Status(403, "Forbidden", true); /** * 404 Not Found, see * HTTP/1.1 documentation. */ - NOT_FOUND_404(404, "Not Found"), + public static final Status NOT_FOUND_404 = new Status(404, "Not Found", true); /** * 405 Method Not Allowed, see * HTTP/1.1 documentation. * * @since 2.0 */ - METHOD_NOT_ALLOWED_405(405, "Method Not Allowed"), + public static final Status METHOD_NOT_ALLOWED_405 = new Status(405, "Method Not Allowed", true); /** * 406 Not Acceptable, see * HTTP/1.1 documentation. */ - NOT_ACCEPTABLE_406(406, "Not Acceptable"), + public static final Status NOT_ACCEPTABLE_406 = new Status(406, "Not Acceptable", true); /** * 407 Proxy Authentication Required, see * HTTP/1.1 documentation. * * @since 2.0 */ - PROXY_AUTHENTICATION_REQUIRED_407(407, "Proxy Authentication Required"), + public static final Status PROXY_AUTHENTICATION_REQUIRED_407 = new Status(407, "Proxy Authentication Required", true); /** * 408 Request Timeout, see * HTTP/1.1 documentation. * * @since 2.0 */ - REQUEST_TIMEOUT_408(408, "Request Timeout"), + public static final Status REQUEST_TIMEOUT_408 = new Status(408, "Request Timeout", true); /** * 409 Conflict, see * HTTP/1.1 documentation. */ - CONFLICT_409(409, "Conflict"), + public static final Status CONFLICT_409 = new Status(409, "Conflict", true); /** * 410 Gone, see HTTP/1.1 documentation. */ - GONE_410(410, "Gone"), + public static final Status GONE_410 = new Status(410, "Gone", true); /** * 411 Length Required, see * HTTP/1.1 documentation. * * @since 2.0 */ - LENGTH_REQUIRED_411(411, "Length Required"), + public static final Status LENGTH_REQUIRED_411 = new Status(411, "Length Required", true); /** * 412 Precondition Failed, see * HTTP/1.1 documentation. */ - PRECONDITION_FAILED_412(412, "Precondition Failed"), + public static final Status PRECONDITION_FAILED_412 = new Status(412, "Precondition Failed", true); /** * 413 Request Entity Too Large, see * HTTP/1.1 documentation. * * @since 2.0 */ - REQUEST_ENTITY_TOO_LARGE_413(413, "Request Entity Too Large"), + public static final Status REQUEST_ENTITY_TOO_LARGE_413 = new Status(413, "Request Entity Too Large", true); /** * 414 Request-URI Too Long, see * HTTP/1.1 documentation. * * @since 2.0 */ - REQUEST_URI_TOO_LONG_414(414, "Request-URI Too Long"), + public static final Status REQUEST_URI_TOO_LONG_414 = new Status(414, "Request-URI Too Long", true); /** * 415 Unsupported Media Type, see * HTTP/1.1 documentation. */ - UNSUPPORTED_MEDIA_TYPE_415(415, "Unsupported Media Type"), + public static final Status UNSUPPORTED_MEDIA_TYPE_415 = new Status(415, "Unsupported Media Type", true); /** * 416 Requested Range Not Satisfiable, see * HTTP/1.1 documentation. * * @since 2.0 */ - REQUESTED_RANGE_NOT_SATISFIABLE_416(416, "Requested Range Not Satisfiable"), + public static final Status REQUESTED_RANGE_NOT_SATISFIABLE_416 = new Status(416, "Requested Range Not Satisfiable", true); /** * 417 Expectation Failed, see * HTTP/1.1 documentation. * * @since 2.0 */ - EXPECTATION_FAILED_417(417, "Expectation Failed"), + public static final Status EXPECTATION_FAILED_417 = new Status(417, "Expectation Failed", true); /** * 418 I'm a teapot, see * Hyper Text Coffee Pot Control Protocol (HTCPCP/1.0). */ - I_AM_A_TEAPOT(418, "I'm a teapot"), + public static final Status I_AM_A_TEAPOT_418 = new Status(418, "I'm a teapot", true); /** * 500 Internal Server Error, see * HTTP/1.1 documentation. */ - INTERNAL_SERVER_ERROR_500(500, "Internal Server Error"), + public static final Status INTERNAL_SERVER_ERROR_500 = new Status(500, "Internal Server Error", true); /** * 501 Not Implemented, see * HTTP/1.1 documentation. * * @since 2.0 */ - NOT_IMPLEMENTED_501(501, "Not Implemented"), + public static final Status NOT_IMPLEMENTED_501 = new Status(501, "Not Implemented", true); /** * 502 Bad Gateway, see * HTTP/1.1 documentation. * * @since 2.0 */ - BAD_GATEWAY_502(502, "Bad Gateway"), + public static final Status BAD_GATEWAY_502 = new Status(502, "Bad Gateway", true); /** * 503 Service Unavailable, see * HTTP/1.1 documentation. */ - SERVICE_UNAVAILABLE_503(503, "Service Unavailable"), + public static final Status SERVICE_UNAVAILABLE_503 = new Status(503, "Service Unavailable", true); /** * 504 Gateway Timeout, see * HTTP/1.1 documentation. * * @since 2.0 */ - GATEWAY_TIMEOUT_504(504, "Gateway Timeout"), + public static final Status GATEWAY_TIMEOUT_504 = new Status(504, "Gateway Timeout", true); /** * 505 HTTP Version Not Supported, see * HTTP/1.1 documentation. * * @since 2.0 */ - HTTP_VERSION_NOT_SUPPORTED(505, "HTTP Version Not Supported"); + public static final Status HTTP_VERSION_NOT_SUPPORTED = new Status(505, "HTTP Version Not Supported", true); + + static { + // THIS MUST BE AFTER THE LAST CONSTANT + StatusHelper.statusesDone(); + } private final int code; private final String reason; private final Family family; + private final String codeText; + private final String stringValue; - Status(int statusCode, String reasonPhrase) { + private Status(int statusCode, String reasonPhrase, boolean instance) { this.code = statusCode; this.reason = reasonPhrase; this.family = Family.of(statusCode); + this.codeText = String.valueOf(code); + this.stringValue = code + " " + reason; + + if (instance) { + StatusHelper.add(this); + } + } + + private Status(int statusCode, String reasonPhrase, Family family, String codeText) { + // for custom codes + this.code = statusCode; + this.reason = reasonPhrase; + this.family = family; + this.codeText = codeText; + this.stringValue = code + " " + reason; } /** - * Convert a numerical status code into the corresponding {@code Status} enum value. + * Convert a numerical status code into the corresponding Status. *

- * As opposed to {@link ResponseStatus#create(int)}, this method returns {@link Optional#empty() no value} - * for an unknown status code not represented by a value in this enumeration of standard HTTP response status codes. + * For an unknown code, an ad-hoc {@link io.helidon.common.http.Http.Status} is created. * - * @param statusCode the numerical status code. - * @return optionally the matching Status if a matching {@code Status} value is defined. + * @param statusCode the numerical status code + * @return the matching Status; either a constant from this class, or an ad-hoc {@link io.helidon.common.http.Http.Status} */ - public static Optional find(int statusCode) { - for (Status s : Status.values()) { - if (s.code == statusCode) { - return Optional.of(s); - } + public static Status create(int statusCode) { + Status found = StatusHelper.find(statusCode); + + if (found == null) { + return createNew(Family.of(statusCode), statusCode, "", String.valueOf(statusCode)); } - return Optional.empty(); + return found; } /** - * Get the class of status code. + * Convert a numerical status code into the corresponding Status. + *

+ * It either returns an existing {@link io.helidon.common.http.Http.Status} constant if possible. + * For an unknown code, or code/reason phrase combination it creates + * an ad-hoc {@link io.helidon.common.http.Http.Status}. * - * @return the class of status code. - */ - @Override - public Family family() { - return family; + * @param statusCode the numerical status code + * @param reasonPhrase the reason phrase; if {@code null} or a known reason phrase, an instance with the default + * phrase is returned; otherwise, a new instance is returned + * @return the matching Status + */ + public static Status create(int statusCode, String reasonPhrase) { + Status found = StatusHelper.find(statusCode); + if (found == null) { + return createNew(Family.of(statusCode), statusCode, reasonPhrase, String.valueOf(statusCode)); + } + if (reasonPhrase == null) { + return found; + } + if (found.reasonPhrase().equalsIgnoreCase(reasonPhrase)) { + return found; + } + return createNew(found.family(), statusCode, reasonPhrase, found.codeText()); + } + + private static Status createNew(Family family, int statusCode, String reasonPhrase, String codeText) { + return new Status(statusCode, reasonPhrase, family, codeText); } /** - * Get the associated status code. + * Get the associated integer value representing the status code. * - * @return the status code. + * @return the integer value representing the status code. */ - @Override public int code() { return code; } + /** + * Get the class of status code. + * + * @return the class of status code. + */ + public Family family() { + return family; + } + /** * Get the reason phrase. * * @return the reason phrase. */ - @Override public String reasonPhrase() { return reason; } + /** + * Text of the {@link #code()}. + * + * @return code string (number as a string) + */ + public String codeText() { + return codeText; + } + /** * Get the response status as string. * * @return the response status string in the form of a partial HTTP response status line, - * i.e. {@code "Status-Code SP Reason-Phrase"}. + * i.e. {@code "Status-Code SP Reason-Phrase"}. */ - @Override public String toString() { - return code + " " + reason; + return stringValue; } - } - /** - * Enumeration of all standard HTTP {@link RequestMethod methods}. - * It is extensible enumeration pattern. - * - * @see RequestMethod - */ - public enum Method implements RequestMethod { /** - * The OPTIONS method represents a request for information about the communication options available - * on the request/response chain identified by the Request-URI. This method allows the client to determine the options - * and/or requirements associated with a resource, or the capabilities of a server, without implying a resource action - * or initiating a resource retrieval. + * Text of the status as used in HTTP/1, such as "200 OK". + * @return text of this status */ - OPTIONS, + public String text() { + return stringValue; + } - /** - * The GET method means retrieve whatever information (in the form of an entity) is identified by the Request-URI. - * If the Request-URI refers to a data-producing process, it is the produced data which shall be returned as the entity - * in the response and not the source text of the process, unless that text happens to be the output of the tryProcess. - */ - GET, + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (o == null || getClass() != o.getClass()) { + return false; + } + Status status = (Status) o; + return code == status.code && reason.equals(status.reason); + } - /** - * The HEAD method is identical to {@link #GET} except that the server MUST NOT return a message-body in the response. - * The metainformation contained in the HTTP headers in response to a HEAD request SHOULD be identical to the information - * sent in response to a GET request. This method can be used for obtaining metainformation about the entity implied - * by the request without transferring the entity-body itself. This method is often used for testing hypertext links - * for validity, accessibility, and recent modification. - */ - HEAD, + @Override + public int hashCode() { + return Objects.hash(code, reason); + } /** - * The POST method is used to request that the origin server acceptedTypes the entity enclosed in the request - * as a new subordinate of the resource identified by the Request-URI in the Request-Line. - * The actual function performed by the POST method is determined by the server and is usually dependent on the - * Request-URI. The posted entity is subordinate to that URI in the same way that a file is subordinate to a directory - * containing it, a news article is subordinate to a newsgroup to which it is posted, or a record is subordinate - * to a database. + * An enumeration representing the class of status code. Family is used + * here since class is overloaded in Java. + *

+ * Copied from JAX-RS. */ - POST, + public enum Family { - /** - * The PUT method requests that the enclosed entity be stored under the supplied Request-URI. If the Request-URI refers - * to an already existing resource, the enclosed entity SHOULD be considered as a modified version of the one residing - * on the origin server. If the Request-URI does not point to an existing resource, and that URI is capable of being - * defined as a new resource by the requesting user agent, the origin server can create the resource with that URI. - * If a new resource is created, the origin server MUST inform the user agent via the 201 (Created) response. - * If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate - * successful completion of the request. If the resource could not be created or modified with the Request-URI, - * an appropriate error response SHOULD be given that reflects the nature of the problem. The recipient of the entity - * MUST NOT ignore any Content-* (e.g. Content-Range) headers that it does not understand or implement and MUST return - * a 501 (Not Implemented) response in such cases. - */ - PUT, + /** + * {@code 1xx} HTTP status codes. + */ + INFORMATIONAL, + /** + * {@code 2xx} HTTP status codes. + */ + SUCCESSFUL, + /** + * {@code 3xx} HTTP status codes. + */ + REDIRECTION, + /** + * {@code 4xx} HTTP status codes. + */ + CLIENT_ERROR, + /** + * {@code 5xx} HTTP status codes. + */ + SERVER_ERROR, + /** + * Other, unrecognized HTTP status codes. + */ + OTHER; + /** + * Get the family for the response status code. + * + * @param statusCode response status code to get the family for. + * @return family of the response status code. + */ + public static Family of(int statusCode) { + return switch (statusCode / 100) { + case 1 -> Family.INFORMATIONAL; + case 2 -> Family.SUCCESSFUL; + case 3 -> Family.REDIRECTION; + case 4 -> Family.CLIENT_ERROR; + case 5 -> Family.SERVER_ERROR; + default -> Family.OTHER; + }; + } + } + } + + /** + * HTTP header name. + */ + public sealed interface HeaderName permits HeaderImpl, HeaderEnum { /** - * The PATCH method as described in RFC 5789 is used to perform an update to an existing resource, where the request - * payload only has to contain the instructions on how to perform the update. This is in contrast to PUT which - * requires that the payload contains the new version of the resource. - * If an existing resource is modified, either the 200 (OK) or 204 (No Content) response codes SHOULD be sent to indicate - * successful completion of the request. + * Lowercase value of this header, used by HTTP/2, may be used for lookup by HTTP/1. + * There is no validation of this value, so if this contains an upper-case letter, behavior + * is undefined. + * + * @return name of the header, lowercase */ - PATCH, + String lowerCase(); /** - * The DELETE method requests that the origin server delete the resource identified by the Request-URI. - * This method MAY be overridden by human intervention (or other means) on the origin server. The client cannot - * be guaranteed that the operation has been carried out, even if the status code returned from the origin server - * indicates that the action has been completed successfully. However, the server SHOULD NOT indicate success unless, - * at the time the response is given, it intends to delete the resource or move it to an inaccessible location. + * Header name as used in HTTP/1, or "human-readable" value of this header. + * + * @return name of the header, may use uppercase and/or lowercase */ - DELETE, + String defaultCase(); /** - * The TRACE method is used to invoke a remote, application-layer loop- back of the request message. - * The final recipient of the request SHOULD reflect the message received back to the client as the entity-body - * of a 200 (OK) response. The final recipient is either the origin server or the first proxy or gateway to receive - * a Max-Forwards value of zero (0) in the request (see section 14.31). A TRACE request MUST NOT include an entity. + * Index of this header (if one of the known indexed headers), or {@code -1} if this is a custom header name. + * + * @return index of this header */ - TRACE - - } - - /** - * Enumeration of supported HTTP protocol versions. - */ - public enum Version { + default int index() { + return -1; + } /** - * HTTP version {@code HTTP/1.0}. + * Http2 defines pseudoheaders as headers starting with a {@code :} character. These are used instead + * of the prologue line from HTTP/1 (to define path, authority etc.) and instead of status line in response. + * + * @return whether this header is a pseudo-header */ - V1_0("HTTP/1.0"), + default boolean isPseudoHeader() { + return lowerCase().charAt(0) == ':'; + } /** - * HTTP version {@code HTTP/1.1}. + * Create a new header with this name and the provided value. + * The header is considered not sensitive and not changing. + * + * @param value value of the header + * @return a new cached HTTP header + * @see #withValue(boolean, boolean, String) */ - V1_1("HTTP/1.1"), + default HeaderValue withValue(String value) { + return HeaderValue.createCached(this, value); + } /** - * HTTP version {@code HTTP/2.0}. + * Create a new header with this name and the provided value. + * The header is considered not sensitive and not changing. + * + * @param value value of the header + * @return a new cached HTTP header */ - V2_0("HTTP/2.0"); - - private final String value; - - Version(String value) { - this.value = value; + default HeaderValue withValue(int value) { + return HeaderValue.createCached(this, String.valueOf(value)); } /** - * Returns HTTP version for provided parameter. + * Create a new header with this name and the provided value. + * The header is considered not sensitive and not changing. * - * @param version HTTP version. - * @return Version instance. - * @throws NullPointerException if parameter {@code version} is null. - * @throws IllegalArgumentException if it is not provided version. + * @param value value(s) of the header + * @return a new HTTP header + * @see #withValue(boolean, boolean, String...) */ - public static Version create(String version) { - Objects.requireNonNull(version, "Version value is null!"); - for (Version v : Version.values()) { - if (version.equals(v.value)) { - return v; - } - } - throw new IllegalArgumentException("Unknown HTTP version: " + version + "!"); + default HeaderValue withValue(String... value) { + return HeaderValue.create(this, value); } /** - * Returns {@code String} representation of this {@link Version}. + * Create a new header with this name and the provided value. * - * @return a string representation. - */ - public String value() { - return value; + * @param changing is this a header that changes value often (e.g. with every request); important for HTTP/2, where + * changing + * headers do not cache values + * @param sensitive is this a sensitive header, where we should not cache the value ever (such as security token) + * @param value value of the header + * @return a new cached HTTP header + */ + default HeaderValue withValue(boolean changing, boolean sensitive, String value) { + return HeaderValue.createCached(this, changing, sensitive, value); + } + + /** + * Create a new header with this name and the provided value. + * + * @param changing is this a header that changes value often (e.g. with every request); important for HTTP/2, where + * changing + * headers do not cache values + * @param sensitive is this a sensitive header, where we should not cache the value ever (such as security token) + * @param value value(s) of the header + * @return a new HTTP header + */ + default HeaderValue withValue(boolean changing, boolean sensitive, String... value) { + return HeaderValue.create(this, changing, sensitive, value); } } /** - * Interface representing an HTTP request method, all standard methods are in {@link Method} enumeration. + * HTTP Header with {@link io.helidon.common.http.Http.HeaderName} and value. * - * @see Method + * @see io.helidon.common.http.Http.HeaderValues */ - @FunctionalInterface - public interface RequestMethod { + public interface HeaderValue { + /** + * Create and cache byte value. + * Use this method if the header value is stored in a constant, or used repeatedly. + * + * @param name header name + * @param value value of the header + * @return a new header + */ + static HeaderValue createCached(HeaderName name, String value) { + return new HeaderValueCached(name, false, + false, + value.getBytes(StandardCharsets.US_ASCII), + value); + } /** - * Create new HTTP request method instance from the provided name. - *

- * In case the method name is recognized as one of the {@link Method standard HTTP methods}, the respective enumeration - * value is returned. + * Create a new header with a single value. This header is considered unchanging and not sensitive. * - * @param name the method name. Must not be {@code null} or empty and must be a legal HTTP method name string. - * @return HTTP request method instance representing an HTTP method with the provided name. - * @throws IllegalArgumentException In case of illegal method name or in case the name is empty or {@code null}. + * @param name name of the header + * @param value lazy string with the value + * @return a new header + * @see #create(HeaderName, boolean, boolean, String...) */ - static RequestMethod create(String name) { - if (name != null && !name.isEmpty()) { - for (int i = 0; i < name.length(); i++) { - char ch = name.charAt(i); - if (Character.isISOControl(ch)) { - throw new IllegalArgumentException("HTTP method name parameter must not contain ISO control characters!"); - } else if (Character.isWhitespace(ch)) { - throw new IllegalArgumentException("HTTP method name parameter must not contain whitespace!"); - } - } - } else { - throw new IllegalArgumentException("HTTP method name must not be null or empty!"); - } + static HeaderValue create(HeaderName name, LazyString value) { + return new HeaderValueLazy(name, false, false, value); + } - final String upperCaseName = name.toUpperCase(); + /** + * Create a new header with a single value. This header is considered unchanging and not sensitive. + * + * @param name name of the header + * @param value value of the header + * @return a new header + * @see #create(HeaderName, boolean, boolean, String...) + */ + static HeaderValue create(HeaderName name, String value) { + return new HeaderValueSingle(name, + false, + false, + value); + } - for (Method method : Method.values()) { - if (method.name().equals(upperCaseName)) { - return method; - } - } + /** + * Create a new header. This header is considered unchanging and not sensitive. + * + * @param name name of the header* + * @param values values of the header + * @return a new header + * @see #create(HeaderName, boolean, boolean, String...) + */ + static HeaderValue create(HeaderName name, String... values) { + return new HeaderValueArray(name, false, false, values); + } - return new RequestMethod() { - @Override - public String name() { - return upperCaseName; - } + /** + * Create a new header. This header is considered unchanging and not sensitive. + * + * @param name name of the header + * @param values values of the header + * @return a new header + * @see #create(HeaderName, boolean, boolean, String...) + */ + static HeaderValue create(HeaderName name, List values) { + return new HeaderValueList(name, false, false, values); + } - @Override - public boolean equals(Object other) { - return (other instanceof RequestMethod) && name().equals(((RequestMethod) other).name()); - } + /** + * Create and cache byte value. + * Use this method if the header value is stored in a constant, or used repeatedly. + * + * @param name header name + * @param changing whether the value is changing often (to disable caching for HTTP/2) + * @param sensitive whether the value is sensitive (to disable caching for HTTP/2) + * @param value value of the header + * @return a new header + */ + static HeaderValue createCached(HeaderName name, boolean changing, boolean sensitive, String value) { + return new HeaderValueCached(name, changing, sensitive, value.getBytes(StandardCharsets.UTF_8), value); + } - @Override - public int hashCode() { - return upperCaseName.hashCode(); - } - }; + /** + * Create a new header. + * + * @param name name of the header + * @param changing whether the value is changing often (to disable caching for HTTP/2) + * @param sensitive whether the value is sensitive (to disable caching for HTTP/2) + * @param values value(s) of the header + * @return a new header + */ + static HeaderValue create(HeaderName name, boolean changing, boolean sensitive, String... values) { + return new HeaderValueArray(name, changing, sensitive, values); } /** - * Get method name. + * Name of the header as configured by user + * or as received on the wire. * - * @return a method name. + * @return header name, always lower case for HTTP/2 headers */ String name(); - } - /** - * Base interface for status codes used in HTTP responses. - *

- * Copied from JAX-RS. - */ - @SuppressWarnings("unused") - public interface ResponseStatus { + /** + * Header name for the header. + * + * @return header name + */ + HeaderName headerName(); /** - * Convert a numerical status code into the corresponding ResponseStatus. - *

- * As opposed to {@link Status#find(int)}, this method is guaranteed to always return an instance. - * For an unknown {@link Status} it creates an ad-hoc {@link ResponseStatus}. + * First value of this header. * - * @param statusCode the numerical status code - * @return the matching ResponseStatus; either a {@link Status} or an ad-hoc {@link ResponseStatus} + * @return the first value */ - static ResponseStatus create(int statusCode) { - return create(statusCode, null); - } + String value(); /** - * Convert a numerical status code into the corresponding ResponseStatus. - *

- * It either returns an existing {@link Status} if possible. For an unknown {@link Status} it creates - * an ad-hoc {@link ResponseStatus} or whenever a custom reason phrase is provided. - * - * @param statusCode the numerical status code; if known, a {@link Status is returned} - * @param reasonPhrase the reason phrase; if {@code null} or a known reason phrase, a - * {@link Status} is returned; otherwise, a new instance is returned - * @return the matching ResponseStatus; either a {@link Status} or an ad-hoc {@link ResponseStatus} - */ - static ResponseStatus create(int statusCode, String reasonPhrase) { - return Status.find(statusCode) - // keep status that has the same reason phrase or if the supplied phrase is null - .filter(status -> (null == reasonPhrase) || status.reasonPhrase().equalsIgnoreCase(reasonPhrase)) - // the next statement returns an instance of ResponseStatus, previous of Status - cast - // to make the mapping work without - .map(ResponseStatus.class::cast) - // only create the new ResponseStatus if we did not find an existing Status - .orElseGet(() -> new ResponseStatus() { - // not using a method, as it would be implicitly public (this being an interface) - @Override - public int code() { - return statusCode; - } - - @Override - public Family family() { - return Family.of(statusCode); - } - - @Override - public String reasonPhrase() { - return reasonPhrase; - } - - @Override - public int hashCode() { - return Integer.hashCode(statusCode); - } - - @Override - public boolean equals(Object other) { - if (other instanceof ResponseStatus) { - ResponseStatus os = (ResponseStatus) other; - - return (code() == os.code()) - && (family() == os.family()) - && (reasonPhraseEquals(os)); - } - - return false; - } - - private boolean reasonPhraseEquals(ResponseStatus other) { - if (null == reasonPhrase) { - return null == other.reasonPhrase(); - } - - return reasonPhrase.equalsIgnoreCase(other.reasonPhrase()); - } - - @Override - public String toString() { - return "ResponseStatus{code=" + code() - + ", reason=" + reasonPhrase() - + "}"; - } - }); + * Value mapped using a {@link io.helidon.common.mapper.MapperManager}. + * + * @param type class of the value + * @param type of the value + * @return typed value + */ + T value(Class type); + + /** + * All values concatenated using a comma. + * + * @return all values joined by a comma + */ + default String values() { + return String.join(",", allValues()); } /** - * Get the associated integer value representing the status code. + * All values of this header. * - * @return the integer value representing the status code. + * @return all configured values */ - int code(); + List allValues(); /** - * Get the class of status code. + * All values of this header. If this header is defined as a single header with comma separated values, + * set {@code split} to true. * - * @return the class of status code. + * @param split whether to split single value by comma, does nothing if the value is already a list. + * @return list of values + */ + default List allValues(boolean split) { + if (split) { + List values = allValues(); + if (values.size() == 1) { + String value = values.get(0); + if (value.contains(", ")) { + return List.of(value.split(", ")); + } else { + return List.of(value); + } + } + return values; + } else { + return allValues(); + } + } + + /** + * Number of values this header has. + * + * @return number of values (minimal number is 1) */ - Family family(); + int valueCount(); /** - * Get the reason phrase. + * Sensitive headers should not be logged, or indexed (HTTP/2). * - * @return the reason phrase. + * @return whether this header is sensitive */ - String reasonPhrase(); + boolean sensitive(); /** - * An enumeration representing the class of status code. Family is used - * here since class is overloaded in Java. - *

- * Copied from JAX-RS. + * Changing headers should not be cached, and their value should not be indexed (HTTP/2). + * + * @return whether this header's value is changing often */ - enum Family { + boolean changing(); - /** - * {@code 1xx} HTTP status codes. - */ - INFORMATIONAL, - /** - * {@code 2xx} HTTP status codes. - */ - SUCCESSFUL, - /** - * {@code 3xx} HTTP status codes. - */ - REDIRECTION, - /** - * {@code 4xx} HTTP status codes. - */ - CLIENT_ERROR, - /** - * {@code 5xx} HTTP status codes. - */ - SERVER_ERROR, - /** - * Other, unrecognized HTTP status codes. - */ - OTHER; + /** + * Cached bytes of a single valued header's value. + * + * @return value bytes + */ + default byte[] valueBytes() { + return value().getBytes(StandardCharsets.US_ASCII); + } - /** - * Get the family for the response status code. - * - * @param statusCode response status code to get the family for. - * @return family of the response status code. - */ - public static Family of(int statusCode) { - switch (statusCode / 100) { - case 1: - return Family.INFORMATIONAL; - case 2: - return Family.SUCCESSFUL; - case 3: - return Family.REDIRECTION; - case 4: - return Family.CLIENT_ERROR; - case 5: - return Family.SERVER_ERROR; - default: - return Family.OTHER; + /** + * Write the current header as an HTTP header to the provided buffer. + * + * @param buffer buffer to write to (should be growing) + */ + default void writeHttp1Header(BufferData buffer) { + byte[] nameBytes = name().getBytes(StandardCharsets.US_ASCII); + if (valueCount() == 1) { + writeHeader(buffer, nameBytes, valueBytes()); + } else { + for (String value : allValues()) { + writeHeader(buffer, nameBytes, value.getBytes(StandardCharsets.US_ASCII)); } } } + + private void writeHeader(BufferData buffer, byte[] nameBytes, byte[] valueBytes) { + // header name + buffer.write(nameBytes); + // ": " + buffer.write(':'); + buffer.write(' '); + // header value + buffer.write(valueBytes); + // \r\n + buffer.write('\r'); + buffer.write('\n'); + } + } + + /** + * Mutable header value. + */ + public interface HeaderValueWriteable extends HeaderValue { + /** + * Create a new mutable header from an existing header. + * + * @param header header to copy + * @return a new mutable header + */ + static HeaderValueWriteable create(HeaderValue header) { + return new HeaderValueCopy(header); + } + + /** + * Add a value to this header. + * + * @param value value to add + * @return this instance + */ + HeaderValueWriteable addValue(String value); } /** @@ -731,310 +1138,483 @@ public static Family of(int statusCode) { */ @SuppressWarnings({"WeakerAccess", "unused"}) public static final class Header { - /** - * The {@value} header name. + * The {@code Accept} header name. * Content-Types that are acceptedTypes for the response. */ - public static final String ACCEPT = "Accept"; + public static final HeaderName ACCEPT = HeaderEnum.ACCEPT; /** - * The {@value} header name. + * The {@code Accept-Charset} header name. * Character sets that are acceptedTypes. */ - public static final String ACCEPT_CHARSET = "Accept-Charset"; + public static final HeaderName ACCEPT_CHARSET = HeaderEnum.ACCEPT_CHARSET; /** - * The {@value} header name. + * The {@code Accept-Encoding} header name. * List of acceptedTypes encodings. */ - public static final String ACCEPT_ENCODING = "Accept-Encoding"; + public static final HeaderName ACCEPT_ENCODING = HeaderEnum.ACCEPT_ENCODING; /** - * The {@value} header name. + * The {@code Accept-Language} header name. * List of acceptedTypes human languages for response. */ - public static final String ACCEPT_LANGUAGE = "Accept-Language"; + public static final HeaderName ACCEPT_LANGUAGE = HeaderEnum.ACCEPT_LANGUAGE; /** - * The {@value} header name. + * The {@code Accept-Datetime} header name. * Acceptable version in time. */ - public static final String ACCEPT_DATETIME = "Accept-Datetime"; + public static final HeaderName ACCEPT_DATETIME = HeaderEnum.ACCEPT_DATETIME; + /** + * The {@code Access-Control-Allow-Credentials} header name. + * CORS configuration. + */ + public static final HeaderName ACCESS_CONTROL_ALLOW_CREDENTIALS = HeaderEnum.ACCESS_CONTROL_ALLOW_CREDENTIALS; + /** + * The {@code Access-Control-Allow-Headers} header name. + * CORS configuration + */ + public static final HeaderName ACCESS_CONTROL_ALLOW_HEADERS = HeaderEnum.ACCESS_CONTROL_ALLOW_HEADERS; + /** + * The {@code Access-Control-Allow-Methods} header name. + * CORS configuration + */ + public static final HeaderName ACCESS_CONTROL_ALLOW_METHODS = HeaderEnum.ACCESS_CONTROL_ALLOW_METHODS; + /** + * The {@code Access-Control-Allow-Origin} header name. + * CORS configuration. + */ + public static final HeaderName ACCESS_CONTROL_ALLOW_ORIGIN = HeaderEnum.ACCESS_CONTROL_ALLOW_ORIGIN; + /** + * The {@code Access-Control-Expose-Headers} header name. + * CORS configuration. + */ + public static final HeaderName ACCESS_CONTROL_EXPOSE_HEADERS = HeaderEnum.ACCESS_CONTROL_EXPOSE_HEADERS; + /** + * The {@code Access-Control-Max-Age} header name. + * CORS configuration. + */ + public static final HeaderName ACCESS_CONTROL_MAX_AGE = HeaderEnum.ACCESS_CONTROL_MAX_AGE; + /** + * The {@code Access-Control-Request-Headers} header name. + * CORS configuration. + */ + public static final HeaderName ACCESS_CONTROL_REQUEST_HEADERS = HeaderEnum.ACCESS_CONTROL_REQUEST_HEADERS; + /** + * The {@code Access-Control-Request-Method} header name. + * CORS configuration. + */ + public static final HeaderName ACCESS_CONTROL_REQUEST_METHOD = HeaderEnum.ACCESS_CONTROL_REQUEST_METHOD; /** - * The {@value} header name. + * The {@code Authorization} header name. * Authentication credentials for HTTP authentication. */ - public static final String AUTHORIZATION = "Authorization"; + public static final HeaderName AUTHORIZATION = HeaderEnum.AUTHORIZATION; /** - * The {@value} header name. - * An HTTP cookie previously sent by the server with {@value SET_COOKIE}. + * The {@code Cookie} header name. + * An HTTP cookie previously sent by the server with {@code Set-Cookie}. */ - public static final String COOKIE = "Cookie"; + public static final HeaderName COOKIE = HeaderEnum.COOKIE; /** - * The {@value} header name. + * The {@code Expect} header name. * Indicates that particular server behaviors are required by the client. */ - public static final String EXPECT = "Expect"; + public static final HeaderName EXPECT = HeaderEnum.EXPECT; /** - * The {@value} header name. + * The {@code Forwarded} header name. * Disclose original information of a client connecting to a web server through an HTTP proxy. */ - public static final String FORWARDED = "Forwarded"; + public static final HeaderName FORWARDED = HeaderEnum.FORWARDED; /** - * The {@value} header name. + * The {@code From} header name. * The email address of the user making the request. */ - public static final String FROM = "From"; + public static final HeaderName FROM = HeaderEnum.FROM; /** - * The {@value} header name. + * The {@code Host} header name. * The domain name of the server (for virtual hosting), and the TCP port number on which the server is listening. * The port number may be omitted if the port is the standard port for the service requested. */ - public static final String HOST = "Host"; + public static final HeaderName HOST = HeaderEnum.HOST; /** - * The {@value} header name. + * The {@code If-Match} header name. * Only perform the action if the client supplied entity matches the same entity on the server. This is mainly * for methods like PUT to only update a resource if it has not been modified since the user last updated it. */ - public static final String IF_MATCH = "If-Match"; + public static final HeaderName IF_MATCH = HeaderEnum.IF_MATCH; /** - * The {@value} header name. + * The {@code If-Modified-Since} header name. * Allows a 304 Not Modified to be returned if content is unchanged. */ - public static final String IF_MODIFIED_SINCE = "If-Modified-Since"; + public static final HeaderName IF_MODIFIED_SINCE = HeaderEnum.IF_MODIFIED_SINCE; /** - * The {@value} header name. + * The {@code If-None-Match} header name. * Allows a 304 Not Modified to be returned if content is unchanged, based on {@link #ETAG}. */ - public static final String IF_NONE_MATCH = "If-None-Match"; + public static final HeaderName IF_NONE_MATCH = HeaderEnum.IF_NONE_MATCH; /** - * The {@value} header name. + * The {@code If-Range} header name. * If the entity is unchanged, send me the part(s) that I am missing; otherwise, send me the entire new entity. */ - public static final String IF_RANGE = "If-Range"; + public static final HeaderName IF_RANGE = HeaderEnum.IF_RANGE; /** - * The {@value} header name. - * Only send the response if the entity has not been modified since a specific time. + * The {@code If-Unmodified-Since} header name. + * Only send The {@code response if The Entity} has not been modified since a specific time. */ - public static final String IF_UNMODIFIED_SINCE = "If-Unmodified-Since"; + public static final HeaderName IF_UNMODIFIED_SINCE = HeaderEnum.IF_UNMODIFIED_SINCE; /** - * The {@value} header name. + * The {@code Max-Forwards} header name. * Limit the number of times the message can be forwarded through proxies or gateways. */ - public static final String MAX_FORWARDS = "Max-Forwards"; + public static final HeaderName MAX_FORWARDS = HeaderEnum.MAX_FORWARDS; /** - * The {@value} header name. + * The {@code {@value}} header name. * Initiates a request for cross-origin resource sharing (asks server for an {@code 'Access-Control-Allow-Origin'} * response field). */ - public static final String ORIGIN = "Origin"; + public static final HeaderName ORIGIN = HeaderEnum.ORIGIN; + /** + * The {@code Proxy-Authenticate} header name. + * Proxy authentication information. + */ + public static final HeaderName PROXY_AUTHENTICATE = HeaderEnum.PROXY_AUTHENTICATE; + /** + * The {@code Proxy-Authorization} header name. + * Proxy authorization information. + */ + public static final HeaderName PROXY_AUTHORIZATION = HeaderEnum.PROXY_AUTHORIZATION; /** - * The {@value} header name. + * The {@code Range} header name. * Request only part of an entity. Bytes are numbered from 0. */ - public static final String RANGE = "Range"; + public static final HeaderName RANGE = HeaderEnum.RANGE; /** - * The {@value} header name. + * The {@code {@value}} header name. * This is the address of the previous web page from which a link to the currently requested page was followed. - * (The word referrer has been misspelled in the RFC as well as in most implementations to the point that it has + * (The {@code word referrer} has been misspelled in The + * {@code RFC as well as in most implementations to the point that it} has * become standard usage and is considered correct terminology.) */ - public static final String REFERER = "Referer"; + public static final HeaderName REFERER = HeaderEnum.REFERER; + /** + * The {@code {@value}} header name. + */ + public static final HeaderName REFRESH = HeaderEnum.REFRESH; /** - * The {@value} header name. - * The transfer encodings the user agent is willing to acceptedTypes: the same values as for the response header field + * The {@code {@value}} header name. + * The {@code transfer encodings the user agent is willing to acceptedTypes: the same values as for The Response} header + * field * {@code Transfer-Encoding} can be used, plus the trailers value (related to the chunked transfer method) * to notify the server it expects to receive additional fields in the trailer after the last, zero-sized, chunk. */ - public static final String TE = "TE"; + public static final HeaderName TE = HeaderEnum.TE; /** - * The {@value} header name. + * The {@code User-Agent} header name. * The user agent string of the user agent. */ - public static final String USER_AGENT = "User-Agent"; + public static final HeaderName USER_AGENT = HeaderEnum.USER_AGENT; /** - * The {@value} header name. + * The {@code Via} header name. * Informs the server of proxies through which the request was sent. */ - public static final String VIA = "Via"; + public static final HeaderName VIA = HeaderEnum.VIA; /** - * The {@value} header name. + * The {@code Accept-Patch} header name. * Specifies which patch document formats this server supports. */ - public static final String ACCEPT_PATCH = "Accept-Patch"; + public static final HeaderName ACCEPT_PATCH = HeaderEnum.ACCEPT_PATCH; /** - * The {@value} header name. + * The {@code Accept-Ranges} header name. * What partial content range types this server supports via byte serving. */ - public static final String ACCEPT_RANGES = "Accept-Ranges"; + public static final HeaderName ACCEPT_RANGES = HeaderEnum.ACCEPT_RANGES; /** - * The {@value} header name. - * The age the object has been in a proxy cache in seconds. + * The {@code Age} header name. + * The {@code age The Object} has been in a proxy cache in seconds. */ - public static final String AGE = "Age"; + public static final HeaderName AGE = HeaderEnum.AGE; /** - * The {@value} header name. + * The {@code Allow} header name. * Valid actions for a specified resource. To be used for a 405 Method not allowed. */ - public static final String ALLOW = "Allow"; + public static final HeaderName ALLOW = HeaderEnum.ALLOW; /** - * The {@value} header name. + * The {@code {@value}} header name. * A server uses Alt-Svc header (meaning Alternative Services) to indicate that its resources can also be * accessed at a different network location (host or port) or using a different protocol. */ - public static final String ALT_SVC = "Alt-Svc"; + public static final HeaderName ALT_SVC = HeaderEnum.ALT_SVC; /** - * The {@value} header name. + * The {@code Cache-Control} header name. * Tells all caching mechanisms from server to client whether they may cache this object. It is measured in seconds. */ - public static final String CACHE_CONTROL = "Cache-Control"; + public static final HeaderName CACHE_CONTROL = HeaderEnum.CACHE_CONTROL; /** - * The {@value} header name. - * Control options for the current connection and list of hop-by-hop response fields. + * The {@code Connection} header name. + * Control options for The {@code current connection and list of} hop-by-hop response fields. */ - public static final String CONNECTION = "Connection"; + public static final HeaderName CONNECTION = HeaderEnum.CONNECTION; /** - * The {@value} header name. + * The {@code {@value}} header name. * An opportunity to raise a File Download dialogue box for a known MIME type with binary format or suggest * a filename for dynamic content. Quotes are necessary with special characters. */ - public static final String CONTENT_DISPOSITION = "Content-Disposition"; + public static final HeaderName CONTENT_DISPOSITION = HeaderEnum.CONTENT_DISPOSITION; /** - * The {@value} header name. + * The {@code Content-Encoding} header name. * The type of encoding used on the data. */ - public static final String CONTENT_ENCODING = "Content-Encoding"; + public static final HeaderName CONTENT_ENCODING = HeaderEnum.CONTENT_ENCODING; /** - * The {@value} header name. + * The {@code Content-Language} header name. * The natural language or languages of the intended audience for the enclosed content. */ - public static final String CONTENT_LANGUAGE = "Content-Language"; + public static final HeaderName CONTENT_LANGUAGE = HeaderEnum.CONTENT_LANGUAGE; /** - * The {@value} header name. + * The {@code Content-Length} header name. * The length of the response body in octets. */ - public static final String CONTENT_LENGTH = "Content-Length"; + public static final HeaderName CONTENT_LENGTH = HeaderEnum.CONTENT_LENGTH; /** - * The {@value} header name. + * The {@code Content-Location} header name. * An alternate location for the returned data. */ - public static final String CONTENT_LOCATION = "aa"; + public static final HeaderName CONTENT_LOCATION = HeaderEnum.CONTENT_LOCATION; /** - * The {@value} header name. + * The {@code Content-Range} header name. * Where in a full body message this partial message belongs. */ - public static final String CONTENT_RANGE = "Content-Range"; + public static final HeaderName CONTENT_RANGE = HeaderEnum.CONTENT_RANGE; /** - * The {@value} header name. + * The {@code Content-Type} header name. * The MIME type of this content. */ - public static final String CONTENT_TYPE = "Content-Type"; + public static final HeaderName CONTENT_TYPE = HeaderEnum.CONTENT_TYPE; /** - * The {@value} header name. + * The {@code Date} header name. * The date and time that the message was sent (in HTTP-date format as defined by RFC 7231). */ - public static final String DATE = "Date"; + public static final HeaderName DATE = HeaderEnum.DATE; /** - * The {@value} header name. + * The {@code Etag} header name. * An identifier for a specific version of a resource, often a message digest. */ - public static final String ETAG = "ETag"; + public static final HeaderName ETAG = HeaderEnum.ETAG; /** - * The {@value} header name. + * The {@code Expires} header name. * Gives the date/time after which the response is considered stale (in HTTP-date format as defined by RFC 7231) */ - public static final String EXPIRES = "Expires"; + public static final HeaderName EXPIRES = HeaderEnum.EXPIRES; /** - * The {@value} header name. + * The {@code Last-Modified} header name. * The last modified date for the requested object (in HTTP-date format as defined by RFC 7231) */ - public static final String LAST_MODIFIED = "Last-Modified"; + public static final HeaderName LAST_MODIFIED = HeaderEnum.LAST_MODIFIED; /** - * The {@value} header name. + * The {@code Link} header name. * Used to express a typed relationship with another resource, where the relation type is defined by RFC 5988. */ - public static final String LINK = "Link"; + public static final HeaderName LINK = HeaderEnum.LINK; /** - * The {@value} header name. + * The {@code Location} header name. * Used in redirection, or whenRequest a new resource has been created. */ - public static final String LOCATION = "Location"; + public static final HeaderName LOCATION = HeaderEnum.LOCATION; /** - * The {@value} header name. + * The {@code Pragma} header name. * Implementation-specific fields that may have various effects anywhere along the request-response chain. */ - public static final String PRAGMA = "Pragma"; + public static final HeaderName PRAGMA = HeaderEnum.PRAGMA; /** - * The {@value} header name. + * The {@code Public-Key-Pins} header name. * HTTP Public Key Pinning, announces hash of website's authentic TLS certificate. */ - public static final String PUBLIC_KEY_PINS = "Public-Key-Pins"; + public static final HeaderName PUBLIC_KEY_PINS = HeaderEnum.PUBLIC_KEY_PINS; /** - * The {@value} header name. + * The {@code {@value}} header name. * If an entity is temporarily unavailable, this instructs the client to try again later. Value could be a specified * period of time (in seconds) or an HTTP-date. */ - public static final String RETRY_AFTER = "Retry-After"; + public static final HeaderName RETRY_AFTER = HeaderEnum.RETRY_AFTER; /** - * The {@value} header name. + * The {@code Server} header name. * A name for the server. */ - public static final String SERVER = "Server"; + public static final HeaderName SERVER = HeaderEnum.SERVER; + /** + * The {@code Set-Cookie} header name. + * An HTTP cookie set directive. + */ + public static final HeaderName SET_COOKIE = HeaderEnum.SET_COOKIE; /** - * The {@value} header name. + * The {@code Set-Cookie2} header name. * An HTTP cookie set directive. */ - public static final String SET_COOKIE = "Set-Cookie"; + public static final HeaderName SET_COOKIE2 = HeaderEnum.SET_COOKIE2; /** - * The {@value} header name. - * A HSTS Policy informing the HTTP client how long to cache the HTTPS only policy and whether this applies to subdomains. + * The {@code Strict-Transport-Security} header name. + * A HSTS Policy informing The {@code HTTP client} how long to cache the HTTPS only policy and whether this applies to + * subdomains. */ - public static final String STRICT_TRANSPORT_SECURITY = "Strict-Transport-Security"; + public static final HeaderName STRICT_TRANSPORT_SECURITY = HeaderEnum.STRICT_TRANSPORT_SECURITY; /** - * The {@value} header name. - * The Trailer general field value indicates that the given set of header fields is present in the trailer of + * The {@code Trailer} header name. + * The Trailer general field value indicates that the given set of} header fields is present in the trailer of * a message encoded with chunked transfer coding. */ - public static final String TRAILER = "Trailer"; + public static final HeaderName TRAILER = HeaderEnum.TRAILER; /** - * The {@value} header name. + * The {@code Transfer-Encoding} header name. * The form of encoding used to safely transfer the entity to the user. Currently defined methods are: * {@code chunked, compress, deflate, gzip, identity}. */ - public static final String TRANSFER_ENCODING = "Transfer-Encoding"; + public static final HeaderName TRANSFER_ENCODING = HeaderEnum.TRANSFER_ENCODING; /** - * The {@value} header name. + * The {@code Tsv} header name. * Tracking Status Value, value suggested to be sent in response to a DNT(do-not-track). */ - public static final String TSV = "TSV"; + public static final HeaderName TSV = HeaderEnum.TSV; /** - * The {@value} header name. + * The {@code Upgrade} header name. * Ask to upgrade to another protocol. */ - public static final String UPGRADE = "Upgrade"; + public static final HeaderName UPGRADE = HeaderEnum.UPGRADE; /** - * The {@value} header name. + * The {@code Vary} header name. * Tells downstream proxies how to match future request headers to decide whether the cached response can be used rather * than requesting a fresh one from the origin server. */ - public static final String VARY = "Vary"; + public static final HeaderName VARY = HeaderEnum.VARY; /** - * The {@value} header name. + * The {@code Warning} header name. * A general warning about possible problems with the entity body. */ - public static final String WARNING = "Warning"; + public static final HeaderName WARNING = HeaderEnum.WARNING; /** - * The {@value} header name. + * The {@code WWW-Authenticate} header name. * Indicates the authentication scheme that should be used to access the requested entity. */ - public static final String WWW_AUTHENTICATE = "WWW-Authenticate"; + public static final HeaderName WWW_AUTHENTICATE = HeaderEnum.WWW_AUTHENTICATE; /** - * The {@value} header name. + * The {@code X_HELIDON_CN} header name. * Corresponds to the certificate CN subject value when client authentication enabled. * This header will be removed if it is part of the request. */ - public static final String X_HELIDON_CN = "X-HELIDON-CN"; + public static final HeaderName X_HELIDON_CN = HeaderEnum.X_HELIDON_CN; private Header() { } + /** + * Find or create a header name. + * If a known indexed header exists for the lower case name, the instance is returned. + * Otherwise a new header name is created with the provided names. + * + * @param name default case to use for custom header names (header names not known by Helidon) + * @return header name instance + */ + public static HeaderName create(String name) { + HeaderName headerName = HeaderEnum.byCapitalizedName(name); + if (headerName == null) { + return new HeaderImpl(Ascii.toLowerCase(name), name); + } + return headerName; + } + + /** + * Find or create a header name. + * If a known indexed header exists for the lower case name, the instance is returned. + * Otherwise a new header name is created with the provided names. + * + * @param lowerCase lower case name + * @param defaultCase default case to use for custom header names (header names not known by Helidon) + * @return header name instance + */ + public static HeaderName create(String lowerCase, String defaultCase) { + HeaderName headerName = HeaderEnum.byName(lowerCase); + if (headerName == null) { + return new HeaderImpl(lowerCase, defaultCase); + } else { + return headerName; + } + } + + /** + * Create a header name from lower case letters. + * + * @param lowerCase lower case + * @return a new header name + */ + public static HeaderName createFromLowercase(String lowerCase) { + if (!Ascii.toLowerCase(lowerCase).equals(lowerCase)) { + throw new IllegalArgumentException("Lower case string required: " + lowerCase); + } + HeaderName headerName = HeaderEnum.byName(lowerCase); + if (headerName == null) { + return new HeaderImpl(lowerCase, lowerCase); + } else { + return headerName; + } + } + } + + /** + * Values of commonly used headers. + */ + public static final class HeaderValues { + /** + * Accept byte ranges for file download. + */ + public static final HeaderValue ACCEPT_RANGES_BYTES = HeaderValue.createCached(Header.ACCEPT_RANGES, "bytes"); + /** + * Not accepting byte ranges for file download. + */ + public static final HeaderValue ACCEPT_RANGES_NONE = HeaderValue.createCached(Header.ACCEPT_RANGES, "none"); + /** + * Chunked transfer encoding. + * Used in {@code HTTP/1}. + */ + public static final HeaderValue TRANSFER_ENCODING_CHUNKED = HeaderValue.createCached(Header.TRANSFER_ENCODING, "chunked"); + /** + * Connection keep-alive. + * Used in {@code HTTP/1}. + */ + public static final HeaderValue CONNECTION_KEEP_ALIVE = HeaderValue.createCached(Header.CONNECTION, "keep-alive"); + /** + * Connection close. + * Used in {@code HTTP/1}. + */ + public static final HeaderValue CONNECTION_CLOSE = HeaderValue.createCached(Header.CONNECTION, "close"); + /** + * Content type application/json with no charset. + */ + public static final HeaderValue CONTENT_TYPE_JSON = HeaderValue.createCached(Header.CONTENT_TYPE, "application/json"); + /** + * Content type text plain with no charset. + */ + public static final HeaderValue CONTENT_TYPE_TEXT_PLAIN = HeaderValue.createCached(Header.CONTENT_TYPE, "text/plain"); + /** + * Content type octet stream. + */ + public static final HeaderValue CONTENT_TYPE_OCTET_STREAM = HeaderValue.createCached(Header.CONTENT_TYPE, + "application/octet-stream"); + /** + * Accept application/json. + */ + public static final HeaderValue ACCEPT_JSON = HeaderValue.createCached(Header.ACCEPT, "application/json"); + /** + * Accept text/plain with UTF-8. + */ + public static final HeaderValue ACCEPT_TEXT = HeaderValue.createCached(Header.ACCEPT, "text/plain;charset=UTF-8"); + /** + * Expect 100 header. + */ + public static final HeaderValue EXPECT_100 = HeaderValue.createCached(Header.EXPECT, "100-continue"); + /** + * Content length with 0 value. + */ + public static final HeaderValue CONTENT_LENGTH_ZERO = HeaderValue.createCached(Header.CONTENT_LENGTH, "0"); + + private HeaderValues() { + } } /** @@ -1057,7 +1637,7 @@ public static final class DateTime { * This is standard for RFC2616 and all created headers MUST be in this format! However implementation must * accept headers also in RFC850 and ANSI C {@code asctime()} format. *

- * This is just copy of convenient copy of {@link DateTimeFormatter#RFC_1123_DATE_TIME}. + * This is just copy of convenient copy of {@link java.time.format.DateTimeFormatter#RFC_1123_DATE_TIME}. */ public static final DateTimeFormatter RFC_1123_DATE_TIME = DateTimeFormatter.RFC_1123_DATE_TIME; /** @@ -1072,6 +1652,9 @@ public static final class DateTime { */ private static final Map MONTH_NAME_3D; + private static volatile String rfc1123String; + private static volatile byte[] http1valueBytes; + static { Map map = new HashMap<>(); map.put(1L, "Jan"); @@ -1091,12 +1674,12 @@ public static final class DateTime { // manually code maps to ensure correct data always used // (locale data can be changed by application code) Map dayOfWeekFull = Map.of(1L, "Monday", - 2L, "Tuesday", - 3L, "Wednesday", - 4L, "Thursday", - 5L, "Friday", - 6L, "Saturday", - 7L, "Sunday"); + 2L, "Tuesday", + 3L, "Wednesday", + 4L, "Thursday", + 5L, "Friday", + 6L, "Saturday", + 7L, "Sunday"); RFC_850_DATE_TIME = new DateTimeFormatterBuilder() .parseCaseInsensitive() .parseLenient() @@ -1124,12 +1707,12 @@ public static final class DateTime { // manually code maps to ensure correct data always used // (locale data can be changed by application code) Map dayOfWeek3d = Map.of(1L, "Mon", - 2L, "Tue", - 3L, "Wed", - 4L, "Thu", - 5L, "Fri", - 6L, "Sat", - 7L, "Sun"); + 2L, "Tue", + 3L, "Wed", + 4L, "Thu", + 5L, "Fri", + 6L, "Sat", + 7L, "Sun"); ASCTIME_DATE_TIME = new DateTimeFormatterBuilder() .parseCaseInsensitive() .parseLenient() @@ -1150,20 +1733,35 @@ public static final class DateTime { .appendValue(YEAR, 4) .parseDefaulting(OFFSET_SECONDS, 0) .toFormatter(); + + update(); + + Thread thread = new Thread(() -> { + while (true) { + update(); + try { + TimeUnit.SECONDS.sleep(1); + } catch (InterruptedException e) { + return; + } + } + }, "helidon-http-timer"); + thread.setDaemon(true); + thread.start(); } private DateTime() { } /** - * Parse provided text to {@link ZonedDateTime} using any possible date / time format specified + * Parse provided text to {@link java.time.ZonedDateTime} using any possible date / time format specified * by RFC2616 Hypertext Transfer Protocol. *

* Formats are specified by {@link #RFC_1123_DATE_TIME}, {@link #RFC_850_DATE_TIME} and {@link #ASCTIME_DATE_TIME}. * * @param text a text to parse. * @return parsed date time. - * @throws DateTimeParseException if not in any of supported formats. + * @throws java.time.format.DateTimeParseException if not in any of supported formats. */ public static ZonedDateTime parse(String text) { try { @@ -1176,5 +1774,29 @@ public static ZonedDateTime parse(String text) { } } } + + /** + * Get current time as RFC-1123 string. + * + * @return formatted current time + * @see #RFC_1123_DATE_TIME + */ + public static String rfc1123String() { + return rfc1123String; + } + + /** + * Formatted date time terminated by carriage return and new line. + * + * @return date bytes for HTTP/1 + */ + public static byte[] http1Bytes() { + return http1valueBytes; + } + + static void update() { + rfc1123String = ZonedDateTime.now().format(RFC_1123_DATE_TIME); + http1valueBytes = (rfc1123String + "\r\n").getBytes(StandardCharsets.US_ASCII); + } } } diff --git a/common/http/src/main/java/io/helidon/common/http/HttpRequest.java b/common/http/src/main/java/io/helidon/common/http/HttpRequest.java deleted file mode 100644 index 144286fe57c..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/HttpRequest.java +++ /dev/null @@ -1,133 +0,0 @@ -/* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import java.net.URI; -import java.util.List; - -/** - * Common attributes of an HTTP Request, that are used both in server requests and in client requests. - */ -public interface HttpRequest { - /** - * Returns an HTTP request method. See also {@link Http.Method HTTP standard methods} utility class. - * - * @return an HTTP method - * @see Http.Method - */ - Http.RequestMethod method(); - - /** - * Returns an HTTP version from the request line. - *

- * See {@link Http.Version HTTP Version} enumeration for supported versions. - *

- * If communication starts as a {@code HTTP/1.1} with {@code h2c} upgrade, then it will be automatically - * upgraded and this method returns {@code HTTP/2.0}. - * - * @return an HTTP version - */ - Http.Version version(); - - /** - * Returns a Request-URI (or alternatively path) as defined in request line. - * - * @return a request URI - */ - URI uri(); - - /** - * Returns an encoded query string without leading '?' character. - * - * @return an encoded query string - */ - String query(); - - /** - * Returns query parameters. - * - * @return an parameters representing query parameters - */ - Parameters queryParams(); - - /** - * Returns a path which was accepted by matcher in actual routing. It is path without a context root - * of the routing. - *

- * Use {@link Path#absolute()} method to obtain absolute request URI path representation. - *

- * Returned {@link Path} also provide access to path template parameters. An absolute path then provides access to - * all (including) context parameters if any. In case of conflict between parameter names, most recent value is returned. - * - * @return a path - */ - Path path(); - - /** - * Returns a decoded request URI fragment without leading hash '#' character. - * - * @return a decoded URI fragment - */ - String fragment(); - - /** - * Represents requested normalised URI path. - */ - interface Path { - - /** - * Returns value of single parameter resolved from path pattern. - * - * @param name a parameter name - * @return a parameter value or {@code null} if not exist - */ - String param(String name); - - /** - * Returns path as a list of its segments. - * - * @return a list of path segments - */ - List segments(); - - /** - * Returns a path string representation with leading slash. - * - * @return a path - */ - String toString(); - - /** - * Returns a path string representation with leading slash without - * any character decoding. - * - * @return an undecoded path - */ - String toRawString(); - - /** - * If the instance represents a path relative to some context root then returns absolute requested path otherwise - * returns this instance. - *

- * The absolute path also contains access to path parameters defined in context matchers. If there is - * name conflict then value represents latest matcher result. - * - * @return an absolute requested URI path - */ - Path absolute(); - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/MediaType.java b/common/http/src/main/java/io/helidon/common/http/MediaType.java deleted file mode 100644 index 6dcf1d9616b..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/MediaType.java +++ /dev/null @@ -1,670 +0,0 @@ -/* - * Copyright (c) 2018, 2022 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.TreeMap; -import java.util.function.Predicate; - -/** - * An abstraction for a media type. Instances are immutable. - * - * @see HTTP/1.1 section 3.7 - */ -public final class MediaType implements AcceptPredicate { - - private static final Map KNOWN_TYPES; - - /** - * The media type {@value CHARSET_PARAMETER} parameter name. - */ - public static final String CHARSET_PARAMETER = "charset"; - - /** - * A {@link MediaType} constant representing wildcard media type. - */ - public static final MediaType WILDCARD; - - // Common media type constants - - /** - * A {@link MediaType} constant representing {@code application/xml} media type. - */ - public static final MediaType APPLICATION_XML; - - /** - * A {@link MediaType} constant representing {@code application/atom+xml} media type. - */ - public static final MediaType APPLICATION_ATOM_XML; - - /** - * A {@link MediaType} constant representing {@code application/xhtml+xml} media type. - */ - public static final MediaType APPLICATION_XHTML_XML; - - /** - * A {@link MediaType} constant representing {@code application/svg+xml} media type. - */ - public static final MediaType APPLICATION_SVG_XML; - - /** - * A {@link MediaType} constant representing {@code application/json} media type. - */ - public static final MediaType APPLICATION_JSON; - - /** - * A {@link MediaType} constant representing {@code application/stream+json} media type. - */ - public static final MediaType APPLICATION_STREAM_JSON; - - /** - * A {@link MediaType} constant representing {@code application/x-www-form-urlencoded} media type. - */ - public static final MediaType APPLICATION_FORM_URLENCODED; - - /** - * A {@link MediaType} constant representing {@code multipart/form-data} media type. - */ - public static final MediaType MULTIPART_FORM_DATA; - - /** - * A {@link MediaType} constant representing {@code application/octet-stream} media type. - */ - public static final MediaType APPLICATION_OCTET_STREAM; - - /** - * A {@link MediaType} constant representing {@code text/plain} media type. - */ - public static final MediaType TEXT_PLAIN; - - /** - * A {@link MediaType} constant representing {@code text/xml} media type. - */ - public static final MediaType TEXT_XML; - - /** - * A {@link MediaType} constant representing {@code text/html} media type. - */ - public static final MediaType TEXT_HTML; - - /** - * A {@link MediaType} constant representing OpenAPI yaml. - *

- * See https://github.com/opengeospatial/WFS_FES/issues/117#issuecomment-402188280 - */ - public static final MediaType APPLICATION_OPENAPI_YAML; - - /** - * A {@link MediaType} constant representing OpenAPI json. - */ - public static final MediaType APPLICATION_OPENAPI_JSON; - - /** - * A {@link MediaType} constant representing "x" YAML as application. - */ - public static final MediaType APPLICATION_X_YAML; - - /** - * A {@link MediaType} constant representing pseudo-registered YAML. (It is not actually registered.) - */ - public static final MediaType APPLICATION_YAML; - - /** - * A {@link MediaType} constant representing "x" YAML as text. - */ - public static final MediaType TEXT_X_YAML; - - /** - * A {@link MediaType} constant representing pseudo-registered YAML as text. - */ - public static final MediaType TEXT_YAML; - - /** - * A {@link MediaType} constant representing {@code application/javascript} media type. - */ - public static final MediaType APPLICATION_JAVASCRIPT; - - /** - * A {@link MediaType} constant representing {@code text/event-stream} media type. - */ - public static final MediaType TEXT_EVENT_STREAM; - - /** - * A {@link MediaType} constant representing {@code application/x-ndjson} media type. - */ - public static final MediaType APPLICATION_X_NDJSON; - - static { - Map knownTypes = new HashMap<>(); - - WILDCARD = new MediaType(AcceptPredicate.WILDCARD_VALUE, AcceptPredicate.WILDCARD_VALUE); - knownTypes.put(AcceptPredicate.WILDCARD_VALUE + '/' + AcceptPredicate.WILDCARD_VALUE, WILDCARD); - - APPLICATION_XML = new MediaType("application", "xml"); - knownTypes.put("application/xml", APPLICATION_XML); - - APPLICATION_ATOM_XML = new MediaType("application", "atom+xml"); - knownTypes.put("application/atom+xml", APPLICATION_ATOM_XML); - - APPLICATION_XHTML_XML = new MediaType("application", "xhtml+xml"); - knownTypes.put("application/xhtml+xml", APPLICATION_XHTML_XML); - - APPLICATION_SVG_XML = new MediaType("application", "svg+xml"); - knownTypes.put("application/svg+xml", APPLICATION_SVG_XML); - - APPLICATION_JSON = new MediaType("application", "json"); - knownTypes.put("application/json", APPLICATION_JSON); - - APPLICATION_STREAM_JSON = new MediaType("application", "stream+json"); - knownTypes.put("application/stream+json", APPLICATION_STREAM_JSON); - - APPLICATION_FORM_URLENCODED = new MediaType("application", "x-www-form-urlencoded"); - knownTypes.put("application/x-www-form-urlencoded", APPLICATION_FORM_URLENCODED); - - MULTIPART_FORM_DATA = new MediaType("multipart", "form-data"); - knownTypes.put("multipart/form-data", MULTIPART_FORM_DATA); - - APPLICATION_OCTET_STREAM = new MediaType("application", "octet-stream"); - knownTypes.put("application/octet-stream", APPLICATION_OCTET_STREAM); - - TEXT_PLAIN = new MediaType("text", "plain"); - knownTypes.put("text/plain", TEXT_PLAIN); - - TEXT_XML = new MediaType("text", "xml"); - knownTypes.put("text/xml", TEXT_XML); - - TEXT_HTML = new MediaType("text", "html"); - knownTypes.put("text/html", TEXT_HTML); - - APPLICATION_OPENAPI_YAML = new MediaType("application", "vnd.oai.openapi"); - knownTypes.put("application/vnd.oai.openapi", APPLICATION_OPENAPI_YAML); - - APPLICATION_OPENAPI_JSON = new MediaType("application", "vnd.oai.openapi+json"); - knownTypes.put("application/vnd.oai.openapi+json", APPLICATION_OPENAPI_JSON); - - APPLICATION_X_YAML = new MediaType("application", "x-yaml"); - knownTypes.put("application/x-yaml", APPLICATION_X_YAML); - - APPLICATION_YAML = new MediaType("application", "yaml"); - knownTypes.put("application/yaml", APPLICATION_YAML); - - TEXT_X_YAML = new MediaType("text", "x-yaml"); - knownTypes.put("text-x-yaml", TEXT_X_YAML); - - TEXT_YAML = new MediaType("text", "yaml"); - knownTypes.put("text-yaml", TEXT_YAML); - - APPLICATION_JAVASCRIPT = new MediaType("application", "javascript"); - knownTypes.put("application/javascript", APPLICATION_JAVASCRIPT); - - TEXT_EVENT_STREAM = new MediaType("text", "event-stream"); - knownTypes.put("text/event-stream", TEXT_EVENT_STREAM); - - APPLICATION_X_NDJSON = new MediaType("application", "x-ndjson"); - knownTypes.put("application/x-ndjson", APPLICATION_X_NDJSON); - - KNOWN_TYPES = Collections.unmodifiableMap(knownTypes); - } - - // Common predicates - /** - * Predicate to test if {@link MediaType} is {@code application/xml} or {@code text/xml} or has {@code xml} suffix. - */ - public static final Predicate XML_PREDICATE = APPLICATION_XML.or(TEXT_XML).or(mt -> mt.hasSuffix("xml")); - - /** - * Predicate to test if {@link MediaType} is {@code application/json} or has {@code json} suffix. - */ - public static final Predicate JSON_PREDICATE = APPLICATION_JSON - .or(mt -> mt.hasSuffix("json")); - - /** - * Predicate to test if {@link MediaType} is {@code text/event-stream} without any parameter or with parameter "element-type". - * This "element-type" has to be equal to "application/json". - */ - public static final Predicate JSON_EVENT_STREAM_PREDICATE = TEXT_EVENT_STREAM - .and(mt -> mt.hasSuffix("event-stream")) - .and(mt -> !mt.parameters().containsKey("element-type") - || "application/json".equals(mt.parameters().get("element-type"))); - - /** - * Matcher for type, subtype and attributes. - */ - private static final CharMatcher TOKEN_MATCHER = - CharMatcher.ascii() - .and(CharMatcher.javaIsoControl().negate()) - .and(CharMatcher.isNot(' ')) - .and(CharMatcher.noneOf("()<>@,;:\\\"/[]?=")); - private static final CharMatcher QUOTED_TEXT_MATCHER = CharMatcher.ascii().and(CharMatcher.noneOf("\"\\\r")); - /* - * This matches the same characters as linear-white-space from RFC 822, but we make no effort to - * enforce any particular rules with regards to line folding as stated in the class docs. - */ - private static final CharMatcher LINEAR_WHITE_SPACE = CharMatcher.anyOf(" \t\r\n"); - private static final String CHARSET_ATTRIBUTE = "charset"; - private final String type; - private final String subtype; - private final Map parameters; - - private MediaType(String type, String subtype) { - this.type = type; - this.subtype = subtype; - this.parameters = Map.of(); - } - - private MediaType(Builder builder) { - this.type = builder.type; - this.subtype = builder.subtype; - boolean charsetParam = builder.charset != null && !builder.charset.isEmpty(); - if (builder.parameters.isEmpty() && !charsetParam) { - this.parameters = Map.of(); - } else { - Map parameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - parameters.putAll(builder.parameters); - if (charsetParam) { - parameters.put(CHARSET_PARAMETER, builder.charset); - } - this.parameters = Collections.unmodifiableMap(parameters); - } - } - - /** - * Creates a new instance of {@code MediaType} with the supplied type and subtype. - * - * @param type the primary type, {@code null} is equivalent to - * {@link #WILDCARD_VALUE} - * @param subtype the subtype, {@code null} is equivalent to - * {@link #WILDCARD_VALUE} - * @return a new media type for the specified type and subtype - */ - public static MediaType create(String type, String subtype) { - return builder() - .type(type) - .subtype(subtype) - .build(); - } - - /** - * Parses a media type from its string representation. - * - * @param input the input string representing a media type - * @return parsed {@link MediaType} instance - * @throws IllegalArgumentException if the input is not parsable - * @throws NullPointerException if the input is {@code null} - */ - public static MediaType parse(String input) { - Objects.requireNonNull(input, "Parameter 'input' is null!"); - Tokenizer tokenizer = new Tokenizer(input); - try { - String type = tokenizer.consumeToken(TOKEN_MATCHER); - tokenizer.consumeCharacter('/'); - String subtype = tokenizer.consumeToken(TOKEN_MATCHER); - Map parameters = new HashMap<>(); - while (tokenizer.hasMore()) { - tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); - tokenizer.consumeCharacter(';'); - tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); - String attribute = tokenizer.consumeToken(TOKEN_MATCHER); - tokenizer.consumeCharacter('='); - final String value; - if (tokenizer.hasMore()) { - if ('"' == tokenizer.previewChar()) { - tokenizer.consumeCharacter('"'); - StringBuilder valueBuilder = new StringBuilder(); - while ('"' != tokenizer.previewChar()) { - if ('\\' == tokenizer.previewChar()) { - tokenizer.consumeCharacter('\\'); - valueBuilder.append(tokenizer.consumeCharacter(CharMatcher.ascii())); - } else { - valueBuilder.append(tokenizer.consumeToken(QUOTED_TEXT_MATCHER)); - } - } - value = valueBuilder.toString(); - tokenizer.consumeCharacter('"'); - } else { - value = tokenizer.consumeTokenIfPresent(TOKEN_MATCHER); - } - if (value != null) { - parameters.put(attribute, value); - } - } - } - return create(type, subtype, parameters); - } catch (IllegalStateException e) { - throw new IllegalArgumentException("Could not parse '" + input + "'", e); - } - } - - /** - * A fluent API builder for creating customized Media type instances. - * - * @return a new builder - */ - public static Builder builder() { - return new Builder(); - } - - private static String normalizeParameterValue(String attribute, String value) { - return CHARSET_ATTRIBUTE.equals(attribute) ? Ascii.toLowerCase(value) : value; - } - - private static MediaType create(String type, String subtype, Map parameters) { - Objects.requireNonNull(type, "Parameter 'type' is null!"); - Objects.requireNonNull(subtype, "Parameter 'subtype' is null!"); - Objects.requireNonNull(parameters, "Parameter 'parameters' is null!"); - - String normalizedType = Tokenizer.normalize(TOKEN_MATCHER, type); - String normalizedSubtype = Tokenizer.normalize(TOKEN_MATCHER, subtype); - if (WILDCARD.type.equals(normalizedType) - && !WILDCARD.type.equals(normalizedSubtype)) { - throw new IllegalStateException( - "A wildcard type cannot be used with a non-wildcard subtype"); - } - - MediaType mediaType = null; - Map normalizedParameters = null; - if (parameters.isEmpty()) { - // Return one of the constants if the media type is a known type. - mediaType = KNOWN_TYPES.get(normalizedType + '/' + normalizedSubtype); - } else { - normalizedParameters = new HashMap<>(); - for (Map.Entry entry : parameters.entrySet()) { - String attribute = Tokenizer.normalize(TOKEN_MATCHER, entry.getKey()); - normalizedParameters.put(attribute, normalizeParameterValue(attribute, entry.getValue())); - } - } - if (mediaType == null) { - MediaType.Builder builder = MediaType.builder() - .type(normalizedType) - .subtype(normalizedSubtype); - if (normalizedParameters != null) { - builder.parameters(normalizedParameters); - } - mediaType = builder.build(); - } - return mediaType; - } - - /** - * Getter for primary type. - * - * @return value of primary type. - */ - public String type() { - return this.type; - } - - /** - * Checks if the primary type is a wildcard. - * - * @return true if the primary type is a wildcard. - */ - public boolean isWildcardType() { - return this.type().equals(AcceptPredicate.WILDCARD_VALUE); - } - - /** - * Getter for subtype. - * - * @return value of subtype. - */ - public String subtype() { - return this.subtype; - } - - /** - * Checks if the subtype is a wildcard. - * - * @return true if the subtype is a wildcard. - */ - public boolean isWildcardSubtype() { - return this.subtype().equals(AcceptPredicate.WILDCARD_VALUE); - } - - /** - * Getter for a read-only parameter map. Keys are case-insensitive. - * - * @return an immutable map of parameters. - */ - public Map parameters() { - return parameters; - } - - /** - * Create a new {@code MediaType} instance with the same type, subtype and parameters - * copied from the original instance and the supplied {@value #CHARSET_PARAMETER} parameter. - * - * @param charset the {@value #CHARSET_PARAMETER} parameter value. If {@code null} or empty - * the {@value #CHARSET_PARAMETER} parameter will not be set or updated. - * @return copy of the current {@code MediaType} instance with the {@value #CHARSET_PARAMETER} - * parameter set to the supplied value. - * @since 2.0 - */ - public MediaType withCharset(String charset) { - return MediaType.builder() - .type(this.type) - .subtype(this.subtype) - .charset(charset) - .parameters(this.parameters) - .build(); - } - - /** - * Gets {@link Optional} value of charset parameter. - * - * @return Charset parameter. - */ - public Optional charset() { - return Optional.ofNullable(parameters.get(CHARSET_PARAMETER)); - } - - @Override - public double qualityFactor() { - String q = parameters.get(AcceptPredicate.QUALITY_FACTOR_PARAMETER); - return q == null ? 1D : Double.parseDouble(q); - } - - /** - * Check if this media type is compatible with another media type. E.g. - * image/* is compatible with image/jpeg, image/png, etc. Media type - * parameters are ignored. The function is commutative. - * - * @param other the media type to compare with. - * @return true if the types are compatible, false otherwise. - */ - // fixme: Bidirectional wildcard compatibility - @Override - public boolean test(MediaType other) { - return other != null // return false if other is null, else - && ( - type.equals(AcceptPredicate.WILDCARD_VALUE) - || other.type.equals(AcceptPredicate.WILDCARD_VALUE) - || ( - type.equalsIgnoreCase(other.type) - && ( - subtype.equals(AcceptPredicate.WILDCARD_VALUE) || other.subtype - .equals(AcceptPredicate.WILDCARD_VALUE))) - || (type.equalsIgnoreCase(other.type) && this.subtype.equalsIgnoreCase(other.subtype))); - } - - /** - * Compares {@code obj} to this media type to see if they are the same by comparing - * type, subtype and parameters. Note that the case-sensitivity of parameter - * values is dependent on the semantics of the parameter name, see - * HTTP/1.1. - * This method assumes that values are case-sensitive. - *

- * Note that the {@code equals(...)} implementation does not perform - * a class equality check ({@code this.getClass() == obj.getClass()}). Therefore - * any class that extends from {@code MediaType} class and needs to override - * one of the {@code equals(...)} and {@link #hashCode()} methods must - * always override both methods to ensure the contract between - * {@link Object#equals(java.lang.Object)} and {@link Object#hashCode()} does - * not break. - * - * @param obj the object to compare to. - * @return true if the two media types are the same, false otherwise. - */ - @SuppressWarnings("UnnecessaryJavaDocLink") - @Override - public boolean equals(Object obj) { - if (!(obj instanceof MediaType)) { - return false; - } - - MediaType other = (MediaType) obj; - return ( - this.type.equalsIgnoreCase(other.type) - && this.subtype.equalsIgnoreCase(other.subtype) - && this.parameters.equals(other.parameters)); - } - - /** - * Generate a hash code from the type, subtype and parameters. - *

- * Note that the {@link #equals(java.lang.Object)} implementation does not perform - * a class equality check ({@code this.getClass() == obj.getClass()}). Therefore - * any class that extends from {@code MediaType} class and needs to override - * one of the {@link #equals(Object)} and {@code hashCode()} methods must - * always override both methods to ensure the contract between - * {@link Object#equals(java.lang.Object)} and {@link Object#hashCode()} does - * not break. - * - * @return a generated hash code. - */ - @SuppressWarnings("UnnecessaryJavaDocLink") - @Override - public int hashCode() { - return (this.type.toLowerCase() + this.subtype.toLowerCase()).hashCode() + this.parameters.hashCode(); - } - - /** - * Convert the media type to a string suitable for use as the value of a corresponding HTTP header. - * - * @return a string version of the media type. - */ - @Override - public String toString() { - StringBuilder result = new StringBuilder(); - result.append(type).append('/').append(subtype); - for (Map.Entry entry : parameters.entrySet()) { - result.append(';').append(entry.getKey()).append('=').append(entry.getValue()); - } - return result.toString(); - } - - /** - * Tests if this media type has provided Structured Syntax {@code suffix} (RFC 6839). - * - * @param suffix Suffix with or without '+' prefix. If null or empty then returns {@code true} if this media type - * has ANY suffix. - * @return {@code true} if media type has specified {@code suffix} or has any suffix if parameter is {@code null} or empty. - */ - public boolean hasSuffix(String suffix) { - if (suffix != null && !suffix.isEmpty()) { - if (suffix.charAt(0) != '+') { - suffix = "+" + suffix; - } - return subtype.endsWith(suffix); - } else { - return subtype.indexOf('+') >= 0; - } - } - - /** - * A fluent API builder to create instances of {@link MediaType}. - */ - public static final class Builder implements io.helidon.common.Builder { - private String type = AcceptPredicate.WILDCARD_VALUE; - private String subtype = AcceptPredicate.WILDCARD_VALUE; - private String charset; - private TreeMap parameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - - private Builder() { - } - - @Override - public MediaType build() { - return new MediaType(this); - } - - /** - * Type of the new media type. - * - * @param type the primary type, default is {@value #WILDCARD_VALUE} - * @return updated builder instance - */ - public Builder type(String type) { - this.type = type; - return this; - } - - /** - * Subtype of the new media type. - * - * @param subtype the secondary type, default is {@value #WILDCARD_VALUE} - * @return updated builder instance - */ - public Builder subtype(String subtype) { - this.subtype = subtype; - return this; - } - - /** - * Character set of the media type. - * - * @param charset the {@value #CHARSET_PARAMETER} parameter value. By default - * the {@value #CHARSET_PARAMETER} parameter will not be set. - * @return updated builder instance - */ - public Builder charset(String charset) { - this.charset = charset; - return this; - } - - /** - * Add a new parameter to the parameter map. - * - * @param parameter name of the parameter to add - * @param value value of the parameter to add - * @return updated builder instance - */ - public Builder addParameter(String parameter, String value) { - parameters.put(parameter.toLowerCase(), value); - return this; - } - - /** - * Parameters of the media type. - * - * @param parameters a map of media type parameters, default is empty - * @return updated builder instance - */ - public Builder parameters(Map parameters) { - this.parameters.clear(); - parameters.forEach((key, value) -> { - this.parameters.put(key.toLowerCase(), value); - }); - - return this; - } - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/Parameters.java b/common/http/src/main/java/io/helidon/common/http/Parameters.java deleted file mode 100644 index 03da50e904a..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/Parameters.java +++ /dev/null @@ -1,232 +0,0 @@ -/* - * Copyright (c) 2018, 2022 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.function.Function; - -/** - * Parameters represents {@code key : value} pairs where {@code key} is a {@code String} with potentially multiple values. - *

- * This structure represents query parameters, headers and path parameters in e.g. {@link HttpRequest}. - *

- * Interface focus on most convenient use cases in HTTP Request and Response processing, like - *

- * {@code
- * // Get and map with default
- * .first("count").map(Integer::new).orElse(0);
- * // Find max in multiple values
- * .all("counts").stream().mapToInt(Integer::valueOf).max().orElse(0);
- * }
- * 
- *

- * Mutable operations are defined in two forms: - *

    - *
  • {@code put...} create or replace association.
  • - *
  • {@code add...} create association or add values to existing association.
  • - *
- *

- * It is possible to use {@link #toMap()} method to get immutable map view of data. - *

- * Various static factory methods can be used to create common implementations. - */ -public interface Parameters { - - /** - * Returns an unmodifiable view. - * - * @param parameters a parameters for unmodifiable view. - * @return An unmodifiable view. - * @throws NullPointerException if parameter {@code parameters} is null. - */ - static Parameters toUnmodifiableParameters(Parameters parameters) { - Objects.requireNonNull(parameters, "Parameter 'parameters' is null!"); - return new UnmodifiableParameters(parameters); - } - - /** - * Returns an {@link Optional} containing the first value of the given - * parameter (and possibly multi-valued) parameter. If the parameter is - * not present, then the returned Optional is empty. - * - * @param name the parameter name - * @return an {@code Optional} for the first named value - * @throws NullPointerException if name is {@code null} - */ - Optional first(String name); - - /** - * Returns an unmodifiable List of all of the values of the given named - * parameter. Always returns a List, which may be empty if the parameter - * is not present. - * - * @param name the parameter name - * @return a {@code List} of values with zero or greater size - * @throws NullPointerException if name is {@code null} - */ - List all(String name); - - /** - * Associates specified values with the specified key (optional operation). - * If parameters previously contained a mapping for the key, the old values fully replaced. - * - * @param key key with which the specified value is to be associated - * @param values value to be associated with the specified key - * @return the previous values associated with key, or empty {@code List} if there was no mapping for key. - * @throws NullPointerException if the specified key is null. - * @throws UnsupportedOperationException if put operation is not supported (unmodifiable Parameters). - */ - List put(String key, String... values); - - /** - * Associates specified values with the specified key (optional operation). - * If parameters previously contained a mapping for the key, the old values fully replaced. - * - * @param key key with which the specified value is to be associated - * @param values value to be associated with the specified key. If {@code null} then association will be removed. - * @return the previous values associated with key, or empty {@code List} if there was no mapping for key. - * @throws NullPointerException if the specified key is null. - * @throws UnsupportedOperationException if put operation is not supported (unmodifiable Parameters). - */ - List put(String key, Iterable values); - - /** - * If the specified key is not already associated with a value associates it with the given value and returns empty - * {@code List}, else returns the current value (optional operation). - * - * @param key key with which the specified value is to be associated - * @param values value to be associated with the specified key - * @return the previous values associated with key, or empty {@code List} if there was no mapping for key. - * @throws NullPointerException if the specified key is null. - * @throws UnsupportedOperationException if put operation is not supported (unmodifiable Parameters). - */ - List putIfAbsent(String key, String... values); - - /** - * If the specified key is not already associated with a value associates it with the given value and returns empty - * {@code List}, else returns the current value (optional operation). - * - * @param key key with which the specified value is to be associated - * @param values value to be associated with the specified key - * @return the previous values associated with key, or empty {@code List} if there was no mapping for key. - * @throws NullPointerException if the specified key is null. - * @throws UnsupportedOperationException if put operation is not supported (unmodifiable Parameters). - */ - List putIfAbsent(String key, Iterable values); - - /** - * If the specified key is not already associated with a value computes new association using the given function and returns - * empty {@code List}, else returns the current value (optional operation). - * - * @param key key with which the specified value is to be associated - * @param values value to be associated with the specified key - * @return the current (potentially computed) values associated with key, - * or empty {@code List} if function returns {@code null} - * @throws NullPointerException if the specified key is null - * @throws UnsupportedOperationException if put operation is not supported (unmodifiable Parameters) - * @throws IllegalStateException if the computation detectably - * attempts a recursive update to this map that would - * otherwise never complete - * @throws RuntimeException or Error if the mappingFunction does so, - * in which case the mapping is left unestablished - */ - List computeIfAbsent(String key, Function> values); - - /** - * If the specified key is not already associated with a value computes new association using the given function and returns - * empty {@code List}, else returns the current value (optional operation). - * - * @param key a key with which the specified value is to be associated - * @param value a single value to be associated with the specified key - * @return the current (potentially computed) values associated with key, - * or empty {@code List} if function returns {@code null} - * @throws NullPointerException if the specified key is null. - * @throws UnsupportedOperationException if put operation is not supported (unmodifiable Parameters). - * @throws IllegalStateException if the computation detectably - * attempts a recursive update to this map that would - * otherwise never complete - * @throws RuntimeException or Error if the mappingFunction does so, - * in which case the mapping is left unestablished - */ - List computeSingleIfAbsent(String key, Function value); - - /** - * Copies all of the mappings from the specified {@code parameters} to this instance replacing values of existing associations - * (optional operation). - * - * @param parameters to copy. - * @return this instance of {@link Parameters} - * @throws NullPointerException if the specified {@code parameters} are null. - * @throws UnsupportedOperationException if put operation is not supported (unmodifiable Parameters). - */ - Parameters putAll(Parameters parameters); - - /** - * Adds specified values to association with the specified key (optional operation). - * If parameters doesn't contains mapping, new mapping is created. - * - * @param key key with which the specified value is to be associated - * @param values value to be add to association with the specified key - * @return this instance of {@link Parameters} - * @throws NullPointerException if the specified key is null. - * @throws UnsupportedOperationException if put operation is not supported (unmodifiable Parameters). - */ - Parameters add(String key, String... values); - - /** - * Adds specified values to association with the specified key (optional operation). - * If parameters doesn't contains mapping, new mapping is created. - * - * @param key key with which the specified value is to be associated - * @param values value to be add to association with the specified key. If {@code null} then noting will be add. - * @return this instance of {@link Parameters} - * @throws NullPointerException if the specified key is null. - * @throws UnsupportedOperationException if put operation is not supported (unmodifiable Parameters). - */ - Parameters add(String key, Iterable values); - - /** - * Copies all of the mappings from the specified {@code parameters} to this instance adding values to existing associations - * (optional operation). - * - * @param parameters to copy. - * @return this instance of {@link Parameters} - * @throws NullPointerException if the specified {@code parameters} are null. - * @throws UnsupportedOperationException if put operation is not supported (unmodifiable Parameters). - */ - Parameters addAll(Parameters parameters); - - /** - * Removes the mapping for a key if it is present (optional operation). - * - * @param key key whose mapping is to be removed. - * @return the previous value associated with key, or empty {@code List}. - */ - List remove(String key); - - /** - * Returns a copy of parameters as a Map. This - * interface should only be used when it is required to iterate over the - * entire set of parameters. - * - * @return the {@code Map} - */ - Map> toMap(); -} diff --git a/common/http/src/main/java/io/helidon/common/http/Preconditions.java b/common/http/src/main/java/io/helidon/common/http/Preconditions.java deleted file mode 100644 index 14541c97979..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/Preconditions.java +++ /dev/null @@ -1,208 +0,0 @@ -/* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -/** - * Copied from Guava. - *

- * Static convenience methods that help a method or constructor check whether it was invoked - * correctly (whether its preconditions have been met). These methods generally accept a - * {@code boolean} expression which is expected to be {@code true} (or in the case of {@code - * checkNotNull}, an object reference which is expected to be non-null). When {@code false} (or - * {@code null}) is passed instead, the {@code Preconditions} method throws an unchecked exception, - * which helps the calling method communicate to its caller that that caller has made - * a mistake. Example:

   {@code
- *
- *   /**
- *    * Returns the positive square root of the given value.
- *    *
- *    * @throws IllegalArgumentException if the value is negative
- *    *}{@code /
- *   public static double sqrt(double value) {
- *     Preconditions.checkArgument(value >= 0.0, "negative value: %s", value);
- *     // calculate the square root
- *   }
- *
- *   void exampleBadCaller() {
- *     double d = sqrt(-1.0);
- *   }}
- * - * In this example, {@code checkArgument} throws an {@code IllegalArgumentException} to indicate - * that {@code exampleBadCaller} made an error in its call to {@code sqrt}. - * - *

Warning about performance

- * - *

The goal of this class is to improve readability of code, but in some circumstances this may - * come at a significant performance cost. Remember that parameter values for message construction - * must all be computed eagerly, and autoboxing and varargs array creation may happen as well, even - * when the precondition check then succeeds (as it should almost always do in production). In some - * circumstances these wasted CPU cycles and allocations can add up to a real problem. - * Performance-sensitive precondition checks can always be converted to the customary form: - *

   {@code
- *
- *   if (value < 0.0) {
- *     throw new IllegalArgumentException("negative value: " + value);
- *   }}
- * - *

Other types of preconditions

- * - *

Not every type of precondition failure is supported by these methods. Continue to throw - * standard JDK exceptions such as {@link java.util.NoSuchElementException} or - * {@link UnsupportedOperationException} in the situations they are intended for. - * - *

Non-preconditions

- * - *

It is of course possible to use the methods of this class to check for invalid conditions - * which are not the caller's fault. Doing so is not recommended because it is - * misleading to future readers of the code and of stack traces. See - * Conditional failures - * explained in the Guava User Guide for more advice. - * - *

Only {@code %s} is supported

- * - *

In {@code Preconditions} error message template strings, only the {@code "%s"} specifier is - * supported, not the full range of {@link java.util.Formatter} specifiers. - * - *

More information

- * - *

See the Guava User Guide on - * using {@code - * Preconditions}. - * - * @author Kevin Bourrillion - */ -final class Preconditions { - - private Preconditions() { - } - - /** - * Ensures that {@code index} specifies a valid position in an array, list or string of - * size {@code size}. A position index may range from zero to {@code size}, inclusive. - * - * @param index a user-supplied index identifying a position in an array, list or string - * @param size the size of that array, list or string - * @return the value of {@code index} - * @throws IndexOutOfBoundsException if {@code index} is negative or is greater than {@code size} - * @throws IllegalArgumentException if {@code size} is negative - */ - public static int checkPositionIndex(int index, int size) { - return checkPositionIndex(index, size, "index"); - } - - /** - * Ensures that {@code index} specifies a valid position in an array, list or string of - * size {@code size}. A position index may range from zero to {@code size}, inclusive. - * - * @param index a user-supplied index identifying a position in an array, list or string - * @param size the size of that array, list or string - * @param desc the text to use to describe this index in an error message - * @return the value of {@code index} - * @throws IndexOutOfBoundsException if {@code index} is negative or is greater than {@code size} - * @throws IllegalArgumentException if {@code size} is negative - */ - public static int checkPositionIndex(int index, int size, String desc) { - // Carefully optimized for execution by hotspot (explanatory comment above) - if (index < 0 || index > size) { - throw new IndexOutOfBoundsException(badPositionIndex(index, size, desc)); - } - return index; - } - - /** - * Ensures that {@code start} and {@code end} specify a valid positions in an array, list - * or string of size {@code size}, and are in order. A position index may range from zero to - * {@code size}, inclusive. - * - * @param start a user-supplied index identifying a starting position in an array, list or string - * @param end a user-supplied index identifying a ending position in an array, list or string - * @param size the size of that array, list or string - * @throws IndexOutOfBoundsException if either index is negative or is greater than {@code size}, - * or if {@code end} is less than {@code start} - * @throws IllegalArgumentException if {@code size} is negative - */ - public static void checkPositionIndexes(int start, int end, int size) { - // Carefully optimized for execution by hotspot (explanatory comment above) - if (start < 0 || end < start || end > size) { - throw new IndexOutOfBoundsException(badPositionIndexes(start, end, size)); - } - } - - private static String badPositionIndexes(int start, int end, int size) { - if (start < 0 || start > size) { - return badPositionIndex(start, size, "start index"); - } - if (end < 0 || end > size) { - return badPositionIndex(end, size, "end index"); - } - // end < start - return format("end index (%s) must not be less than start index (%s)", end, start); - } - - private static String badPositionIndex(int index, int size, String desc) { - if (index < 0) { - return format("%s (%s) must not be negative", desc, index); - } else if (size < 0) { - throw new IllegalArgumentException("negative size: " + size); - } else { // index > size - return format("%s (%s) must not be greater than size (%s)", desc, index, size); - } - } - - /** - * Substitutes each {@code %s} in {@code template} with an argument. These are matched by - * position: the first {@code %s} gets {@code args[0]}, etc. If there are more arguments than - * placeholders, the unmatched arguments will be appended to the end of the formatted message in - * square braces. - * - * @param template a non-null string containing 0 or more {@code %s} placeholders. - * @param args the arguments to be substituted into the message template. Arguments are converted - * to strings using {@link String#valueOf(Object)}. Arguments can be null. - */ - // Note that this is somewhat-improperly used from Verify.java as well. - static String format(String template, Object... args) { - template = String.valueOf(template); // null -> "null" - - // start substituting the arguments into the '%s' placeholders - StringBuilder builder = new StringBuilder(template.length() + 16 * args.length); - int templateStart = 0; - int i = 0; - while (i < args.length) { - int placeholderStart = template.indexOf("%s", templateStart); - if (placeholderStart == -1) { - break; - } - builder.append(template, templateStart, placeholderStart); - builder.append(args[i++]); - templateStart = placeholderStart + 2; - } - builder.append(template, templateStart, template.length()); - - // if we run out of placeholders, append the extra args in square braces - if (i < args.length) { - builder.append(" ["); - builder.append(args[i++]); - while (i < args.length) { - builder.append(", "); - builder.append(args[i++]); - } - builder.append(']'); - } - - return builder.toString(); - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/ReadOnlyParameters.java b/common/http/src/main/java/io/helidon/common/http/ReadOnlyParameters.java deleted file mode 100644 index c48d3ba4b14..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/ReadOnlyParameters.java +++ /dev/null @@ -1,158 +0,0 @@ -/* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import java.util.ArrayList; -import java.util.Collections; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.TreeMap; -import java.util.function.Function; - -/** - * An immutable implementation of {@link Parameters}. - * - * @see Parameters - */ -public class ReadOnlyParameters implements Parameters { - - /** - * Empty, immutable parameters. - */ - private static final ReadOnlyParameters EMPTY = new ReadOnlyParameters((Parameters) null); - - private final Map> data; - - /** - * Creates an instance from provided multi-map. - * - * @param data multi-map data to copy. - */ - public ReadOnlyParameters(Map> data) { - this.data = copyMultimapAsImutable(data); - } - - /** - * Creates an instance from provided multi-map. - * - * @param parameters parameters to copy. - */ - public ReadOnlyParameters(Parameters parameters) { - this(parameters == null ? null : parameters.toMap()); - } - - /** - * Returns empty and immutable singleton. - * - * @return the parameters singleton instance which is empty and immutable. - */ - public static ReadOnlyParameters empty() { - return EMPTY; - } - - /** - * Returns a deep copy of provided multi-map which is completely unmodifiable. - * - * @param data data to copy, if {@code null} then returns empty map. - * @return unmodifiable map, never {@code null}. - */ - static Map> copyMultimapAsImutable(Map> data) { - if (data == null || data.isEmpty()) { - return Collections.emptyMap(); - } else { - // Deep copy - Map> h = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); - data.forEach((k, v) -> h.put(k, Collections.unmodifiableList(new ArrayList<>(v)))); - return Collections.unmodifiableMap(h); - } - } - - @Override - public Optional first(String name) { - return Optional.ofNullable(data.get(name)).map(l -> - !l.isEmpty() ? l.get(0) : null); - } - - @Override - public List all(String name) { - return Optional.ofNullable(data.get(name)).orElse(Collections.emptyList()); - } - - @Override - public List put(String key, String... values) { - throw new UnsupportedOperationException(); - } - - @Override - public List put(String key, Iterable values) { - throw new UnsupportedOperationException(); - } - - @Override - public List putIfAbsent(String key, String... values) { - throw new UnsupportedOperationException(); - } - - @Override - public List putIfAbsent(String key, Iterable values) { - throw new UnsupportedOperationException(); - } - - @Override - public List computeIfAbsent(String key, Function> values) { - throw new UnsupportedOperationException(); - } - - @Override - public ReadOnlyParameters putAll(Parameters parameters) { - throw new UnsupportedOperationException(); - } - - @Override - public ReadOnlyParameters add(String key, String... values) { - throw new UnsupportedOperationException(); - } - - @Override - public ReadOnlyParameters add(String key, Iterable values) { - throw new UnsupportedOperationException(); - } - - @Override - public List computeSingleIfAbsent(String key, Function value) { - throw new UnsupportedOperationException(); - } - - @Override - public ReadOnlyParameters addAll(Parameters parameters) { - throw new UnsupportedOperationException(); - } - - @Override - public List remove(String key) { - throw new UnsupportedOperationException(); - } - - @Override - public Map> toMap() { - Map> h = new HashMap<>(data.size()); - data.forEach((k, v) -> h.put(k, new ArrayList<>(v))); - return h; - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/Reader.java b/common/http/src/main/java/io/helidon/common/http/Reader.java deleted file mode 100644 index 1932aeb00a4..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/Reader.java +++ /dev/null @@ -1,82 +0,0 @@ -/* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import java.util.concurrent.CompletionStage; -import java.util.concurrent.Flow; -import java.util.function.BiFunction; - -/** - * The Reader transforms a {@link DataChunk} publisher into a completion stage of the associated type. - * - * @param the requested type - * @deprecated since 2.0.0, use {@code io.helidon.media.common.MessageBodyReader} instead - */ -@FunctionalInterface -@Deprecated -public interface Reader extends BiFunction, Class, CompletionStage> { - - /** - * Transforms a publisher into a completion stage. - * If an exception is thrown, the resulting completion stage of - * {@link Content#as(Class)} method call ends exceptionally. - * - * @param publisher the publisher to transform - * @param clazz the requested type to be returned as a completion stage. The purpose of - * this parameter is to know what the user of this Reader actually requested. - * @return the result as a completion stage - */ - @Override - CompletionStage apply(Flow.Publisher publisher, Class clazz); - - /** - * Transforms a publisher into a completion stage. - * If an exception is thrown, the resulting completion stage of - * {@link Content#as(Class)} method call ends exceptionally. - *

- * The default implementation calls {@link #apply(Flow.Publisher, Class)} with {@link Object} as - * the class parameter. - * - * @param publisher the publisher to transform - * @return the result as a completion stage - */ - default CompletionStage apply(Flow.Publisher publisher) { - return apply(publisher, Object.class); - } - - /** - * Transforms a publisher into a completion stage. - * If an exception is thrown, the resulting completion stage of - * {@link Content#as(Class)} method call ends exceptionally. - *

- * The default implementation calls {@link #apply(Flow.Publisher, Class)} with {@link Object} as - * the class parameter. - * - * @param publisher the publisher to transform - * @param type the desired type to cast the guarantied {@code R} type to - * @param the desired type to cast the guarantied {@code R} type to - * @return the result as a completion stage which might end exceptionally with - * {@link ClassCastException} if the {@code R} type wasn't possible to cast - * to {@code T} - */ - @SuppressWarnings("unchecked") - default CompletionStage applyAndCast(Flow.Publisher publisher, Class type) { - // if this was implemented as (CompletionStage) apply(publisher, (Class) clazz); - // the class cast exception might occur outside of the completion stage which might be confusing - return apply(publisher, (Class) type).thenApply(type::cast); - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/SetCookie.java b/common/http/src/main/java/io/helidon/common/http/SetCookie.java index 5c855ae42f5..efe30fc2dff 100644 --- a/common/http/src/main/java/io/helidon/common/http/SetCookie.java +++ b/common/http/src/main/java/io/helidon/common/http/SetCookie.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -132,6 +132,15 @@ public static SetCookie parse(String setCookie) { return builder.build(); } + /** + * Text representation of this cookie. + * + * @return cookie text + */ + public String text() { + return toString(); + } + private static void hasNoValue(String partName, String partValue) { if (partValue != null) { throw new IllegalArgumentException("Set-Cookie parameter " + partName + " has to have no value!"); diff --git a/common/http/src/main/java/io/helidon/common/http/Tokenizer.java b/common/http/src/main/java/io/helidon/common/http/Tokenizer.java deleted file mode 100644 index b0f4fc67b22..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/Tokenizer.java +++ /dev/null @@ -1,176 +0,0 @@ -/* - * Copyright (c) 2018, 2022 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.common.http; - -import java.util.function.Supplier; - -/** - * String tokenizer for parsing headers. - */ -@SuppressWarnings("checkstyle:VisibilityModifier") -public final class Tokenizer { - - /** - * The input string. - */ - final String input; - - /** - * The current position. - */ - int position = 0; - - /** - * Create a new instance. - * @param input string to parse - */ - public Tokenizer(String input) { - this.input = input; - } - - /** - * Get the token represented by the specified matcher and advance the - * position the to next character if matched. - * - * @param matcher matcher to use - * @return token matched, or {@code null} if not matched - * @throws IllegalStateException if {@link #hasMore() } returns - * {@code false} - */ - public String consumeTokenIfPresent(CharMatcher matcher) { - checkState(hasMore(), "No more elements!"); - int startPosition = position; - position = matcher.negate().indexIn(input, startPosition); - return position == startPosition ? null - : hasMore() ? input.substring(startPosition, position) - : input.substring(startPosition); - } - - /** - * Get the token represented by the specified matcher and advance the - * position the to next character. - * - * @param matcher matcher to use - * @return IllegalStateException if {@link #hasMore() } returns - * {@code false} or if the matcher wasn't matched. - */ - public String consumeToken(CharMatcher matcher) { - int startPosition = position; - String token = consumeTokenIfPresent(matcher); - checkState(position != startPosition, () - -> String.format("Position '%d' should not be '%d'!", position, - startPosition)); - return token; - } - - /** - * Get the one character at the current position and matches it with the - * specified matcher, then update the position to the next character. - * - * @param matcher matcher to use - * @return consumed character - * @throws IllegalStateException if {@link #hasMore() } returns - * {@code false} or if the specified matcher does not match the character at - * the current position - */ - public char consumeCharacter(CharMatcher matcher) { - checkState(hasMore(), "No more elements!"); - char c = previewChar(); - checkState(matcher.matches(c), "Unexpected character matched: " + c); - position++; - return c; - } - - /** - * Get the one character at the current position and matches it with the - * specified character and update the position to the next character. - * - * @param c character to match - * @return matched character - * @throws IllegalStateException if {@link #hasMore() } returns - * {@code false} or if the specified character does not match the character - * at the current position - */ - public char consumeCharacter(char c) { - checkState(hasMore(), "No more elements!"); - checkState(previewChar() == c, () -> "Unexpected character: " + c); - position++; - return c; - } - - /** - * Get the character at the current position. - * @return char - * @throws IllegalStateException if {@link #hasMore() } returns {@code false} - */ - public char previewChar() { - checkState(hasMore(), "No more elements!"); - return input.charAt(position); - } - - /** - * Test if there are more characters to process. - * @return {@code true} if there are more characters to process, {@code false} - * otherwise - */ - public boolean hasMore() { - return (position >= 0) && (position < input.length()); - } - - /** - * Verify that the given token matches the specified matcher and return - * a lower case only token string. - * @param matcher matcher to use - * @param token input token - * @return normalized token string (lower case only) - */ - public static String normalize(CharMatcher matcher, String token) { - checkState(matcher.matchesAllOf(token), () - -> String.format( - "Parameter '%s' doesn't match token matcher: %s", - token, matcher)); - return Ascii.toLowerCase(token); - } - - /** - * Ensures the truth of an expression involving the state of the calling - * instance, but not involving any parameters to the calling method. - * - * @param expression a boolean expression - * @param message a message to pass to the {@link IllegalStateException} - * that is possibly thrown - * @throws IllegalStateException if {@code expression} is false - */ - private static void checkState(boolean expression, String message) { - checkState(expression, () -> message); - } - - /** - * Ensures the truth of an expression involving the state of the calling - * instance, but not involving any parameters to the calling method. - * - * @param expression a boolean expression - * @param messageSupplier a message to pass to the - * {@link IllegalStateException} that is possibly thrown - * @throws IllegalStateException if {@code expression} is false - */ - private static void checkState(boolean expression, - Supplier messageSupplier) { - if (!expression) { - throw new IllegalStateException(messageSupplier.get()); - } - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/UnmodifiableParameters.java b/common/http/src/main/java/io/helidon/common/http/UnmodifiableParameters.java deleted file mode 100644 index bb0c992eb2e..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/UnmodifiableParameters.java +++ /dev/null @@ -1,110 +0,0 @@ -/* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; - -/** - * Immutable delegate for {@link Parameters}. - */ -class UnmodifiableParameters implements Parameters { - - private final Parameters parameters; - - /** - * Creates new instance. - * - * @param parameters To delegate on. - */ - UnmodifiableParameters(Parameters parameters) { - this.parameters = parameters; - } - - @Override - public Optional first(String name) { - return parameters.first(name); - } - - @Override - public List all(String name) { - return parameters.all(name); - } - - @Override - public Map> toMap() { - return parameters.toMap(); - } - - @Override - public List computeSingleIfAbsent(String key, Function value) { - throw new UnsupportedOperationException(); - } - - @Override - public List put(String key, String... values) { - throw new UnsupportedOperationException(); - } - - @Override - public List put(String key, Iterable values) { - throw new UnsupportedOperationException(); - } - - @Override - public List putIfAbsent(String key, String... values) { - throw new UnsupportedOperationException(); - } - - @Override - public List putIfAbsent(String key, Iterable values) { - throw new UnsupportedOperationException(); - } - - @Override - public List computeIfAbsent(String key, Function> values) { - throw new UnsupportedOperationException(); - } - - @Override - public UnmodifiableParameters putAll(Parameters parameters) { - throw new UnsupportedOperationException(); - } - - @Override - public UnmodifiableParameters add(String key, String... values) { - throw new UnsupportedOperationException(); - } - - @Override - public UnmodifiableParameters add(String key, Iterable values) { - throw new UnsupportedOperationException(); - } - - @Override - public UnmodifiableParameters addAll(Parameters parameters) { - throw new UnsupportedOperationException(); - } - - @Override - public List remove(String key) { - throw new UnsupportedOperationException(); - } - -} diff --git a/common/http/src/main/java/io/helidon/common/http/UriComponent.java b/common/http/src/main/java/io/helidon/common/http/UriComponent.java deleted file mode 100644 index 1f64e728ae4..00000000000 --- a/common/http/src/main/java/io/helidon/common/http/UriComponent.java +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (c) 2022 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import java.net.URLDecoder; - -import static java.nio.charset.StandardCharsets.UTF_8; - -/** - * Extracted from Jersey - *

- * Utility class for validating, encoding and decoding components of a URI. - */ -public final class UriComponent { - - private UriComponent() { - } - - /** - * Decode the query component of a URI. - *

- * Query parameter names in the returned map are always decoded. Decoding of query parameter - * values can be controlled using the {@code decode} parameter flag. - * - * @param query the query component in encoded form. - * @param decode {@code true} if the returned query parameter values of the query component - * should be in decoded form. - * @return the multivalued map of query parameters. - */ - public static Parameters decodeQuery(String query, boolean decode) { - return decodeQuery(query, true, decode); - } - - /** - * Decode the query component of a URI. - *

- * Decoding of query parameter names and values can be controlled using the {@code decodeNames} - * and {@code decodeValues} parameter flags. - * - * @param query the query component in encoded form. - * @param decodeNames {@code true} if the returned query parameter names of the query component - * should be in decoded form. - * @param decodeValues {@code true} if the returned query parameter values of the query component - * should be in decoded form. - * @return the multivalued map of query parameters. - */ - public static Parameters decodeQuery(String query, boolean decodeNames, boolean decodeValues) { - Parameters queryParameters = HashParameters.create(); - - if (query == null || query.isEmpty()) { - return queryParameters; - } - - int s = 0; - do { - int e = query.indexOf('&', s); - - if (e == -1) { - decodeQueryParam(queryParameters, query.substring(s), decodeNames, decodeValues); - } else if (e > s) { - decodeQueryParam(queryParameters, query.substring(s, e), decodeNames, decodeValues); - } - s = e + 1; - } while (s > 0 && s < query.length()); - - return queryParameters; - } - - private static void decodeQueryParam(Parameters params, String param, boolean decodeNames, boolean decodeValues) { - int equals = param.indexOf('='); - if (equals > 0) { - params.add((decodeNames) ? URLDecoder.decode(param.substring(0, equals), UTF_8) : param.substring(0, equals), - (decodeValues) - ? URLDecoder.decode(param.substring(equals + 1), UTF_8) - : param.substring(equals + 1)); - } else if (equals == 0) { - // no key declared, ignore - return; - } else if (!param.isEmpty()) { - params.add((decodeNames) ? URLDecoder.decode(param, UTF_8) : param, ""); - } - } -} diff --git a/common/http/src/main/java/io/helidon/common/http/Utils.java b/common/http/src/main/java/io/helidon/common/http/Utils.java index f657c82b093..89b5176e877 100644 --- a/common/http/src/main/java/io/helidon/common/http/Utils.java +++ b/common/http/src/main/java/io/helidon/common/http/Utils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ /** * Internal utility methods. */ -public final class Utils { +final class Utils { static final Runnable EMPTY_RUNNABLE = () -> { }; @@ -43,7 +43,7 @@ private Utils() { * @param text a text to be tokenized. * @return A list of tokens without separator characters. */ - public static List tokenize(char separator, String quoteChars, boolean includeEmptyTokens, String text) { + static List tokenize(char separator, String quoteChars, boolean includeEmptyTokens, String text) { char[] quotes = quoteChars == null ? new char[0] : quoteChars.toCharArray(); StringBuilder token = new StringBuilder(); List result = new ArrayList<>(); @@ -86,7 +86,7 @@ public static List tokenize(char separator, String quoteChars, boolean i * @param str string to unwrap. * @return unwrapped string. */ - public static String unwrap(String str) { + static String unwrap(String str) { if (str.length() >= 2 && '"' == str.charAt(0) && '"' == str.charAt(str.length() - 1)) { return str.substring(1, str.length() - 1); } @@ -98,9 +98,9 @@ public static String unwrap(String str) { * * @param out the stream where to append the byte buffer * @param byteBuffer the byte buffer to append to the stream - * @throws IOException in case of an IO problem + * @throws java.io.IOException in case of an IO problem */ - public static void write(ByteBuffer byteBuffer, OutputStream out) throws IOException { + static void write(ByteBuffer byteBuffer, OutputStream out) throws IOException { if (byteBuffer.hasArray()) { out.write(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), byteBuffer.remaining()); } else { @@ -115,7 +115,7 @@ public static void write(ByteBuffer byteBuffer, OutputStream out) throws IOExcep * @param byteBuffer byte buffer * @return byte array */ - public static byte[] toByteArray(ByteBuffer byteBuffer) { + static byte[] toByteArray(ByteBuffer byteBuffer) { byte[] buff = new byte[byteBuffer.remaining()]; return toByteArray(byteBuffer, buff, 0); } diff --git a/common/http/src/main/java/module-info.java b/common/http/src/main/java/module-info.java index 1e41092c7db..24dbce21995 100644 --- a/common/http/src/main/java/module-info.java +++ b/common/http/src/main/java/module-info.java @@ -18,9 +18,11 @@ * Helidon Common classes for HTTP server and client. */ module io.helidon.common.http { - requires io.helidon.common; - requires io.helidon.common.context; - requires io.helidon.common.reactive; + requires transitive io.helidon.common; + requires transitive io.helidon.common.mapper; + requires transitive io.helidon.common.buffers; + requires transitive io.helidon.common.media.type; + requires transitive io.helidon.common.uri; exports io.helidon.common.http; } diff --git a/common/http/src/test/java/io/helidon/common/http/AsciiTest.java b/common/http/src/test/java/io/helidon/common/http/AsciiTest.java deleted file mode 100644 index e236721594a..00000000000 --- a/common/http/src/test/java/io/helidon/common/http/AsciiTest.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import org.junit.jupiter.api.Test; - -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; - -/** - * Unit tests for class {@link Ascii}. - * - * @see Ascii - */ -public class AsciiTest { - - @Test - public void testIsLowerCaseOne() { - assertFalse(Ascii.isLowerCase('{')); - } - - - @Test - public void testIsLowerCaseReturningTrue() { - assertTrue(Ascii.isLowerCase('o')); - } - - - @Test - public void testIsLowerCaseTwo() { - assertFalse(Ascii.isLowerCase('\"')); - } - - - @Test - public void testToLowerCaseTakingCharSequenceOne() { - StringBuilder stringBuilder = new StringBuilder("uhho^s} b'jdwtym"); - - assertEquals("uhho^s} b'jdwtym", Ascii.toLowerCase(stringBuilder)); - } - - - @Test - public void testToLowerCaseTakingCharSequenceTwo() { - assertEquals("uhho^s} b'jdwtym", Ascii.toLowerCase((CharSequence) "uHHO^S} b'jDwTYM")); - } - - - @Test - public void testToLowerCaseTakingString() { - assertEquals("", Ascii.toLowerCase("")); - } - -} diff --git a/common/http/src/test/java/io/helidon/common/http/FormParamsTest.java b/common/http/src/test/java/io/helidon/common/http/FormParamsTest.java deleted file mode 100644 index 5869fa180d9..00000000000 --- a/common/http/src/test/java/io/helidon/common/http/FormParamsTest.java +++ /dev/null @@ -1,98 +0,0 @@ -/* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.common.http; - -import java.util.List; -import java.util.Optional; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.hasItem; -import static org.hamcrest.CoreMatchers.hasItems; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -public class FormParamsTest { - - private static final String KEY1 = "key1"; - private static final String VAL1 = "value1"; - - private static final String KEY2 = "key2"; - private static final String VAL2_1 = "value2.1"; - private static final String VAL2_2 = "value2.2"; - - - @Test - void testOneLineSingleAssignment() { - FormParams fp = FormParams.create(KEY1 + "=" + VAL1, MediaType.TEXT_PLAIN); - - checkKey1(fp); - } - - @Test - void testTwoDifferentAssignments() { - String assignments = String.format("%s=%s\n%s=%s\n%s=%s", KEY1, VAL1, - KEY2, VAL2_1, KEY2, VAL2_2); - - FormParams fp = FormParams.create(assignments, MediaType.TEXT_PLAIN); - - checkKey1(fp); - checkKey2(fp); - } - - @Test - void testTwoDifferentAssignmentsURLEncoded() { - String assignments = String.format("%s=%s&%s=%s&%s=%s", KEY1, VAL1, KEY2, VAL2_1, KEY2, VAL2_2); - - FormParams fp = FormParams.create(assignments, MediaType.APPLICATION_FORM_URLENCODED); - - checkKey1(fp); - checkKey2(fp); - } - - @Test - void testAbsentKey() { - FormParams fp = FormParams.create(KEY1 + "=" + VAL1, MediaType.TEXT_PLAIN); - - Optional shouldNotExist = fp.first(KEY2); - assertThat(shouldNotExist.isPresent(), is(false)); - - List shouldBeEmpty = fp.all(KEY2); - assertThat(shouldBeEmpty.size(), is(0)); - - assertThrows(UnsupportedOperationException.class, () -> fp.computeSingleIfAbsent(KEY2, k -> "replacement")); - } - - private static void checkKey1(FormParams fp) { - Optional result = fp.first(KEY1); - assertThat(result.orElse("missing"), is(equalTo(VAL1))); - - List listResult = fp.all(KEY1); - assertThat(listResult, hasItem(VAL1)); - assertThat(listResult.size(), is(1)); - } - - private static void checkKey2(FormParams fp) { - Optional result = fp.first(KEY2); - assertThat(result.orElse("missing"), is(equalTo(VAL2_1))); - - List listResult = fp.all(KEY2); - assertThat(listResult, hasItems(VAL2_1, VAL2_2)); - } - -} diff --git a/common/http/src/test/java/io/helidon/common/http/HttpTest.java b/common/http/src/test/java/io/helidon/common/http/HttpTest.java index 2b901544888..6a6b9d21e51 100644 --- a/common/http/src/test/java/io/helidon/common/http/HttpTest.java +++ b/common/http/src/test/java/io/helidon/common/http/HttpTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,21 +29,21 @@ */ class HttpTest { @Test - void testResponseStatusIsStatus() { - Http.ResponseStatus rs = Http.ResponseStatus.create(TEMPORARY_REDIRECT_307.code()); + void testStatusIsStatus() { + Http.Status rs = Http.Status.create(TEMPORARY_REDIRECT_307.code()); assertThat(rs, sameInstance(TEMPORARY_REDIRECT_307)); } @Test - void testResponseStatusWithReasonIsStatus() { - Http.ResponseStatus rs = Http.ResponseStatus + void testStatusWithReasonIsStatus() { + Http.Status rs = Http.Status .create(TEMPORARY_REDIRECT_307.code(), TEMPORARY_REDIRECT_307.reasonPhrase().toUpperCase()); assertThat(rs, sameInstance(TEMPORARY_REDIRECT_307)); } @Test void testResposneStatusCustomReason() { - Http.ResponseStatus rs = Http.ResponseStatus + Http.Status rs = Http.Status .create(TEMPORARY_REDIRECT_307.code(), "Custom reason phrase"); assertThat(rs, not(TEMPORARY_REDIRECT_307)); assertThat(rs.reasonPhrase(), is("Custom reason phrase")); diff --git a/common/http/src/test/java/io/helidon/common/http/MediaTypeTest.java b/common/http/src/test/java/io/helidon/common/http/MediaTypeTest.java index f6deb45c929..9dfa09a4f38 100644 --- a/common/http/src/test/java/io/helidon/common/http/MediaTypeTest.java +++ b/common/http/src/test/java/io/helidon/common/http/MediaTypeTest.java @@ -19,7 +19,9 @@ import java.util.Map; import java.util.Optional; -import org.hamcrest.core.Is; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; + import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.is; @@ -34,28 +36,28 @@ */ class MediaTypeTest { @Test - void parseEquals() { - assertThat(MediaType.parse("application/json"), is(MediaType.APPLICATION_JSON)); + public void parseUnknownType() { + HttpMediaType mediaType = HttpMediaType.create("unknown-type/unknown-subtype"); + + assertThat(mediaType.mediaType().type(), is("unknown-type")); + assertThat(mediaType.mediaType().subtype(), is("unknown-subtype")); + assertThat(mediaType.charset(), is(Optional.empty())); + assertThat(mediaType.parameters().entrySet(), iterableWithSize(0)); } @Test - void parseIdentity() { - assertThat(MediaType.parse("application/json"), sameInstance(MediaType.APPLICATION_JSON)); + void parseEquals() { + assertThat(HttpMediaType.create("application/json").mediaType(), is(MediaTypes.APPLICATION_JSON)); } @Test - public void parseUnknownType() { - MediaType mediaType = MediaType.parse("unknown-type/unknown-subtype"); - - assertThat(mediaType.type(), Is.is("unknown-type")); - assertThat(mediaType.subtype(), Is.is("unknown-subtype")); - assertThat(mediaType.charset(), Is.is(Optional.empty())); - assertThat(mediaType.parameters().entrySet(), iterableWithSize(0)); + void parseIdentity() { + assertThat(HttpMediaType.create("application/json").mediaType(), sameInstance(MediaTypes.APPLICATION_JSON)); } @Test void parseCharset() { - MediaType mediaType = MediaType.parse("unknown-type/unknown-subtype; charset=utf-8"); + HttpMediaType mediaType = HttpMediaType.create("unknown-type/unknown-subtype; charset=utf-8"); assertThat(mediaType.charset(), not(Optional.empty())); assertThat(mediaType.charset().get(), is("utf-8")); @@ -64,71 +66,70 @@ void parseCharset() { @Test void parseParameters() { - MediaType mediaType = MediaType.parse("unknown-type/unknown-subtype; option1=value1; option2=value2"); + HttpMediaType mediaType = HttpMediaType.create("unknown-type/unknown-subtype; option1=value1; option2=value2"); assertThat(mediaType.parameters(), is(Map.of("option1", "value1", - "option2", "value2"))); + "option2", "value2"))); } @Test void parseDuplicateParameters() { - MediaType mediaType = MediaType.parse("unknown-type/unknown-subtype; option=value1; option=value2"); + HttpMediaType mediaType = HttpMediaType.create("unknown-type/unknown-subtype; option=value1; option=value2"); assertThat(mediaType.parameters(), is(Map.of("option", "value2"))); } @Test void parseEmptyParameterValue() { - assertThat(MediaType.parse("type/subtype; o1=").parameters(), is(Map.of())); - assertThat(MediaType.parse("type/subtype; o1=; o2=v2").parameters(), is(Map.of("o2", "v2"))); + assertThat(HttpMediaType.create("type/subtype; o1=").parameters(), is(Map.of())); + assertThat(HttpMediaType.create("type/subtype; o1=; o2=v2").parameters(), is(Map.of("o2", "v2"))); } @Test void qualityFactor() { - MediaType mediaType = MediaType.parse("unknown-type/unknown-subtype; q=0.2"); + HttpMediaType mediaType = HttpMediaType.create("unknown-type/unknown-subtype; q=0.2"); assertThat(mediaType.qualityFactor(), closeTo(0.2, 0.000001)); } @Test void asPredicate() { - assertThat(MediaType.parse("application/json").test(MediaType.APPLICATION_JSON), is(true)); - assertThat(MediaType.APPLICATION_JSON.test(MediaType.parse("application/json")), is(true)); - assertThat(MediaType.parse("application/*").test(MediaType.APPLICATION_JSON), is(true)); - assertThat(MediaType.APPLICATION_JSON.test(MediaType.parse("application/*")), is(true)); + assertThat(HttpMediaType.create("application/json").test(MediaTypes.APPLICATION_JSON), is(true)); + assertThat(HttpMediaType.JSON_UTF_8.test(MediaTypes.create("application/json")), is(true)); + assertThat(HttpMediaType.create("application/*").test(MediaTypes.APPLICATION_JSON), is(true)); + assertThat(HttpMediaType.JSON_UTF_8.test(MediaTypes.create("application/*")), is(true)); - assertThat(MediaType.parse("application/json").test(MediaType.APPLICATION_JSON.withCharset("UTF-8")), is(true)); - assertThat(MediaType.APPLICATION_JSON.withCharset("UTF-8").test(MediaType.parse("application/json")), is(true)); + assertThat(HttpMediaType.create(MediaTypes.APPLICATION_JSON).withCharset("UTF-8") + .test(MediaTypes.create("application/json")), is(true)); } @Test void jsonPredicate() { - assertThat(MediaType.JSON_PREDICATE.test(MediaType.parse("application/json")), is(true)); - assertThat(MediaType.JSON_PREDICATE.test(MediaType.parse("application/javascript")), is(false)); - assertThat(MediaType.JSON_PREDICATE.test(MediaType.parse("application/manifest+json")), is(true)); - assertThat(MediaType.JSON_PREDICATE.test(MediaType.parse("application/manifest")), is(false)); + assertThat(HttpMediaType.JSON_PREDICATE.test(HttpMediaType.create(MediaTypes.create("application/json"))), is(true)); + assertThat(HttpMediaType.JSON_PREDICATE.test(HttpMediaType.create(MediaTypes.create("application/javascript"))), + is(false)); + assertThat(HttpMediaType.JSON_PREDICATE.test(HttpMediaType.create(MediaTypes.create("application/manifest+json"))), + is(true)); + assertThat(HttpMediaType.JSON_PREDICATE.test(HttpMediaType.create(MediaTypes.create("application/manifest"))), is(false)); } @Test void testText() { - MediaType textPlain = MediaType.TEXT_PLAIN; + HttpMediaType textPlain = HttpMediaType.create(MediaTypes.TEXT_PLAIN); - assertThat(textPlain.type(), is("text")); - assertThat(textPlain.subtype(), is("plain")); + assertThat(textPlain.mediaType(), is(MediaTypes.TEXT_PLAIN)); assertThat(textPlain.charset(), is(Optional.empty())); } @Test void testBuilt() { - MediaType mediaType = MediaType.builder() - .type("application") - .subtype("json") + HttpMediaType mediaType = HttpMediaType.builder() + .mediaType(MediaTypes.create("application", "json")) .charset("ISO-8859-2") .addParameter("q", "0.1") .build(); - assertThat(mediaType.type(), is("application")); - assertThat(mediaType.subtype(), is("json")); + assertThat(mediaType.mediaType(), is(MediaTypes.create("application", "json"))); assertThat(mediaType.charset(), is(Optional.of("ISO-8859-2"))); assertThat(mediaType.parameters(), is(Map.of("q", "0.1", "charset", "ISO-8859-2"))); assertThat(mediaType.qualityFactor(), closeTo(0.1, 0.000001)); diff --git a/common/http/src/test/java/io/helidon/common/http/UriComponentTest.java b/common/http/src/test/java/io/helidon/common/http/UriComponentTest.java deleted file mode 100644 index 0d90ee4cd3a..00000000000 --- a/common/http/src/test/java/io/helidon/common/http/UriComponentTest.java +++ /dev/null @@ -1,66 +0,0 @@ -/* - * Copyright (c) 2022 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.common.http; - -import java.net.URI; -import java.net.URLEncoder; -import java.util.Optional; - -import org.junit.jupiter.api.Test; - -import static java.nio.charset.StandardCharsets.US_ASCII; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.collection.IsCollectionWithSize.hasSize; -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsCollectionContaining.hasItems; - -/** - * The UriComponentTest. - */ -public class UriComponentTest { - - @Test - public void noDecode() throws Exception { - Parameters parameters = UriComponent.decodeQuery("a=" + URLEncoder.encode("1&b=2", US_ASCII.name()), false); - - assertThat(parameters.first("a").get(), is(URLEncoder.encode("1&b=2", US_ASCII.name()))); - } - - @Test - public void yesDecode() throws Exception { - Parameters parameters = UriComponent.decodeQuery("a=" + URLEncoder.encode("1&b=2", US_ASCII.name()), true); - - assertThat(parameters.first("a").get(), is("1&b=2")); - } - - @Test - public void testNonExistingParam() throws Exception { - Parameters parameters = UriComponent.decodeQuery("a=b", true); - - assertThat(parameters.first("c"), is(Optional.empty())); - } - - @SuppressWarnings("unchecked") - @Test - public void sanityParse() throws Exception { - Parameters parameters = UriComponent.decodeQuery(URI.create("http://foo/bar?a=b&c=d&a=e").getRawQuery(), true); - - assertThat(parameters.all("a"), hasItems(is("b"), is("e"))); - assertThat(parameters.all("c"), hasItems(is("d"))); - assertThat(parameters.all("z"), hasSize(0)); - } -} From 117374867eb4c51a5a747f0a8a35f91fd0493fcc Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 22:22:43 +0200 Subject: [PATCH 08/54] Updated common mapper module --- CHANGELOG.md | 2 +- .../common/mapper/DefaultMapperProvider.java | 58 ++++++ .../helidon/common/mapper/MapperManager.java | 95 +++++---- .../common/mapper/MapperManagerImpl.java | 182 +++++++++--------- .../common/mapper/spi/MapperProvider.java | 72 +++++-- .../common/mapper/MapperManagerTest.java | 56 +++--- .../common/mapper/ServiceLoaderMapper1.java | 12 +- .../common/mapper/ServiceLoaderMapper2.java | 19 +- 8 files changed, 314 insertions(+), 182 deletions(-) create mode 100644 common/mapper/src/main/java/io/helidon/common/mapper/DefaultMapperProvider.java diff --git a/CHANGELOG.md b/CHANGELOG.md index ce0dc770e05..5905d92e2d4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,4 +25,4 @@ We are pleased to announce Helidon 4.0.0 a major release that includes significa - moved priority related types to MP config (as that is the lowest level MP module) - replaces all instances in SE that use priority with weight (no dependency on Jakarta, predictible and easy to understand behavior) - Introduction of `MediaType` as the abstraction of any media type, as used by Config, static content and HTTP in general. See `MediaType` and `MediaTypes` -- \ No newline at end of file +- `MapperManager` now supports mapping qualifiers \ No newline at end of file diff --git a/common/mapper/src/main/java/io/helidon/common/mapper/DefaultMapperProvider.java b/common/mapper/src/main/java/io/helidon/common/mapper/DefaultMapperProvider.java new file mode 100644 index 00000000000..88bdef3ab83 --- /dev/null +++ b/common/mapper/src/main/java/io/helidon/common/mapper/DefaultMapperProvider.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.mapper; + +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; + +import io.helidon.common.mapper.spi.MapperProvider; + +class DefaultMapperProvider implements MapperProvider { + private static final Map CACHE = new ConcurrentHashMap<>(); + + @Override + public ProviderResponse mapper(Class sourceClass, Class targetClass, String qualifier) { + return CACHE.computeIfAbsent(new CacheKey(sourceClass, targetClass), key -> { + if (sourceClass.equals(String.class)) { + return fromString(targetClass); + } + if (targetClass.equals(String.class)) { + return toString(targetClass); + } + return ProviderResponse.unsupported(); + }); + } + + private static ProviderResponse fromString(Class target) { + // todo add all supported types + if (target.equals(int.class) || target.equals(Integer.class)) { + return new ProviderResponse(Support.COMPATIBLE, o -> Integer.parseInt((String) o)); + } + if (target.equals(long.class) || target.equals(Long.class)) { + return new ProviderResponse(Support.COMPATIBLE, o -> Long.parseLong((String) o)); + } + return ProviderResponse.unsupported(); + } + + private static ProviderResponse toString(Class source) { + // todo add all supported types (explicitly) + return new ProviderResponse(Support.COMPATIBLE, String::valueOf); + } + + private record CacheKey(Class source, Class target) { + } +} diff --git a/common/mapper/src/main/java/io/helidon/common/mapper/MapperManager.java b/common/mapper/src/main/java/io/helidon/common/mapper/MapperManager.java index 5500fd0cea9..8113a5b9959 100644 --- a/common/mapper/src/main/java/io/helidon/common/mapper/MapperManager.java +++ b/common/mapper/src/main/java/io/helidon/common/mapper/MapperManager.java @@ -16,8 +16,8 @@ package io.helidon.common.mapper; import java.util.List; -import java.util.Optional; import java.util.ServiceLoader; +import java.util.Set; import io.helidon.common.GenericType; import io.helidon.common.HelidonServiceLoader; @@ -30,10 +30,10 @@ * To map a source to target, you can use either of the {@code map} methods defined in this interface, * as they make sure that the mapping exists in either space. *

    - *
  • If you call {@link #map(Object, Class, Class)} and no mapper is found for the class pair, - * the implementation calls the {@link #map(Object, io.helidon.common.GenericType, io.helidon.common.GenericType)} with - * {@link io.helidon.common.GenericType}s created for each parameters
  • - *
  • If you call {@link #map(Object, io.helidon.common.GenericType, io.helidon.common.GenericType)} and no mapper is + *
  • If you call {@link #map(Object, Class, Class, String...)} and no mapper is found for the class pair, + * the implementation calls the {@link #map(Object, io.helidon.common.GenericType, io.helidon.common.GenericType, String...)} + * with {@link io.helidon.common.GenericType}s created for each parameters
  • + *
  • If you call {@link #map(Object, io.helidon.common.GenericType, io.helidon.common.GenericType, String...)} and no mapper is * found for the {@link io.helidon.common.GenericType} pair, an attempt is to locate a mapper for * the underlying class *IF* the generic type represents a simple class (e.g. not a generic type declaration)
  • *
@@ -88,12 +88,16 @@ static MapperManager create(HelidonServiceLoader serviceLoader) * @param source object to map * @param sourceType type of the source object (to locate the mapper) * @param targetType type of the target object (to locate the mapper) + * @param qualifiers qualifiers of the usage (such as {@code http-headers, http}, most specific one first) * @param type of the source * @param type of the target * @return result of the mapping * @throws MapperException in case the mapper was not found or failed */ - TARGET map(SOURCE source, GenericType sourceType, GenericType targetType) + TARGET map(SOURCE source, + GenericType sourceType, + GenericType targetType, + String... qualifiers) throws MapperException; /** @@ -102,12 +106,14 @@ TARGET map(SOURCE source, GenericType sourceType, Gener * @param source object to map * @param sourceType class of the source object (to locate the mapper) * @param targetType class of the target object (to locate the mapper) + * @param qualifiers qualifiers of the usage (such as {@code http-headers, http}, most specific one first) * @param type of the source * @param type of the target * @return result of the mapping * @throws MapperException in case the mapper was not found or failed */ - TARGET map(SOURCE source, Class sourceType, Class targetType) throws MapperException; + TARGET map(SOURCE source, Class sourceType, Class targetType, String... qualifiers) + throws MapperException; /** * Fluent API builder for {@link io.helidon.common.mapper.MapperManager}. @@ -121,17 +127,10 @@ private Builder() { @Override public MapperManager build() { + providers.addService(new DefaultMapperProvider(), 0); return new MapperManagerImpl(this); } - private Builder mapperProviders(HelidonServiceLoader serviceLoader) { - providers = HelidonServiceLoader.builder(ServiceLoader.load(MapperProvider.class)) - .useSystemServiceLoader(false); - - serviceLoader.forEach(providers::addService); - return this; - } - /** * Add a new {@link io.helidon.common.mapper.spi.MapperProvider} to the list of providers loaded from * system service loader. @@ -172,12 +171,13 @@ public Builder addMapperProvider(MapperProvider provider, int priority) { * @param mapper the mapper to map source instances to target instances * @param sourceType class of the source instance * @param targetType class of the target instance + * @param qualifiers supported qualifiers of this mapper (if none provided, will return a compatible response) * @param type of source * @param type of target * @return updated builder instance */ - public Builder addMapper(Mapper mapper, Class sourceType, Class targetType) { - return addMapper(mapper, sourceType, targetType, Weighted.DEFAULT_WEIGHT); + public Builder addMapper(Mapper mapper, Class sourceType, Class targetType, String... qualifiers) { + return addMapper(mapper, sourceType, targetType, Weighted.DEFAULT_WEIGHT, qualifiers); } /** @@ -187,6 +187,7 @@ public Builder addMapper(Mapper mapper, Class sourceType, Class< * @param sourceType class of the source instance * @param targetType class of the target instance * @param weight weight of the mapper + * @param qualifiers supported qualifiers of this mapper (if none provided, will return a compatible response) * @param type of source * @param type of target * @return updated builder instance @@ -194,17 +195,19 @@ public Builder addMapper(Mapper mapper, Class sourceType, Class< public Builder addMapper(Mapper mapper, Class sourceType, Class targetType, - double weight) { - this.providers.addService(new MapperProvider() { - @SuppressWarnings({"unchecked", "ObjectEquality"}) - @Override - public Optional> mapper(Class sourceClass, - Class targetClass) { - if ((sourceType == sourceClass) && (targetType == targetClass)) { - return Optional.of((Mapper) mapper); + double weight, + String... qualifiers) { + Set qualifierSet = Set.of(qualifiers); + + this.providers.addService((sourceClass, targetClass, qualifier) -> { + if ((sourceType == sourceClass) && (targetType == targetClass)) { + if (qualifierSet.contains(qualifier)) { + return new MapperProvider.ProviderResponse(MapperProvider.Support.SUPPORTED, mapper); + } else { + return new MapperProvider.ProviderResponse(MapperProvider.Support.COMPATIBLE, mapper); } - return Optional.empty(); } + return MapperProvider.ProviderResponse.unsupported(); }, weight); return this; } @@ -215,12 +218,16 @@ public Builder addMapper(Mapper mapper, * @param mapper the mapper to map source instances to target instances * @param sourceType generic type of the source instance * @param targetType generic type of the target instance + * @param qualifiers qualifiers of this mapper, if empty, will be a compatible mapper * @param type of source * @param type of target * @return updated builder instance */ - public Builder addMapper(Mapper mapper, GenericType sourceType, GenericType targetType) { - return addMapper(mapper, sourceType, targetType, Weighted.DEFAULT_WEIGHT); + public Builder addMapper(Mapper mapper, + GenericType sourceType, + GenericType targetType, + String... qualifiers) { + return addMapper(mapper, sourceType, targetType, Weighted.DEFAULT_WEIGHT, qualifiers); } /** @@ -237,21 +244,27 @@ public Builder addMapper(Mapper mapper, GenericType sourceType, public Builder addMapper(Mapper mapper, GenericType sourceType, GenericType targetType, - double weight) { + double weight, + String... qualifiers) { + Set qualifierSet = Set.of(qualifiers); + this.providers.addService(new MapperProvider() { @Override - public Optional> mapper(Class sourceClass, Class targetClass) { - return Optional.empty(); + public ProviderResponse mapper(Class sourceClass, Class targetClass, String qualifier) { + return ProviderResponse.unsupported(); } - @SuppressWarnings({"unchecked"}) @Override - public Optional> mapper(GenericType sourceClass, - GenericType targetClass) { - if ((sourceType.equals(sourceClass)) && (targetType.equals(targetClass))) { - return Optional.of((Mapper) mapper); + public ProviderResponse mapper(GenericType source, GenericType target, String qualifier) { + if ((sourceType.equals(source)) && (targetType.equals(target))) { + if (qualifierSet.contains(qualifier)) { + return new ProviderResponse(Support.SUPPORTED, mapper); + } else { + return new ProviderResponse(Support.COMPATIBLE, mapper); + } + } - return Optional.empty(); + return ProviderResponse.unsupported(); } }, weight); return this; @@ -261,5 +274,13 @@ public Builder addMapper(Mapper mapper, List mapperProviders() { return providers.build().asList(); } + + private Builder mapperProviders(HelidonServiceLoader serviceLoader) { + providers = HelidonServiceLoader.builder(ServiceLoader.load(MapperProvider.class)) + .useSystemServiceLoader(false); + + serviceLoader.forEach(providers::addService); + return this; + } } } diff --git a/common/mapper/src/main/java/io/helidon/common/mapper/MapperManagerImpl.java b/common/mapper/src/main/java/io/helidon/common/mapper/MapperManagerImpl.java index 8932f3fac36..4ed63334c9c 100644 --- a/common/mapper/src/main/java/io/helidon/common/mapper/MapperManagerImpl.java +++ b/common/mapper/src/main/java/io/helidon/common/mapper/MapperManagerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,9 +15,9 @@ */ package io.helidon.common.mapper; +import java.util.Arrays; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.Optional; import java.util.concurrent.ConcurrentHashMap; @@ -37,9 +37,12 @@ final class MapperManagerImpl implements MapperManager { } @Override - public TARGET map(SOURCE source, GenericType sourceType, GenericType targetType) { + public TARGET map(SOURCE source, + GenericType sourceType, + GenericType targetType, + String... qualifiers) { try { - return findMapper(sourceType, targetType, false) + return findMapper(sourceType, targetType, false, qualifiers) .map(source); } catch (MapperException e) { throw e; @@ -49,9 +52,9 @@ public TARGET map(SOURCE source, GenericType sourceType } @Override - public TARGET map(SOURCE source, Class sourceType, Class targetType) { + public TARGET map(SOURCE source, Class sourceType, Class targetType, String... qualifiers) { try { - return findMapper(sourceType, targetType, false) + return findMapper(sourceType, targetType, false, qualifiers) .map(source); } catch (MapperException e) { throw e; @@ -60,31 +63,54 @@ public TARGET map(SOURCE source, Class sourceType, Clas } } - private RuntimeException createMapperException(Object source, - GenericType sourceType, - GenericType targetType, - Throwable throwable) { + private static Mapper notFoundMapper(GenericType sourceType, + GenericType targetType, + String... qualifier) { + String qualifierString = Arrays.toString(qualifier); + return source -> { + throw createMapperException(source, + sourceType, + targetType, + "Failed to find mapper. Qualifiers: " + qualifierString + "."); + }; + } - throw new MapperException(GenericType.create(sourceType), - GenericType.create(targetType), + private static RuntimeException createMapperException(Object source, + GenericType sourceType, + GenericType targetType, + Throwable throwable) { + + throw new MapperException(sourceType, + targetType, "Failed to map source of class '" + source.getClass().getName() + "'", throwable); } + private static RuntimeException createMapperException(Object source, + GenericType sourceType, + GenericType targetType, + String message) { + + throw new MapperException(sourceType, + targetType, + message + ", source of class '" + source.getClass().getName() + "'"); + } + @SuppressWarnings("unchecked") private Mapper findMapper(Class sourceType, Class targetType, - boolean fromTypes) { - Mapper mapper = classCache.computeIfAbsent(new ClassCacheKey(sourceType, targetType), key -> { + boolean fromTypes, + String... qualifiers) { + Mapper mapper = classCache.computeIfAbsent(new ClassCacheKey(sourceType, targetType, qualifiers), key -> { // first attempt to find by classes - return fromProviders(sourceType, targetType) + return fromProviders(sourceType, targetType, qualifiers) .orElseGet(() -> { GenericType sourceGenericType = GenericType.create(sourceType); GenericType targetGenericType = GenericType.create(targetType); if (fromTypes) { - return notFoundMapper(sourceGenericType, targetGenericType); + return notFoundMapper(sourceGenericType, targetGenericType, qualifiers); } - return findMapper(sourceGenericType, targetGenericType, true); + return findMapper(sourceGenericType, targetGenericType, true, qualifiers); }); }); return (Mapper) mapper; @@ -93,95 +119,77 @@ private Mapper findMapper(Class sourceT @SuppressWarnings("unchecked") private Mapper findMapper(GenericType sourceType, GenericType targetType, - boolean fromClasses) { - Mapper mapper = typeCache.computeIfAbsent(new GenericCacheKey(sourceType, targetType), key -> { + boolean fromClasses, + String... qualifiers) { + Mapper mapper = typeCache.computeIfAbsent(new GenericCacheKey(sourceType, targetType, qualifiers), key -> { // first attempt to find by types - return fromProviders(sourceType, targetType) + return fromProviders(sourceType, targetType, qualifiers) .orElseGet(() -> { // and then by classes (unless we are already called from findMapper(Class, Class) if (!fromClasses && (sourceType.isClass() && targetType.isClass())) { - return findMapper((Class) sourceType.rawType(), (Class) targetType.rawType(), true); + return findMapper((Class) sourceType.rawType(), + (Class) targetType.rawType(), + true, + qualifiers); } - return notFoundMapper(sourceType, targetType); + return notFoundMapper(sourceType, targetType, qualifiers); }); }); return (Mapper) mapper; } private Optional> fromProviders(Class sourceType, - Class targetType) { - return providers.stream() - .flatMap(provider -> provider.mapper(sourceType, targetType).stream()) - .findFirst(); - } + Class targetType, + String... qualifiers) { + Mapper compatible = null; + + for (String qualifier : qualifiers) { + for (MapperProvider provider : providers) { + MapperProvider.ProviderResponse response = provider.mapper(sourceType, targetType, qualifier); + switch (response.support()) { + case SUPPORTED -> { + return Optional.of(response.mapper()); + } + case COMPATIBLE -> { + compatible = (compatible == null) ? response.mapper() : compatible; + } + default -> { + } + } + } + } - private Optional> fromProviders(GenericType sourceType, - GenericType targetType) { - return providers.stream() - .flatMap(provider -> provider.mapper(sourceType, targetType).stream()) - .findFirst(); + return Optional.ofNullable(compatible); } - private static Mapper notFoundMapper(GenericType sourceType, - GenericType targetType) { - return source -> { - throw new MapperException(sourceType, targetType, "Failed to find mapper."); - }; - } - - private static final class GenericCacheKey { - private final GenericType sourceType; - private final GenericType targetType; - - private GenericCacheKey(GenericType sourceType, GenericType targetType) { - this.sourceType = sourceType; - this.targetType = targetType; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof GenericCacheKey)) { - return false; + private Optional> fromProviders(GenericType sourceType, + GenericType targetType, + String... qualifiers) { + + Mapper compatible = null; + + for (String qualifier : qualifiers) { + for (MapperProvider provider : providers) { + MapperProvider.ProviderResponse response = provider.mapper(sourceType, targetType, qualifier); + switch (response.support()) { + case SUPPORTED -> { + return Optional.of(response.mapper()); + } + case COMPATIBLE -> { + compatible = (compatible == null) ? response.mapper() : compatible; + } + default -> { + } + } } - GenericCacheKey that = (GenericCacheKey) o; - return sourceType.equals(that.sourceType) - && targetType.equals(that.targetType); } - @Override - public int hashCode() { - return Objects.hash(sourceType, targetType); - } + return Optional.ofNullable(compatible); } - private static final class ClassCacheKey { - private final Class sourceType; - private final Class targetType; - - private ClassCacheKey(Class sourceType, Class targetType) { - this.sourceType = sourceType; - this.targetType = targetType; - } - - @Override - public boolean equals(Object o) { - if (this == o) { - return true; - } - if (!(o instanceof ClassCacheKey)) { - return false; - } - ClassCacheKey that = (ClassCacheKey) o; - return sourceType.equals(that.sourceType) - && targetType.equals(that.targetType); - } + private record GenericCacheKey(GenericType sourceType, GenericType targetType, String... qualifiers) { + } - @Override - public int hashCode() { - return Objects.hash(sourceType, targetType); - } + private record ClassCacheKey(Class sourceType, Class targetType, String... qualifiers) { } } diff --git a/common/mapper/src/main/java/io/helidon/common/mapper/spi/MapperProvider.java b/common/mapper/src/main/java/io/helidon/common/mapper/spi/MapperProvider.java index a1d3c337ed8..263a076a365 100644 --- a/common/mapper/src/main/java/io/helidon/common/mapper/spi/MapperProvider.java +++ b/common/mapper/src/main/java/io/helidon/common/mapper/spi/MapperProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,26 +15,31 @@ */ package io.helidon.common.mapper.spi; -import java.util.Optional; - import io.helidon.common.GenericType; import io.helidon.common.mapper.Mapper; /** * Java Service loader service to get mappers. + * + * Mapper provider provides mappers based on the source and target types and a qualifier. + * Generic mappers should always return {@link io.helidon.common.mapper.spi.MapperProvider.Support#COMPATIBLE}, so specific mappers + * can be created for qualified usages. This is to support a different date/time mapper depending on usage. For this + * case we may have the following qualifiers (example, not normative): {@code config,jdbc-oracle,http-header,http-query}. + * Qualifiers should be defined by a constant in each component using mapping. */ @FunctionalInterface public interface MapperProvider { /** * Find a mapper that is capable of mapping from source to target classes. + * Qualifiers are defined by each component using mapping. In case of clashing qualifiers, the first mapper + * that returns {@link io.helidon.common.mapper.spi.MapperProvider.Support#SUPPORTED} will be chosen. * * @param sourceClass class of the source * @param targetClass class of the target - * @param type of the source - * @param type of the target + * @param qualifier qualifiers of this mapping (such as {@code config} or {@code http-headers} * @return a mapper that is capable of mapping (or converting) sources to targets */ - Optional> mapper(Class sourceClass, Class targetClass); + ProviderResponse mapper(Class sourceClass, Class targetClass, String qualifier); /** * Find a mapper that is capable of mapping from source to target types. @@ -42,12 +47,55 @@ public interface MapperProvider { * * @param sourceType generic type of the source * @param targetType generic type of the target - * @param type of the source - * @param type of the target - * @return a mapper that is capable of mapping (or converting) sources to targets + * @param qualifier qualifier of the mapping - this is to allow multiple mappings for the same type depending on context + * such as HTTP Headers may use a different date mapping than database operations + * @return a mapper that is capable of mapping (or converting) sources to targets, default implementation + * calls {@link #mapper(Class, Class, java.lang.String)} for types that are not generic, + * {@link io.helidon.common.mapper.spi.MapperProvider.ProviderResponse#unsupported()} otherwise. + */ + default ProviderResponse mapper(GenericType sourceType, GenericType targetType, String qualifier) { + if (sourceType.isClass() && targetType.isClass()) { + ProviderResponse resp = mapper(sourceType.rawType(), targetType.rawType(), qualifier); + if (resp.support() == Support.SUPPORTED) { + return new ProviderResponse(Support.COMPATIBLE, resp.mapper()); + } + } + return ProviderResponse.unsupported(); + } + + /** + * How does this provider support the type. */ - default Optional> mapper(GenericType sourceType, - GenericType targetType) { - return Optional.empty(); + enum Support { + /** + * Correct type(s) and expected qualifier. + */ + SUPPORTED, + /** + * Correct type(s), unexpected qualifier. + */ + COMPATIBLE, + /** + * Incorrect type(s). + */ + UNSUPPORTED + } + + /** + * Response of a provider. + * + * @param support how is this supported + * @param mapper mapper to map the type, or null if support is {@link Support#UNSUPPORTED} + */ + record ProviderResponse(Support support, Mapper mapper) { + private static final ProviderResponse UNSUPPORTED = new ProviderResponse(Support.UNSUPPORTED, null); + + /** + * Unsupported provider response. + * @return constant - unsupported response + */ + public static ProviderResponse unsupported() { + return UNSUPPORTED; + } } } diff --git a/common/mapper/src/test/java/io/helidon/common/mapper/MapperManagerTest.java b/common/mapper/src/test/java/io/helidon/common/mapper/MapperManagerTest.java index ab65781ef66..eb598ef493f 100644 --- a/common/mapper/src/test/java/io/helidon/common/mapper/MapperManagerTest.java +++ b/common/mapper/src/test/java/io/helidon/common/mapper/MapperManagerTest.java @@ -19,8 +19,8 @@ import java.util.ServiceLoader; import io.helidon.common.GenericType; -import io.helidon.common.mapper.spi.MapperProvider; import io.helidon.common.HelidonServiceLoader; +import io.helidon.common.mapper.spi.MapperProvider; import org.junit.jupiter.api.Test; @@ -38,27 +38,27 @@ void testUsingServiceLoader() { String source = "10"; // using classes - Integer result = mm.map(source, String.class, Integer.class); + Integer result = mm.map(source, String.class, Integer.class, "default"); assertThat(result, is(10)); // using generic types - result = mm.map(source, GenericType.STRING, ServiceLoaderMapper2.INTEGER_TYPE); + result = mm.map(source, ServiceLoaderMapper2.STRING_TYPE, ServiceLoaderMapper2.INTEGER_TYPE, "default"); assertThat(result, is(11)); // search for opposite (use class, find type and vice versa) - Long longResult = mm.map(source, GenericType.STRING, GenericType.create(Long.class)); + Long longResult = mm.map(source, ServiceLoaderMapper2.STRING_TYPE, GenericType.create(Long.class), "default"); assertThat(longResult, is(10L)); // must be the same - longResult = mm.map(source, String.class, Long.class); + longResult = mm.map(source, String.class, Long.class, "default"); assertThat(longResult, is(10L)); - Short shortResult = mm.map(source, String.class, Short.class); + Short shortResult = mm.map(source, String.class, Short.class, "default"); assertThat(shortResult, is((short) 10)); // must be the same - shortResult = mm.map(source, GenericType.STRING, ServiceLoaderMapper2.SHORT_TYPE); + shortResult = mm.map(source, ServiceLoaderMapper2.STRING_TYPE, ServiceLoaderMapper2.SHORT_TYPE, "default"); assertThat(shortResult, is((short) 10)); - assertThrows(MapperException.class, () -> mm.map(source, String.class, Object.class)); + assertThrows(MapperException.class, () -> mm.map(source, String.class, Object.class, "default")); } @Test @@ -71,66 +71,66 @@ void testUsingCustomProviders() { String source = "10"; // using classes - Integer result = mm.map(source, String.class, Integer.class); + Integer result = mm.map(source, String.class, Integer.class, "default"); assertThat(result, is(10)); // using generic types - result = mm.map(source, GenericType.STRING, ServiceLoaderMapper2.INTEGER_TYPE); + result = mm.map(source, ServiceLoaderMapper2.STRING_TYPE, ServiceLoaderMapper2.INTEGER_TYPE, "default"); assertThat(result, is(10)); // search for opposite (use class, find type and vice versa) - Long longResult = mm.map(source, GenericType.STRING, GenericType.create(Long.class)); + Long longResult = mm.map(source, ServiceLoaderMapper2.STRING_TYPE, GenericType.create(Long.class), "default"); assertThat(longResult, is(10L)); // must be the same - longResult = mm.map(source, String.class, Long.class); + longResult = mm.map(source, String.class, Long.class, "default"); assertThat(longResult, is(10L)); - assertThrows(MapperException.class, () -> mm.map(source, String.class, Short.class)); - assertThrows(MapperException.class, () -> mm.map(source, GenericType.STRING, - ServiceLoaderMapper2.SHORT_TYPE)); - assertThrows(MapperException.class, () -> mm.map(source, String.class, Object.class)); + assertThrows(MapperException.class, () -> mm.map(source, String.class, Short.class, "default")); + assertThrows(MapperException.class, () -> mm.map(source, ServiceLoaderMapper2.STRING_TYPE, + ServiceLoaderMapper2.SHORT_TYPE, "default")); + assertThrows(MapperException.class, () -> mm.map(source, String.class, Object.class, "default")); } @Test void testUsingServiceLoaderAndCustomMappers() { MapperManager mm = MapperManager.builder() .addMapper(String::valueOf, Integer.class, String.class) - .addMapper(String::valueOf, ServiceLoaderMapper2.SHORT_TYPE, GenericType.STRING) + .addMapper(String::valueOf, ServiceLoaderMapper2.SHORT_TYPE, ServiceLoaderMapper2.STRING_TYPE) .build(); String source = "10"; // using classes - Integer result = mm.map(source, String.class, Integer.class); + Integer result = mm.map(source, String.class, Integer.class, "default"); assertThat(result, is(10)); // using generic types - result = mm.map(source, GenericType.STRING, ServiceLoaderMapper2.INTEGER_TYPE); + result = mm.map(source, ServiceLoaderMapper2.STRING_TYPE, ServiceLoaderMapper2.INTEGER_TYPE, "default"); assertThat(result, is(11)); // search for opposite (use class, find type and vice versa) - Long longResult = mm.map(source, GenericType.STRING, GenericType.create(Long.class)); + Long longResult = mm.map(source, ServiceLoaderMapper2.STRING_TYPE, GenericType.create(Long.class), "default"); assertThat(longResult, is(10L)); // must be the same - longResult = mm.map(source, String.class, Long.class); + longResult = mm.map(source, String.class, Long.class, "default"); assertThat(longResult, is(10L)); - Short shortResult = mm.map(source, String.class, Short.class); + Short shortResult = mm.map(source, String.class, Short.class, "default"); assertThat(shortResult, is((short) 10)); // must be the same - shortResult = mm.map(source, GenericType.STRING, ServiceLoaderMapper2.SHORT_TYPE); + shortResult = mm.map(source, ServiceLoaderMapper2.STRING_TYPE, ServiceLoaderMapper2.SHORT_TYPE, "default"); assertThat(shortResult, is((short) 10)); - assertThrows(MapperException.class, () -> mm.map(source, String.class, Object.class)); + assertThrows(MapperException.class, () -> mm.map(source, String.class, Object.class, "default")); // and add tests for integer and short types - String stringResult = mm.map(42, Integer.class, String.class); + String stringResult = mm.map(42, Integer.class, String.class, "default"); assertThat(stringResult, is("42")); - stringResult = mm.map(42, GenericType.create(Integer.class), GenericType.STRING); + stringResult = mm.map(42, GenericType.create(Integer.class), ServiceLoaderMapper2.STRING_TYPE, "default"); assertThat(stringResult, is("42")); - stringResult = mm.map((short)42, Short.class, String.class); + stringResult = mm.map((short)42, Short.class, String.class, "default"); assertThat(stringResult, is("42")); - stringResult = mm.map((short)42, ServiceLoaderMapper2.SHORT_TYPE, GenericType.STRING); + stringResult = mm.map((short)42, ServiceLoaderMapper2.SHORT_TYPE, ServiceLoaderMapper2.STRING_TYPE, "default"); assertThat(stringResult, is("42")); } } \ No newline at end of file diff --git a/common/mapper/src/test/java/io/helidon/common/mapper/ServiceLoaderMapper1.java b/common/mapper/src/test/java/io/helidon/common/mapper/ServiceLoaderMapper1.java index cf119ec8076..c5d5a68bfbd 100644 --- a/common/mapper/src/test/java/io/helidon/common/mapper/ServiceLoaderMapper1.java +++ b/common/mapper/src/test/java/io/helidon/common/mapper/ServiceLoaderMapper1.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,6 @@ */ package io.helidon.common.mapper; -import java.util.Optional; - import io.helidon.common.mapper.spi.MapperProvider; /** @@ -24,15 +22,15 @@ */ public class ServiceLoaderMapper1 implements MapperProvider { @Override - public Optional> mapper(Class sourceClass, Class targetClass) { + public ProviderResponse mapper(Class sourceClass, Class targetClass, String qualifier) { if ((sourceClass.equals(String.class)) && (targetClass.equals(Integer.class))) { - return Optional.of(source -> Integer.parseInt((String) source)); + return new ProviderResponse(Support.SUPPORTED, source -> Integer.parseInt((String) source)); } if ((sourceClass.equals(String.class)) && (targetClass.equals(Long.class))) { - return Optional.of(source -> Long.parseLong((String) source)); + return new ProviderResponse(Support.SUPPORTED, source -> Long.parseLong((String) source)); } - return Optional.empty(); + return ProviderResponse.unsupported(); } } diff --git a/common/mapper/src/test/java/io/helidon/common/mapper/ServiceLoaderMapper2.java b/common/mapper/src/test/java/io/helidon/common/mapper/ServiceLoaderMapper2.java index 96eea7a3840..28aae543dba 100644 --- a/common/mapper/src/test/java/io/helidon/common/mapper/ServiceLoaderMapper2.java +++ b/common/mapper/src/test/java/io/helidon/common/mapper/ServiceLoaderMapper2.java @@ -15,8 +15,6 @@ */ package io.helidon.common.mapper; -import java.util.Optional; - import io.helidon.common.GenericType; import io.helidon.common.mapper.spi.MapperProvider; @@ -24,23 +22,24 @@ * Maps String to Integer, and String to Short using type. */ public class ServiceLoaderMapper2 implements MapperProvider { + static final GenericType STRING_TYPE = GenericType.create(String.class); static final GenericType INTEGER_TYPE = GenericType.create(Integer.class); static final GenericType SHORT_TYPE = GenericType.create(Short.class); @Override - public Optional> mapper(Class sourceClass, Class targetClass) { - return Optional.empty(); + public ProviderResponse mapper(Class sourceClass, Class targetClass, String qualifier) { + return ProviderResponse.unsupported(); } @Override - public Optional> mapper(GenericType sourceType, GenericType targetType) { - if (sourceType.equals(GenericType.STRING) && targetType.equals(INTEGER_TYPE)) { - return Optional.of(string -> Integer.parseInt((String)string) + 1); + public ProviderResponse mapper(GenericType sourceType, GenericType targetType, String qualifier) { + if (sourceType.equals(STRING_TYPE) && targetType.equals(INTEGER_TYPE)) { + return new ProviderResponse(Support.SUPPORTED, string -> Integer.parseInt((String) string) + 1); } - if (sourceType.equals(GenericType.STRING) && targetType.equals(SHORT_TYPE)) { - return Optional.of(string -> Short.parseShort((String)string)); + if (sourceType.equals(STRING_TYPE) && targetType.equals(SHORT_TYPE)) { + return new ProviderResponse(Support.SUPPORTED, string -> Short.parseShort((String) string)); } - return Optional.empty(); + return ProviderResponse.unsupported(); } } From 10d49ac6a40b5593bc9bbced7c3b5fd9085d9590 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 22:22:59 +0200 Subject: [PATCH 09/54] BOM update --- bom/pom.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/bom/pom.xml b/bom/pom.xml index 70be6388961..846960dce10 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -641,6 +641,16 @@ helidon-common-buffers ${helidon.version}
+ + io.helidon.common + helidon-common-uri + ${helidon.version} + + + io.helidon.common + helidon-common-socket + ${helidon.version} + io.helidon.common.testing helidon-common-testing-junit5 From 35bd28333bebad2e77ee99e9c2ca0caf136167cb Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 22:23:16 +0200 Subject: [PATCH 10/54] Log cleanup (and remove deprecated method use) --- .../src/main/java/io/helidon/common/pki/PkiUtil.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/common/key-util/src/main/java/io/helidon/common/pki/PkiUtil.java b/common/key-util/src/main/java/io/helidon/common/pki/PkiUtil.java index 8e32099693f..3cfe8f54227 100644 --- a/common/key-util/src/main/java/io/helidon/common/pki/PkiUtil.java +++ b/common/key-util/src/main/java/io/helidon/common/pki/PkiUtil.java @@ -105,8 +105,14 @@ static List loadCertificates(KeyStore keyStore) { X509Certificate cert = (X509Certificate) keyStore.getCertificate(alias); certs.add(cert); - LOGGER.log(Level.DEBUG, () -> "Added certificate under alis " + alias + " for " + cert - .getSubjectDN() + " to list of certificates"); + if (LOGGER.isLoggable(System.Logger.Level.DEBUG)) { + LOGGER.log(System.Logger.Level.DEBUG, "Added certificate under alis " + + alias + + " for " + + cert + .getIssuerX500Principal().getName() + + " to list of certificates"); + } } } } catch (KeyStoreException e) { From 7c9491e4970f3a459350e3672eb117ea5a249db4 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 22:24:30 +0200 Subject: [PATCH 11/54] HTTP module classes missed on first update --- .../common/http/ContentDisposition.java | 389 ++++++++++++++++++ .../io/helidon/common/http/CookieParser.java | 95 +++++ .../io/helidon/common/http/HeaderEnum.java | 145 +++++++ .../io/helidon/common/http/HeaderHelper.java | 92 +++++ .../io/helidon/common/http/HeaderImpl.java | 39 ++ .../helidon/common/http/HeaderValueArray.java | 60 +++ .../helidon/common/http/HeaderValueBase.java | 105 +++++ .../common/http/HeaderValueCached.java | 71 ++++ .../helidon/common/http/HeaderValueCopy.java | 56 +++ .../helidon/common/http/HeaderValueLazy.java | 66 +++ .../helidon/common/http/HeaderValueList.java | 46 +++ .../common/http/HeaderValueSingle.java | 59 +++ .../common/http/HeadersClientRequest.java | 60 +++ .../common/http/HeadersClientRequestImpl.java | 132 ++++++ .../common/http/HeadersClientResponse.java | 108 +++++ .../http/HeadersClientResponseImpl.java | 69 ++++ .../io/helidon/common/http/HeadersImpl.java | 277 +++++++++++++ .../common/http/HeadersServerRequest.java | 209 ++++++++++ .../common/http/HeadersServerRequestImpl.java | 111 +++++ .../common/http/HeadersServerResponse.java | 195 +++++++++ .../http/HeadersServerResponseImpl.java | 77 ++++ .../helidon/common/http/HeadersWritable.java | 174 ++++++++ .../common/http/Http1HeadersParser.java | 118 ++++++ .../io/helidon/common/http/HttpMediaType.java | 332 +++++++++++++++ .../common/http/HttpMediaTypeImpl.java | 154 +++++++ .../io/helidon/common/http/HttpPrologue.java | 240 +++++++++++ .../io/helidon/common/http/HttpToken.java | 56 +++ .../java/io/helidon/common/http/IntSet.java | 94 +++++ .../io/helidon/common/http/MethodHelper.java | 55 +++ .../helidon/common/http/MethodPredicates.java | 115 ++++++ .../io/helidon/common/http/StatusHelper.java | 55 +++ .../common/http/ContentDispositionTest.java | 246 +++++++++++ .../helidon/common/http/CookieParserTest.java | 74 ++++ .../helidon/common/http/HeaderNamesTest.java | 76 ++++ .../common/http/Http1HeadersParserTest.java | 56 +++ .../common/http/HttpMediaTypeImplTest.java | 69 ++++ .../helidon/common/http/HttpMethodTest.java | 58 +++ .../helidon/common/http/HttpPrologueTest.java | 60 +++ .../helidon/common/http/HttpStatusTest.java | 83 ++++ .../io/helidon/common/http/IntSetTest.java | 48 +++ 40 files changed, 4624 insertions(+) create mode 100644 common/http/src/main/java/io/helidon/common/http/ContentDisposition.java create mode 100644 common/http/src/main/java/io/helidon/common/http/CookieParser.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeaderEnum.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeaderHelper.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeaderImpl.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeaderValueArray.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeaderValueBase.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeaderValueCached.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeaderValueCopy.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeaderValueLazy.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeaderValueList.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeaderValueSingle.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeadersClientRequest.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeadersClientRequestImpl.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeadersClientResponse.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeadersClientResponseImpl.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeadersImpl.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeadersServerRequest.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeadersServerRequestImpl.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeadersServerResponse.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeadersServerResponseImpl.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HeadersWritable.java create mode 100644 common/http/src/main/java/io/helidon/common/http/Http1HeadersParser.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HttpMediaType.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HttpMediaTypeImpl.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HttpPrologue.java create mode 100644 common/http/src/main/java/io/helidon/common/http/HttpToken.java create mode 100644 common/http/src/main/java/io/helidon/common/http/IntSet.java create mode 100644 common/http/src/main/java/io/helidon/common/http/MethodHelper.java create mode 100644 common/http/src/main/java/io/helidon/common/http/MethodPredicates.java create mode 100644 common/http/src/main/java/io/helidon/common/http/StatusHelper.java create mode 100644 common/http/src/test/java/io/helidon/common/http/ContentDispositionTest.java create mode 100644 common/http/src/test/java/io/helidon/common/http/CookieParserTest.java create mode 100644 common/http/src/test/java/io/helidon/common/http/HeaderNamesTest.java create mode 100644 common/http/src/test/java/io/helidon/common/http/Http1HeadersParserTest.java create mode 100644 common/http/src/test/java/io/helidon/common/http/HttpMediaTypeImplTest.java create mode 100644 common/http/src/test/java/io/helidon/common/http/HttpMethodTest.java create mode 100644 common/http/src/test/java/io/helidon/common/http/HttpPrologueTest.java create mode 100644 common/http/src/test/java/io/helidon/common/http/HttpStatusTest.java create mode 100644 common/http/src/test/java/io/helidon/common/http/IntSetTest.java diff --git a/common/http/src/main/java/io/helidon/common/http/ContentDisposition.java b/common/http/src/main/java/io/helidon/common/http/ContentDisposition.java new file mode 100644 index 00000000000..68908150c48 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/ContentDisposition.java @@ -0,0 +1,389 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.nio.charset.StandardCharsets; +import java.time.ZonedDateTime; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalLong; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import io.helidon.common.mapper.MapperManager; + +/** + * A generic representation of the {@code Content-Disposition} header. + *

+ * Parameter encoding is not supported, other than + * URI percent + * encoding in the filename parameter. See {@link java.net.URLDecoder}. + *

+ * See also: + * + */ +public class ContentDisposition implements Http.HeaderValue { + private static final String NAME_PARAMETER = "name"; + private static final String FILENAME_PARAMETER = "filename"; + private static final String CREATION_DATE_PARAMETER = "creation-date"; + private static final String MODIFICATION_DATE_PARAMETER = "modification-date"; + private static final String READ_DATE_PARAMETER = "read-date"; + private static final String SIZE_PARAMETER = "size"; + private static final MapperManager MAPPER_MANAGER = MapperManager.create(); + private static final ContentDisposition EMPTY = ContentDisposition.builder() + .type("") + .build(); + + private static final Pattern DISPOSITION_PART_PATTERN = Pattern.compile("^(.+?)=\"?(.+?)\"?$"); + + private final String type; + private final Map parameters; + + private String value; + + private ContentDisposition(Builder builder) { + this.type = builder.type; + this.parameters = new LinkedHashMap<>(builder.parameters); + } + + /** + * A new builder to set up content disposition. + * + * @return builder + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Parse a received header value. + * + * @param headerValue content disposition header value + * @return a parsed content disposition + */ + public static ContentDisposition parse(String headerValue) { + Builder builder = ContentDisposition.builder(); + + // first split by semicolon + String[] parts = headerValue.split("(?<=[^\\\\]);"); + + if (parts.length > 0) { + String type = parts[0]; + if (type.indexOf('=') > -1) { + throw new IllegalArgumentException("No type defined"); + } else { + builder.type(type.trim()); + } + for (int i = 1; i < parts.length; i++) { + String part = parts[i]; + Matcher matcher = DISPOSITION_PART_PATTERN.matcher(part.trim()); + if (matcher.matches()) { + String name = matcher.group(1); + String value = matcher.group(2); + value = value.replace("\\\\", "\\"); + value = value.replace("\\\"", "\""); + value = value.replace("\\;", ";"); + builder.parameter(name, value); + } + } + } + return builder.build(); + } + + /** + * An empty content disposition. + * + * @return empty disposition with empty type + */ + public static ContentDisposition empty() { + return EMPTY; + } + + @Override + public String name() { + return Http.Header.CONTENT_DISPOSITION.defaultCase(); + } + + @Override + public Http.HeaderName headerName() { + return Http.Header.CONTENT_DISPOSITION; + } + + @Override + public String value() { + if (value == null) { + StringBuilder sb = new StringBuilder(); + sb.append(type); + for (Map.Entry param : parameters.entrySet()) { + sb.append(";"); + sb.append(param.getKey()); + sb.append("="); + if (SIZE_PARAMETER.equals(param.getKey())) { + sb.append(param.getValue()); + } else { + sb.append("\""); + sb.append(param.getValue()); + sb.append("\""); + } + } + value = sb.toString(); + } + return value; + } + + @Override + public T value(Class type) { + return MAPPER_MANAGER.map(value(), String.class, type, "http-header"); + } + + @Override + public List allValues() { + return List.of(value()); + } + + @Override + public int valueCount() { + return 1; + } + + @Override + public boolean sensitive() { + return false; + } + + @Override + public boolean changing() { + return true; + } + + @Override + public String toString() { + return value(); + } + + /** + * Get the value of the {@code name} parameter. In the case of a + * {@code form-data} disposition type the value is the original field name + * from the form. + * + * @return {@code Optional}, never {@code null} + */ + public Optional contentName() { + return Optional.ofNullable(parameters.get(NAME_PARAMETER)); + } + + /** + * Get the value of the {@code filename} parameter that can be used to + * suggest a filename to be used if the entity is detached and stored in a + * separate file. + * + * @return {@code Optional}, never {@code null} + */ + public Optional filename() { + String filename = null; + String value = parameters.get(FILENAME_PARAMETER); + if (value != null) { + filename = URLDecoder.decode(value, StandardCharsets.UTF_8); + } + return Optional.ofNullable(filename); + } + + /** + * Get the value of the {@code creation-date} parameter that can be used + * to indicate the date at which the file was created. + * + * @return {@code Optional}, never {@code null} + */ + public Optional creationDate() { + return Optional.ofNullable(parameters.get(CREATION_DATE_PARAMETER)).map(Http.DateTime::parse); + } + + /** + * Get the value of the {@code modification-date} parameter that can be + * used to indicate the date at which the file was last modified. + * + * @return {@code Optional}, never {@code null} + */ + public Optional modificationDate() { + return Optional.ofNullable(parameters.get(MODIFICATION_DATE_PARAMETER)).map(Http.DateTime::parse); + } + + /** + * Get the value of the {@code modification-date} parameter that can be + * used to indicate the date at which the file was last read. + * + * @return {@code Optional}, never {@code null} + */ + public Optional readDate() { + return Optional.ofNullable(parameters.get(READ_DATE_PARAMETER)).map(Http.DateTime::parse); + } + + /** + * Get the value of the {@code size} parameter that can be + * used to indicate an approximate size of the file in octets. + * + * @return {@code OptionalLong}, never {@code null} + */ + public OptionalLong size() { + String size = parameters.get(SIZE_PARAMETER); + if (size != null) { + return OptionalLong.of(Long.parseLong(size)); + } + return OptionalLong.empty(); + } + + /** + * Get the parameters map. + * + * @return map, never {@code null} + */ + public Map parameters() { + return Map.copyOf(parameters); + } + + /** + * Content disposition type. + * + * @return type of this content disposition + */ + public String type() { + return type; + } + + /** + * Fluent API builder for {@link io.helidon.common.http.ContentDisposition}. + */ + public static final class Builder implements io.helidon.common.Builder { + /** + * The form-data content disposition used by {@link io.helidon.common.media.type.MediaTypes#MULTIPART_FORM_DATA}. + */ + public static final String TYPE_FORM_DATA = "form-data"; + + private final Map parameters = new LinkedHashMap<>(); + + private String type = TYPE_FORM_DATA; + + private Builder() { + } + + @Override + public ContentDisposition build() { + return new ContentDisposition(this); + } + + /** + * Set the content disposition type. + * Defaults to {@value #TYPE_FORM_DATA}. + * + * @param type content disposition type + * @return updated builder + */ + public Builder type(String type) { + this.type = type.toLowerCase(); + return this; + } + + /** + * Set the content disposition {@code name} parameter. + * + * @param name control name + * @return this builder + */ + public Builder name(String name) { + parameters.put(NAME_PARAMETER, name); + return this; + } + + /** + * Set the content disposition {@code filename} parameter. + * + * @param filename filename parameter + * @return this builder + */ + public Builder filename(String filename) { + parameters.put(FILENAME_PARAMETER, URLEncoder.encode(filename, StandardCharsets.UTF_8)); + return this; + } + + /** + * Set the content disposition {@code creation-date} parameter. + * + * @param date date value + * @return this builder + */ + public Builder creationDate(ZonedDateTime date) { + parameters.put(CREATION_DATE_PARAMETER, date.format(Http.DateTime.RFC_1123_DATE_TIME)); + return this; + } + + /** + * Set the content disposition {@code modification-date} parameter. + * + * @param date date value + * @return this builder + */ + public Builder modificationDate(ZonedDateTime date) { + parameters.put(MODIFICATION_DATE_PARAMETER, date.format(Http.DateTime.RFC_1123_DATE_TIME)); + return this; + } + + /** + * Set the content disposition {@code read-date} parameter. + * + * @param date date value + * @return this builder + */ + public Builder readDate(ZonedDateTime date) { + parameters.put(READ_DATE_PARAMETER, date.format(Http.DateTime.RFC_1123_DATE_TIME)); + return this; + } + + /** + * Set the content disposition {@code size} parameter. + * + * @param size size value + * @return this builder + */ + public Builder size(long size) { + parameters.put(SIZE_PARAMETER, Long.toString(size)); + return this; + } + + /** + * Add a new content disposition header parameter. + * + * @param name parameter name + * @param value parameter value + * @return this builder + */ + public Builder parameter(String name, String value) { + parameters.put(name, value); + return this; + } + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/CookieParser.java b/common/http/src/main/java/io/helidon/common/http/CookieParser.java new file mode 100644 index 00000000000..f174b116f21 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/CookieParser.java @@ -0,0 +1,95 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import io.helidon.common.parameters.Parameters; + +final class CookieParser { + private static final Parameters EMPTY_COOKIES = Parameters.empty("cookies"); + + private static final String RFC2965_VERSION = "$Version"; + private static final String RFC2965_PATH = "$Path"; + private static final String RFC2965_DOMAIN = "$Domain"; + private static final String RFC2965_PORT = "$Port"; + + private CookieParser() { + } + + /** + * Parse cookies based on RFC6265 which also accepts older formats including RFC2965 but skips parameters. + * + *

Multiple cookies can be returned in a single headers and a single cookie-name can have multiple values. + * Note that base on RFC6265 an order of cookie values has no semantics. + * + * @param httpHeader cookie header + * @return a cookie name and values parsed into a parameter format. + */ + static Parameters parse(Http.HeaderValue httpHeader) { + Map> allCookies = new HashMap<>(); + for (String value : httpHeader.allValues()) { + parse(allCookies, value); + } + if (allCookies.isEmpty()) { + return EMPTY_COOKIES; + } + return Parameters.create("cookies", allCookies); + } + + static Parameters empty() { + return EMPTY_COOKIES; + } + + private static void parse(Map> allCookies, String cookieHeaderValue) { + // Beware RFC2965 + boolean isRfc2965 = false; + if (cookieHeaderValue.regionMatches(true, 0, RFC2965_VERSION, 0, RFC2965_VERSION.length())) { + isRfc2965 = true; + int ind = cookieHeaderValue.indexOf(';'); + if (ind < 0) { + return; + } else { + cookieHeaderValue = cookieHeaderValue.substring(ind + 1); + } + } + + for (String baseToken : HeaderHelper.tokenize(',', "\"", false, cookieHeaderValue)) { + for (String token : HeaderHelper.tokenize(';', "\"", false, baseToken)) { + int eqInd = token.indexOf('='); + if (eqInd > 0) { + String name = token.substring(0, eqInd).trim(); + if (name.isEmpty()) { + continue; // Name MOST NOT be empty; + } + if (isRfc2965 && name.charAt(0) == '$' + && ( + RFC2965_PATH.equalsIgnoreCase(name) || RFC2965_DOMAIN.equalsIgnoreCase(name) + || RFC2965_PORT.equalsIgnoreCase(name) || RFC2965_VERSION.equalsIgnoreCase(name))) { + continue; // Skip RFC2965 attributes + } + String value = token.substring(eqInd + 1).trim(); + allCookies.computeIfAbsent(name, it -> new ArrayList<>(1)).add(HeaderHelper.unwrap(value)); + } + } + } + } + +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeaderEnum.java b/common/http/src/main/java/io/helidon/common/http/HeaderEnum.java new file mode 100644 index 00000000000..6e97838388a --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeaderEnum.java @@ -0,0 +1,145 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.HashMap; +import java.util.Map; + +import io.helidon.common.buffers.Ascii; + +enum HeaderEnum implements Http.HeaderName { + ACCEPT("Accept"), + ACCEPT_CHARSET("Accept-Charset"), + ACCEPT_ENCODING("Accept-Encoding"), + ACCEPT_LANGUAGE("Accept-Language"), + ACCEPT_DATETIME("Accept-Datetime"), + ACCESS_CONTROL_ALLOW_CREDENTIALS("Access-Control-Allow-Credentials"), + ACCESS_CONTROL_ALLOW_HEADERS("Access-Control-Allow-Headers"), + ACCESS_CONTROL_ALLOW_METHODS("Access-Control-Allow-Methods"), + ACCESS_CONTROL_ALLOW_ORIGIN("Access-Control-Allow-Origin"), + ACCESS_CONTROL_EXPOSE_HEADERS("Access-Control-Expose-Headers"), + ACCESS_CONTROL_MAX_AGE("Access-Control-Max-Age"), + ACCESS_CONTROL_REQUEST_HEADERS("Access-Control-Request-Headers"), + ACCESS_CONTROL_REQUEST_METHOD("Access-Control-Request-Method"), + AUTHORIZATION("Authorization"), + COOKIE("Cookie"), + EXPECT("Expect"), + FORWARDED("Forwarded"), + FROM("From"), + HOST("Host"), + IF_MATCH("If-Match"), + IF_MODIFIED_SINCE("If-Modified-Since"), + IF_NONE_MATCH("If-None-Match"), + IF_RANGE("If-Range"), + IF_UNMODIFIED_SINCE("If-Unmodified-Since"), + MAX_FORWARDS("Max-Forwards"), + ORIGIN("Origin"), + PROXY_AUTHENTICATE("Proxy-Authenticate"), + PROXY_AUTHORIZATION("Proxy-Authorization"), + RANGE("Range"), + REFERER("Referer"), + REFRESH("Refresh"), + TE("TE"), + USER_AGENT("User-Agent"), + VIA("Via"), + ACCEPT_PATCH("Accept-Patch"), + ACCEPT_RANGES("Accept-Ranges"), + AGE("Age"), + ALLOW("Allow"), + ALT_SVC("Alt-Svc"), + CACHE_CONTROL("Cache-Control"), + CONNECTION("Connection"), + CONTENT_DISPOSITION("Content-Disposition"), + CONTENT_ENCODING("Content-Encoding"), + CONTENT_LANGUAGE("Content-Language"), + CONTENT_LENGTH("Content-Length"), + CONTENT_LOCATION("aa"), + CONTENT_RANGE("Content-Range"), + CONTENT_TYPE("Content-Type"), + DATE("Date"), + ETAG("ETag"), + EXPIRES("Expires"), + LAST_MODIFIED("Last-Modified"), + LINK("Link"), + LOCATION("Location"), + PRAGMA("Pragma"), + PUBLIC_KEY_PINS("Public-Key-Pins"), + RETRY_AFTER("Retry-After"), + SERVER("Server"), + SET_COOKIE("Set-Cookie"), + SET_COOKIE2("Set-Cookie2"), + STRICT_TRANSPORT_SECURITY("Strict-Transport-Security"), + TRAILER("Trailer"), + TRANSFER_ENCODING("Transfer-Encoding"), + TSV("TSV"), + UPGRADE("Upgrade"), + VARY("Vary"), + WARNING("Warning"), + WWW_AUTHENTICATE("WWW-Authenticate"), + X_HELIDON_CN("X-HELIDON-CN"); + + private static final Map BY_NAME; + private static final Map BY_CAP_NAME; + + static { + Map byName = new HashMap<>(); + Map byCapName = new HashMap<>(); + for (HeaderEnum value : HeaderEnum.values()) { + byName.put(value.lowerCase(), value); + byCapName.put(value.defaultCase(), value); + } + BY_NAME = byName; + BY_CAP_NAME = byCapName; + } + + private final String lowerCase; + private final String http1Case; + private final int index; + + HeaderEnum(String http1Case) { + this.http1Case = http1Case; + this.lowerCase = this.http1Case.toLowerCase(); + this.index = this.ordinal(); + } + + static Http.HeaderName byCapitalizedName(String name) { + Http.HeaderName found = BY_CAP_NAME.get(name); + if (found == null) { + return byName(Ascii.toLowerCase(name)); + } + return found; + } + + static Http.HeaderName byName(String lowerCase) { + return BY_NAME.get(lowerCase); + } + + @Override + public String lowerCase() { + return lowerCase; + } + + @Override + public String defaultCase() { + return http1Case; + } + + @Override + public int index() { + return index; + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeaderHelper.java b/common/http/src/main/java/io/helidon/common/http/HeaderHelper.java new file mode 100644 index 00000000000..c7a7d8db786 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeaderHelper.java @@ -0,0 +1,92 @@ +/* + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.ArrayList; +import java.util.List; + +/** + * Headers utility methods. + */ +final class HeaderHelper { + static final Runnable EMPTY_RUNNABLE = () -> { + }; + + private HeaderHelper() { + } + + /** + * Tokenize provide {@code text} by {@code separator} char respecting quoted sub-sequences. Quoted sub-sequences are + * parts of {@code text} which starts and ends by the same {@code quoteChar}. + * + * @param separator a token separator. + * @param quoteChars characters which can be used for quoting. Quoted part must start and ends with the same + * character. + * @param includeEmptyTokens return also tokens with {@code length == 0}. + * @param text a text to be tokenized. + * @return A list of tokens without separator characters. + */ + public static List tokenize(char separator, String quoteChars, boolean includeEmptyTokens, String text) { + char[] quotes = quoteChars == null ? new char[0] : quoteChars.toCharArray(); + StringBuilder token = new StringBuilder(); + List result = new ArrayList<>(); + boolean quoted = false; + char lastQuoteCharacter = ' '; + for (int i = 0; i < text.length(); i++) { + char ch = text.charAt(i); + if (quoted) { + if (ch == lastQuoteCharacter) { + quoted = false; + } + token.append(ch); + } else { + if (ch == separator) { + if (includeEmptyTokens || token.length() > 0) { + result.add(token.toString()); + } + token.setLength(0); + } else { + for (char quote : quotes) { + if (ch == quote) { + quoted = true; + lastQuoteCharacter = ch; + break; + } + } + token.append(ch); + } + } + } + if (includeEmptyTokens || token.length() > 0) { + result.add(token.toString()); + } + return result; + } + + /** + * Unwrap from double-quotes - if exists. + * + * @param str string to unwrap. + * @return unwrapped string. + */ + public static String unwrap(String str) { + if (str.length() >= 2 && '"' == str.charAt(0) && '"' == str.charAt(str.length() - 1)) { + return str.substring(1, str.length() - 1); + } + return str; + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeaderImpl.java b/common/http/src/main/java/io/helidon/common/http/HeaderImpl.java new file mode 100644 index 00000000000..9aa98e74a00 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeaderImpl.java @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.Objects; + +record HeaderImpl(String lowerCase, String defaultCase) implements Http.HeaderName { + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + var that = (HeaderImpl) obj; + return this.lowerCase.equals(that.lowerCase); + } + + @Override + public int hashCode() { + return Objects.hash(lowerCase); + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeaderValueArray.java b/common/http/src/main/java/io/helidon/common/http/HeaderValueArray.java new file mode 100644 index 00000000000..2967d71ba95 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeaderValueArray.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.ArrayList; +import java.util.List; + +import io.helidon.common.http.Http.HeaderName; + +class HeaderValueArray extends HeaderValueBase { + private final String[] originalValues; + private List values; + + HeaderValueArray(HeaderName name, boolean changing, boolean sensitive, String[] values) { + super(name, changing, sensitive, values[0]); + + this.originalValues = values; + } + + @Override + public Http.HeaderValueWriteable addValue(String value) { + if (values == null) { + values = new ArrayList<>(originalValues.length + 1); + values.addAll(List.of(originalValues)); + } + values.add(value); + return this; + } + + @Override + public List allValues() { + if (values == null) { + values = new ArrayList<>(List.of(originalValues)); + } + return values; + } + + @Override + public int valueCount() { + if (values == null) { + return originalValues.length; + } + + return values.size(); + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeaderValueBase.java b/common/http/src/main/java/io/helidon/common/http/HeaderValueBase.java new file mode 100644 index 00000000000..c40d6eab9bd --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeaderValueBase.java @@ -0,0 +1,105 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.Objects; + +import io.helidon.common.http.Http.HeaderName; +import io.helidon.common.mapper.MapperManager; + +abstract class HeaderValueBase implements Http.HeaderValueWriteable { + private static final MapperManager MAPPER_MANAGER = MapperManager.create(); + + private final HeaderName name; + private final String actualName; + private final String firstValue; + private final boolean changing; + private final boolean sensitive; + + HeaderValueBase(HeaderName name, boolean changing, boolean sensitive, String value) { + this.name = name; + this.actualName = name.defaultCase(); + this.changing = changing; + this.sensitive = sensitive; + this.firstValue = value; + } + + @Override + public abstract Http.HeaderValueWriteable addValue(String value); + + @Override + public String name() { + return actualName; + } + + @Override + public HeaderName headerName() { + return name; + } + + @Override + public String value() { + return firstValue; + } + + @Override + public T value(Class type) { + return MAPPER_MANAGER.map(value(), String.class, type, "http-header"); + } + + @Override + public abstract int valueCount(); + + @Override + public boolean sensitive() { + return sensitive; + } + + @Override + public boolean changing() { + return changing; + } + + @Override + public int hashCode() { + return Objects.hash(changing, sensitive, actualName, allValues()); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof HeaderValueBase that)) { + return false; + } + return changing == that.changing + && sensitive == that.sensitive + && actualName.equals(that.actualName) + && valueCount() == that.valueCount() + && allValues().equals(that.allValues()); + } + + @Override + public String toString() { + return "HttpHeaderImpl[" + + "name=" + name + ", " + + "values=" + allValues() + ", " + + "changing=" + changing + ", " + + "sensitive=" + sensitive + ']'; + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeaderValueCached.java b/common/http/src/main/java/io/helidon/common/http/HeaderValueCached.java new file mode 100644 index 00000000000..0b4526c3d0e --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeaderValueCached.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.nio.charset.StandardCharsets; +import java.util.List; + +import io.helidon.common.buffers.BufferData; + +class HeaderValueCached extends HeaderValueBase { + private final byte[] cached; + private final String value; + private final byte[] cachedHttp1Header; + + HeaderValueCached(Http.HeaderName name, boolean changing, boolean sensitive, byte[] cached, String value) { + super(name, changing, sensitive, value); + + this.value = value; + this.cached = cached; + + byte[] nameBytes = name.defaultCase().getBytes(StandardCharsets.US_ASCII); + cachedHttp1Header = new byte[nameBytes.length + cached.length + 4]; + int pos = nameBytes.length; + System.arraycopy(nameBytes, 0, cachedHttp1Header, 0, pos); + cachedHttp1Header[pos++] = ':'; + cachedHttp1Header[pos++] = ' '; + System.arraycopy(cached, 0, cachedHttp1Header, pos, cached.length); + pos += cached.length; + cachedHttp1Header[pos++] = '\r'; + cachedHttp1Header[pos] = '\n'; + } + + @Override + public byte[] valueBytes() { + return cached; + } + + @Override + public void writeHttp1Header(BufferData buffer) { + buffer.write(cachedHttp1Header); + } + + @Override + public Http.HeaderValueWriteable addValue(String value) { + throw new UnsupportedOperationException("Cannot change values of a cached header " + name()); + } + + @Override + public List allValues() { + return List.of(value); + } + + @Override + public int valueCount() { + return 1; + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeaderValueCopy.java b/common/http/src/main/java/io/helidon/common/http/HeaderValueCopy.java new file mode 100644 index 00000000000..7e1f987e787 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeaderValueCopy.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.ArrayList; +import java.util.List; + +class HeaderValueCopy extends HeaderValueBase { + private final Http.HeaderValue original; + private List values; + + HeaderValueCopy(Http.HeaderValue header) { + super(header.headerName(), header.changing(), header.sensitive(), header.value()); + + this.original = header; + } + + @Override + public Http.HeaderValueWriteable addValue(String value) { + if (values == null) { + values = new ArrayList<>(original.allValues()); + } + values.add(value); + return this; + } + + @Override + public List allValues() { + if (values == null) { + values = new ArrayList<>(original.allValues()); + } + return values; + } + + @Override + public int valueCount() { + if (values == null) { + values = new ArrayList<>(original.allValues()); + } + return values.size(); + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeaderValueLazy.java b/common/http/src/main/java/io/helidon/common/http/HeaderValueLazy.java new file mode 100644 index 00000000000..21a7570618c --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeaderValueLazy.java @@ -0,0 +1,66 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.ArrayList; +import java.util.List; + +import io.helidon.common.buffers.LazyString; + +class HeaderValueLazy extends HeaderValueBase { + private final LazyString value; + private List values; + + HeaderValueLazy(Http.HeaderName name, boolean changing, boolean sensitive, LazyString value) { + super(name, changing, sensitive, null); + + this.value = value; + } + + @Override + public Http.HeaderValueWriteable addValue(String value) { + if (values == null) { + values = new ArrayList<>(2); + values.add(this.value.toString()); + } + values.add(value); + return this; + } + + @Override + public String value() { + return value.toString(); + } + + @Override + public List allValues() { + if (values == null) { + values = new ArrayList<>(2); + values.add(value.toString()); + } + return values; + } + + @Override + public int valueCount() { + if (values == null) { + return 1; + } + + return values.size(); + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeaderValueList.java b/common/http/src/main/java/io/helidon/common/http/HeaderValueList.java new file mode 100644 index 00000000000..f360648039e --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeaderValueList.java @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.ArrayList; +import java.util.List; + +class HeaderValueList extends HeaderValueBase { + private List values; + + HeaderValueList(Http.HeaderName name, boolean changing, boolean sensitive, List values) { + super(name, changing, sensitive, values.get(0)); + + this.values = new ArrayList<>(values); + } + + @Override + public Http.HeaderValueWriteable addValue(String value) { + values.add(value); + return this; + } + + @Override + public List allValues() { + return values; + } + + @Override + public int valueCount() { + return values.size(); + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeaderValueSingle.java b/common/http/src/main/java/io/helidon/common/http/HeaderValueSingle.java new file mode 100644 index 00000000000..9c947868e8b --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeaderValueSingle.java @@ -0,0 +1,59 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.ArrayList; +import java.util.List; + +class HeaderValueSingle extends HeaderValueBase { + private final String value; + private List values; + + HeaderValueSingle(Http.HeaderName name, boolean changing, boolean sensitive, String value) { + super(name, changing, sensitive, value); + + this.value = value; + } + + @Override + public Http.HeaderValueWriteable addValue(String value) { + if (values == null) { + values = new ArrayList<>(2); + values.add(this.value); + } + values.add(value); + return this; + } + + @Override + public List allValues() { + if (values == null) { + values = new ArrayList<>(2); + values.add(this.value); + } + return values; + } + + @Override + public int valueCount() { + if (values == null) { + return 1; + } + + return values.size(); + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeadersClientRequest.java b/common/http/src/main/java/io/helidon/common/http/HeadersClientRequest.java new file mode 100644 index 00000000000..3b8a5885698 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeadersClientRequest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +/** + * Mutable headers of a client request. + */ +public interface HeadersClientRequest extends HeadersServerRequest, + HeadersWritable { + + /** + * Create client request headers from writable headers. + * + * @param delegate headers + * @return client request headers + */ + static HeadersClientRequest create(HeadersWritable delegate) { + return new HeadersClientRequestImpl(delegate); + } + + /** + * Create client request headers from headers. + * + * @param headers headers + * @return client request headers + */ + static HeadersClientRequest create(Headers headers) { + return new HeadersClientRequestImpl(HeadersWritable.create(headers)); + } + + /** + * Accepted media types. Supports quality factor and wildcards. + * + * @param accepted media types to accept + * @return updated headers + */ + default HeadersClientRequest accept(HttpMediaType... accepted) { + String[] values = new String[accepted.length]; + for (int i = 0; i < accepted.length; i++) { + HttpMediaType mediaType = accepted[i]; + values[i] = mediaType.text(); + } + set(Http.HeaderValue.create(Http.Header.ACCEPT, values)); + return this; + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeadersClientRequestImpl.java b/common/http/src/main/java/io/helidon/common/http/HeadersClientRequestImpl.java new file mode 100644 index 00000000000..17740fbc80b --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeadersClientRequestImpl.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.Iterator; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import io.helidon.common.http.Http.Header; +import io.helidon.common.http.Http.HeaderName; +import io.helidon.common.http.Http.HeaderValue; + +/** + * Client request headers. + */ +class HeadersClientRequestImpl implements HeadersClientRequest { + private final HeadersWritable delegate; + + private List mediaTypes; + + HeadersClientRequestImpl(HeadersWritable delegate) { + this.delegate = delegate; + } + + @Override + public List all(HeaderName name, Supplier> defaultSupplier) { + return delegate.all(name, defaultSupplier); + } + + @Override + public boolean contains(HeaderName name) { + return delegate.contains(name); + } + + @Override + public boolean contains(HeaderValue headerWithValue) { + return delegate.contains(headerWithValue); + } + + @Override + public HeaderValue get(HeaderName name) { + return delegate.get(name); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public List acceptedTypes() { + if (mediaTypes == null) { + if (delegate.contains(Header.ACCEPT)) { + List accepts = delegate.get(Header.ACCEPT).allValues(true); + + List mediaTypes = new ArrayList<>(accepts.size()); + for (String accept : accepts) { + mediaTypes.add(HttpMediaType.create(accept)); + } + Collections.sort(mediaTypes); + this.mediaTypes = List.copyOf(mediaTypes); + } else { + this.mediaTypes = List.of(); + } + } + + return mediaTypes; + } + + @Override + public HeadersClientRequest setIfAbsent(HeaderValue header) { + delegate.setIfAbsent(header); + return this; + } + + @Override + public HeadersClientRequest add(HeaderValue header) { + delegate.add(header); + return this; + } + + @Override + public HeadersClientRequest remove(HeaderName name) { + delegate.remove(name); + return this; + } + + @Override + public HeadersClientRequest remove(HeaderName name, Consumer removedConsumer) { + delegate.remove(name, removedConsumer); + return this; + } + + @Override + public HeadersClientRequest set(HeaderValue header) { + delegate.set(header); + return this; + } + + @Override + public Iterator iterator() { + return delegate.iterator(); + } + + @Override + public String toString() { + return delegate.toString(); + } + + @Override + public HeadersClientRequest clear() { + delegate.clear(); + return this; + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeadersClientResponse.java b/common/http/src/main/java/io/helidon/common/http/HeadersClientResponse.java new file mode 100644 index 00000000000..14408873d2a --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeadersClientResponse.java @@ -0,0 +1,108 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.net.URI; +import java.time.ZonedDateTime; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; + +import io.helidon.common.http.Http.DateTime; +import io.helidon.common.http.Http.HeaderValue; + +import static io.helidon.common.http.Http.Header.ACCEPT_PATCH; +import static io.helidon.common.http.Http.Header.EXPIRES; +import static io.helidon.common.http.Http.Header.LAST_MODIFIED; +import static io.helidon.common.http.Http.Header.LOCATION; + +/** + * HTTP Headers of a client response. + */ +public interface HeadersClientResponse extends Headers { + /** + * Create a new instance from headers parsed from client response. + * + * @param responseHeaders client response headers + * @return immutable instance of client response HTTP headers + */ + static HeadersClientResponse create(Headers responseHeaders) { + return new HeadersClientResponseImpl(responseHeaders); + } + + /** + * Accepted patches. + * + * @return list of accepted patches media types + */ + default List acceptPatches() { + List all = all(ACCEPT_PATCH, List::of); + List mediaTypes = new ArrayList<>(all.size()); + for (String value : all) { + mediaTypes.add(HttpMediaType.create(value)); + } + return mediaTypes; + } + + /** + * Optionally gets the value of {@link io.helidon.common.http.Http.Header#LOCATION} header. + *

+ * Used in redirection, or when a new resource has been created. + * + * @return Location header value. + */ + default Optional location() { + if (contains(LOCATION)) { + return Optional.of(get(LOCATION)) + .map(HeaderValue::value) + .map(URI::create); + } + return Optional.empty(); + } + + /** + * Optionally gets the value of {@link io.helidon.common.http.Http.Header#LAST_MODIFIED} header. + *

+ * The last modified date for the requested object. + * + * @return Last modified header value. + */ + default Optional lastModified() { + if (contains(LAST_MODIFIED)) { + return Optional.of(get(LAST_MODIFIED)) + .map(HeaderValue::value) + .map(DateTime::parse); + } + return Optional.empty(); + } + + /** + * Optionally gets the value of {@link io.helidon.common.http.Http.Header#EXPIRES} header. + *

+ * Gives the date/time after which the response is considered stale. + * + * @return Expires header value. + */ + default Optional expires() { + if (contains(EXPIRES)) { + return Optional.of(get(EXPIRES)) + .map(HeaderValue::value) + .map(DateTime::parse); + } + return Optional.empty(); + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeadersClientResponseImpl.java b/common/http/src/main/java/io/helidon/common/http/HeadersClientResponseImpl.java new file mode 100644 index 00000000000..48af7cdb668 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeadersClientResponseImpl.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + +class HeadersClientResponseImpl implements HeadersClientResponse { + private final Headers headers; + + HeadersClientResponseImpl(Headers headers) { + this.headers = headers; + } + + @Override + public List all(Http.HeaderName name, Supplier> defaultSupplier) { + return headers.all(name, defaultSupplier); + } + + @Override + public boolean contains(Http.HeaderName name) { + return headers.contains(name); + } + + @Override + public boolean contains(Http.HeaderValue headerWithValue) { + return headers.contains(headerWithValue); + } + + @Override + public Http.HeaderValue get(Http.HeaderName name) { + return headers.get(name); + } + + @Override + public int size() { + return headers.size(); + } + + @Override + public Iterator iterator() { + return headers.iterator(); + } + + @Override + public List acceptedTypes() { + return headers.acceptedTypes(); + } + + @Override + public String toString() { + return headers.toString(); + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeadersImpl.java b/common/http/src/main/java/io/helidon/common/http/HeadersImpl.java new file mode 100644 index 00000000000..f15ed27c602 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeadersImpl.java @@ -0,0 +1,277 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.NoSuchElementException; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import io.helidon.common.http.Http.HeaderName; +import io.helidon.common.http.Http.HeaderValue; +import io.helidon.common.http.Http.HeaderValueWriteable; + +@SuppressWarnings("unchecked") +class HeadersImpl> implements HeadersWritable { + static final int KNOWN_HEADER_SIZE = HeaderEnum.values().length; + /* + Optimization for most commonly used header names + */ + private final HeaderValue[] knownHeaders = new HeaderValue[KNOWN_HEADER_SIZE]; + // custom (unknown) headers are slower + private final Map customHeaders = new HashMap<>(); + private IntSet knownHeaderIndices = new IntSet(KNOWN_HEADER_SIZE); + + HeadersImpl() { + } + + HeadersImpl(Headers headers) { + for (HeaderValue header : headers) { + set(header); + } + } + + @Override + public List all(HeaderName name, Supplier> defaultSupplier) { + HeaderValue headerValue = find(name); + if (headerValue == null) { + return defaultSupplier.get(); + } + return headerValue.allValues(); + } + + @Override + public boolean contains(HeaderName name) { + return find(name) != null; + } + + @Override + public boolean contains(HeaderValue headerWithValue) { + HeaderValue headerValue = find(headerWithValue.headerName()); + if (headerValue == null) { + return false; + } + return headerWithValue.allValues().equals(headerValue.allValues()); + } + + @Override + public HeaderValue get(HeaderName name) { + HeaderValue headerValue = find(name); + if (headerValue == null) { + throw new NoSuchElementException("Header " + name + " is not present in these headers"); + } + return headerValue; + } + + @Override + public int size() { + return customHeaders.size() + knownHeaderIndices.size(); + } + + @Override + public List acceptedTypes() { + if (contains(Http.Header.ACCEPT)) { + List accepts = get(Http.Header.ACCEPT).allValues(true); + + List mediaTypes = new ArrayList<>(accepts.size()); + for (String accept : accepts) { + mediaTypes.add(HttpMediaType.create(accept)); + } + Collections.sort(mediaTypes); + return mediaTypes; + } else { + return List.of(); + } + } + + @Override + public Iterator iterator() { + return new HeaderIterator(); + } + + @Override + public T setIfAbsent(HeaderValue header) { + HeaderValue found = find(header.headerName()); + if (found == null) { + set(header); + } + + return (T) this; + } + + @Override + public T add(HeaderValue header) { + HeaderName name = header.headerName(); + HeaderValue headerValue = find(name); + if (headerValue == null) { + set(header); + } else { + HeaderValueWriteable writable; + + if (headerValue instanceof HeaderValueWriteable hvw) { + writable = hvw; + } else { + writable = HeaderValueWriteable.create(header); + } + for (String value : header.allValues()) { + writable.addValue(value); + } + set(writable); + } + return (T) this; + } + + @Override + public T remove(HeaderName name) { + doRemove(name); + return (T) this; + } + + @Override + public T remove(HeaderName name, Consumer removedConsumer) { + HeaderValue remove = doRemove(name); + if (remove != null) { + removedConsumer.accept(remove); + } + return (T) this; + } + + @Override + public T set(HeaderValue header) { + doSet(header); + return (T) this; + } + + @Override + public T clear() { + Arrays.fill(knownHeaders, null); + knownHeaderIndices = new IntSet(KNOWN_HEADER_SIZE); + customHeaders.clear(); + return (T) this; + } + + @Override + public String toString() { + StringBuilder builder = new StringBuilder(); + + for (HeaderValue headerValue : this) { + for (String value : headerValue.allValues()) { + builder.append(headerValue.name()) + .append(": "); + if (headerValue.sensitive()) { + builder.append("*".repeat(value.length())); + } else { + builder.append(value); + } + List details = new ArrayList<>(2); + if (headerValue.sensitive()) { + details.add("sensitive"); + } + if (headerValue.changing()) { + details.add("changing"); + } + if (!details.isEmpty()) { + builder.append(" ("); + builder.append(String.join(", ", details)); + builder.append(")"); + } + builder.append("\n"); + } + } + + return builder.toString(); + } + + public HeaderValue doRemove(HeaderName name) { + if (name instanceof HeaderEnum) { + int index = ((HeaderEnum) name).ordinal(); + HeaderValue value = knownHeaders[index]; + knownHeaders[index] = null; + knownHeaderIndices.remove(index); + return value; + } + return customHeaders.remove(name); + } + + private void doSet(HeaderValue header) { + HeaderName name = header.headerName(); + int index = name.index(); + if (index == -1) { + customHeaders.put(name, header); + } else { + knownHeaders[index] = header; + knownHeaderIndices.add(index); + } + } + + private HeaderValue find(HeaderName name) { + int index = name.index(); + + if (index > -1) { + return knownHeaders[index]; + } + + return customHeaders.get(name); + } + + private class HeaderIterator implements Iterator { + private final boolean noCustom = customHeaders.isEmpty(); + + private boolean inKnown = true; + private int last = -1; + private Iterator customHeadersIterator; + + @Override + public boolean hasNext() { + if (inKnown) { + last = knownHeaderIndices.nextSetBit(last + 1); + if (last >= 0) { + return true; + } + inKnown = false; + if (noCustom) { + return false; + } + ensureCustom(); + } + + return customHeadersIterator.hasNext(); + } + + @Override + public HeaderValue next() { + if (last >= 0) { + return knownHeaders[last]; + } + + ensureCustom(); + return customHeadersIterator.next(); + } + + private void ensureCustom() { + if (customHeadersIterator == null) { + customHeadersIterator = customHeaders.values().iterator(); + } + } + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeadersServerRequest.java b/common/http/src/main/java/io/helidon/common/http/HeadersServerRequest.java new file mode 100644 index 00000000000..8c941a66039 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeadersServerRequest.java @@ -0,0 +1,209 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.net.URI; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Optional; + +import io.helidon.common.http.Http.HeaderValue; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; +import io.helidon.common.parameters.Parameters; + +/** + * HTTP headers of a server request. + */ +public interface HeadersServerRequest extends Headers { + /** + * Header value of the non compliant {@code Accept} header sent by + * {@link java.net.HttpURLConnection} when none is set. + * + * @see JDK-8163921 + */ + HeaderValue HUC_ACCEPT_DEFAULT = HeaderValue.create(Http.Header.ACCEPT, + "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"); + + /** + * Accepted types for {@link #HUC_ACCEPT_DEFAULT}. + */ + List HUC_ACCEPT_DEFAULT_TYPES = List.of( + HttpMediaType.create(MediaTypes.TEXT_HTML), + HttpMediaType.create(MediaTypes.create("image", "gif")), + HttpMediaType.create(MediaTypes.create("image", "jpeg")), + HttpMediaType.builder().mediaType(MediaTypes.WILDCARD) + .q(0.2) + .build()); + + /** + * Create a new instance from headers. + * + * @param headers headers parsed from server request + * @return immutable instance of request headers + */ + static HeadersServerRequest create(Headers headers) { + return new HeadersServerRequestImpl(headers); + } + + /** + * Optionally returns a value of {@link Http.Header#IF_MODIFIED_SINCE} header. + *

+ * Allows a 304 Not Modified to be returned if content is unchanged. + * + * @return Content of {@link Http.Header#IF_MODIFIED_SINCE} header. + */ + default Optional ifModifiedSince() { + if (contains(Http.Header.IF_MODIFIED_SINCE)) { + return Optional.of(get(Http.Header.IF_MODIFIED_SINCE)) + .map(HeaderValue::value) + .map(Http.DateTime::parse); + } + + return Optional.empty(); + } + + /** + * Optionally returns a value of {@link Http.Header#IF_UNMODIFIED_SINCE} header. + *

+ * Only send the response if the entity has not been modified since a specific time. + * + * @return Content of {@link Http.Header#IF_UNMODIFIED_SINCE} header. + */ + default Optional ifUnmodifiedSince() { + if (contains(Http.Header.IF_UNMODIFIED_SINCE)) { + return Optional.of(get(Http.Header.IF_UNMODIFIED_SINCE)) + .map(HeaderValue::value) + .map(Http.DateTime::parse); + } + return Optional.empty(); + } + + /** + * Test if the given media type is acceptable as a response for this request. + * A media type is accepted if the {@code Accept} header is not present in the + * request or if it contains the provided media type. + * + * @param mediaType the media type to test + * @return {@code true} if provided type is acceptable, {@code false} otherwise + * @throws NullPointerException if the provided type is {@code null}. + */ + default boolean isAccepted(MediaType mediaType) { + List accepted = acceptedTypes(); + if (accepted.isEmpty()) { + return true; + } + for (HttpMediaType acceptedType : accepted) { + if (acceptedType.test(mediaType)) { + return true; + } + } + return false; + } + + /** + * Optionally returns a single media type from the given media types that is the + * best one accepted by the client. + * Method uses content negotiation {@link io.helidon.common.http.Http.Header#ACCEPT} + * header parameter and returns an empty value in case nothing matches. + * + * @param mediaTypes media type candidates, never null + * @return an accepted media type. + */ + default Optional bestAccepted(MediaType... mediaTypes) { + if (mediaTypes.length == 0) { + return Optional.empty(); + } + List accepted = acceptedTypes(); + if (accepted.isEmpty()) { + return Optional.of(mediaTypes[0]); + } + for (HttpMediaType acceptedType : accepted) { + for (MediaType mediaType : mediaTypes) { + if (acceptedType.test(mediaType)) { + return Optional.of(mediaType); + } + } + } + return Optional.empty(); + } + + /** + * Returns cookies (parsed from '{@code Cookie:}' header) based on RFC6265. + * It parses also older formats including RFC2965 but skips parameters. Only cookie {@code name} and {@code value} + * is returned. + * + *

Multiple cookies can be returned in a single headers and a single cookie-name can have multiple values. + * Note that base on RFC6265 an order of cookie values has no semantics. + * + * @return An unmodifiable cookies represented by {@link Parameters} interface where key is a name of the cookie and + * values are cookie values. + */ + default Parameters cookies() { + if (contains(Http.Header.COOKIE)) { + return CookieParser.parse(get(Http.Header.COOKIE)); + } else { + return CookieParser.empty(); + } + } + + /** + * Optionally returns acceptedTypes version in time ({@link io.helidon.common.http.Http.Header#ACCEPT_DATETIME} header). + * + * @return Acceptable version in time. + */ + default Optional acceptDatetime() { + if (contains(Http.Header.ACCEPT_DATETIME)) { + return Optional.of(get(Http.Header.ACCEPT_DATETIME)) + .map(HeaderValue::value) + .map(Http.DateTime::parse); + } + return Optional.empty(); + } + + /** + * Optionally returns request date ({@link io.helidon.common.http.Http.Header#DATE} header). + * + * @return Request date. + */ + default Optional date() { + if (contains(Http.Header.DATE)) { + return Optional.of(get(Http.Header.DATE)) + .map(HeaderValue::value) + .map(Http.DateTime::parse); + } + return Optional.empty(); + } + + /** + * Optionally returns the address of the previous web page (header {@link io.helidon.common.http.Http.Header#REFERER}) from which a link + * to the currently requested page was followed. + *

+ * The word {@code referrer} has been misspelled in the RFC as well as in most implementations to the point that it + * has become standard usage and is considered correct terminology + * + * @return Referrers URI + */ + default Optional referer() { + if (contains(Http.Header.REFERER)) { + return Optional.of(get(Http.Header.REFERER)) + .map(HeaderValue::value) + .map(URI::create); + } + return Optional.empty(); + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeadersServerRequestImpl.java b/common/http/src/main/java/io/helidon/common/http/HeadersServerRequestImpl.java new file mode 100644 index 00000000000..cc74e0b8aaa --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeadersServerRequestImpl.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.Iterator; +import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; + +import io.helidon.common.http.Http.HeaderName; +import io.helidon.common.parameters.Parameters; + +class HeadersServerRequestImpl implements HeadersServerRequest { + private final Headers headers; + private List cachedAccepted; + private Parameters cacheCookies; + private Optional cacheContentType; + + HeadersServerRequestImpl(Headers headers) { + this.headers = headers; + } + + @Override + public List all(HeaderName name, Supplier> defaultSupplier) { + return headers.all(name, defaultSupplier); + } + + @Override + public boolean contains(HeaderName name) { + return headers.contains(name); + } + + @Override + public boolean contains(Http.HeaderValue headerWithValue) { + return headers.contains(headerWithValue); + } + + @Override + public Http.HeaderValue get(HeaderName name) { + return headers.get(name); + } + + @SuppressWarnings("OptionalAssignedToNull") + @Override + public Optional contentType() { + if (cacheContentType == null) { + cacheContentType = HeadersServerRequest.super.contentType(); + } + return cacheContentType; + } + + @Override + public int size() { + return headers.size(); + } + + @Override + public List acceptedTypes() { + if (cachedAccepted != null) { + return cachedAccepted; + } + List acceptedTypes; + + List acceptValues = all(Http.Header.ACCEPT, List::of); + if (acceptValues.size() == 1 && HUC_ACCEPT_DEFAULT.value().equals(acceptValues.get(0))) { + acceptedTypes = HUC_ACCEPT_DEFAULT_TYPES; + } else { + acceptedTypes = acceptValues.stream() + .flatMap(h -> HeaderHelper.tokenize(',', "\"", false, h).stream()) + .map(String::trim) + .map(HttpMediaType::create) + .sorted() + .toList(); + } + cachedAccepted = acceptedTypes; + + return cachedAccepted; + } + + @Override + public Iterator iterator() { + return headers.iterator(); + } + + @Override + public String toString() { + return headers.toString(); + } + + @Override + public Parameters cookies() { + if (cacheCookies == null) { + cacheCookies = HeadersServerRequest.super.cookies(); + } + return cacheCookies; + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeadersServerResponse.java b/common/http/src/main/java/io/helidon/common/http/HeadersServerResponse.java new file mode 100644 index 00000000000..f098ca6533a --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeadersServerResponse.java @@ -0,0 +1,195 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.net.URI; +import java.time.Duration; +import java.time.Instant; +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import io.helidon.common.http.Http.Header; +import io.helidon.common.http.Http.HeaderValue; +import io.helidon.common.media.type.MediaType; + +import static io.helidon.common.http.Http.Header.EXPIRES; +import static io.helidon.common.http.Http.Header.LAST_MODIFIED; +import static io.helidon.common.http.Http.Header.LOCATION; + +/** + * Mutable headers of a server response. + */ +public interface HeadersServerResponse extends HeadersClientResponse, + HeadersWritable { + + /** + * Create a new instance of mutable server response headers. + * + * @return new server response headers + */ + static HeadersServerResponse create() { + return new HeadersServerResponseImpl(); + } + + /** + * Create a new instance of mutable server response headers. + * + * @param existing headers to add to these response headers + * @return new server response headers + */ + static HeadersServerResponse create(Headers existing) { + return new HeadersServerResponseImpl(existing); + } + + /** + * Adds one or more acceptedTypes path document formats + * (header {@link Header#ACCEPT_PATCH}). + * + * @param acceptableMediaTypes media types to add. + * @return this instance + */ + default HeadersServerResponse addAcceptPatches(HttpMediaType... acceptableMediaTypes) { + String[] values = new String[acceptableMediaTypes.length]; + for (int i = 0; i < acceptableMediaTypes.length; i++) { + HttpMediaType acceptableMediaType = acceptableMediaTypes[i]; + values[i] = acceptableMediaType.text(); + } + return add(HeaderValue.create(Header.ACCEPT_PATCH, + values)); + } + + /** + * Adds one or more acceptedTypes path document formats + * (header {@link Header#ACCEPT_PATCH}). + * + * @param acceptableMediaTypes media types to add. + * @return this instance + */ + default HeadersServerResponse addAcceptPatches(MediaType... acceptableMediaTypes) { + String[] values = new String[acceptableMediaTypes.length]; + for (int i = 0; i < acceptableMediaTypes.length; i++) { + MediaType acceptableMediaType = acceptableMediaTypes[i]; + values[i] = acceptableMediaType.text(); + } + return add(HeaderValue.create(Header.ACCEPT_PATCH, + values)); + } + + /** + * Adds {@code Set-Cookie} header specified in RFC6265. + * + * @param cookie a cookie definition + * @return this instance + */ + HeadersServerResponse addCookie(SetCookie cookie); + + /** + * Adds {@code Set-Cookie} header based on RFC6265 with {@code Max-Age} + * parameter. + * + * @param name name of the cookie + * @param value value of the cookie + * @param maxAge {@code Max-Age} cookie parameter + * @return this instance + */ + default HeadersServerResponse addCookie(String name, String value, Duration maxAge) { + return addCookie(SetCookie.builder(name, value) + .maxAge(maxAge) + .build()); + } + + /** + * Adds {@code Set-Cookie} header based on RFC2616. + * + * @param name name of the cookie + * @param value value of the cookie + * @return this instance + */ + default HeadersServerResponse addCookie(String name, String value) { + return addCookie(SetCookie.create(name, value)); + } + + /** + * Clears a cookie by adding a {@code Set-Cookie} header with an expiration date in the past. + * + * @param name name of the cookie. + * @return this instance + */ + HeadersServerResponse clearCookie(String name); + + /** + * Sets the value of {@link Header#LAST_MODIFIED} header. + *

+ * The last modified date for the requested object + * + * @param modified Last modified date/time. + * @return this instance + */ + default HeadersServerResponse lastModified(Instant modified) { + ZonedDateTime dt = ZonedDateTime.ofInstant(modified, ZoneId.systemDefault()); + return set(HeaderValue.create(LAST_MODIFIED, true, false, dt.format(Http.DateTime.RFC_1123_DATE_TIME))); + } + + /** + * Sets the value of {@link Header#LAST_MODIFIED} header. + *

+ * The last modified date for the requested object + * + * @param modified Last modified date/time. + * @return this instance + */ + default HeadersServerResponse lastModified(ZonedDateTime modified) { + return set(HeaderValue.create(LAST_MODIFIED, true, false, modified.format(Http.DateTime.RFC_1123_DATE_TIME))); + } + + /** + * Sets the value of {@link Header#LOCATION} header. + *

+ * Used in redirection, or when a new resource has been created. + * + * @param location Location header value. + * @return updated headers + */ + default HeadersServerResponse location(URI location) { + return set(HeaderValue.create(LOCATION, true, false, location.toASCIIString())); + } + + /** + * Sets the value of {@link io.helidon.common.http.Http.Header#EXPIRES} header. + *

+ * The date/time after which the response is considered stale. + * + * @param dateTime Expires date/time. + * @return updated headers + */ + default HeadersServerResponse expires(ZonedDateTime dateTime) { + return set(HeaderValue.create(EXPIRES, dateTime.format(Http.DateTime.RFC_1123_DATE_TIME))); + } + + /** + * Sets the value of {@link io.helidon.common.http.Http.Header#EXPIRES} header. + *

+ * The date/time after which the response is considered stale. + * + * @param dateTime Expires date/time. + * @return updated headers + */ + default HeadersServerResponse expires(Instant dateTime) { + return set(HeaderValue.create(EXPIRES, ZonedDateTime.ofInstant(dateTime, ZoneId.systemDefault()) + .format(Http.DateTime.RFC_1123_DATE_TIME))); + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeadersServerResponseImpl.java b/common/http/src/main/java/io/helidon/common/http/HeadersServerResponseImpl.java new file mode 100644 index 00000000000..591d3d4fabf --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeadersServerResponseImpl.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; + +import io.helidon.common.LazyValue; + +class HeadersServerResponseImpl extends HeadersImpl implements HeadersServerResponse { + private static final LazyValue START_OF_YEAR_1970 = LazyValue.create( + () -> ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneId.of("GMT+0"))); + + HeadersServerResponseImpl() { + } + + HeadersServerResponseImpl(Headers existing) { + super(existing); + } + + @Override + public HeadersServerResponse addCookie(SetCookie cookie) { + add(Http.HeaderValue.create(Http.Header.SET_COOKIE, cookie.toString())); + return this; + } + + @Override + public HeadersServerResponse clearCookie(String name) { + SetCookie expiredCookie = SetCookie.builder(name, "deleted") + .path("/") + .expires(START_OF_YEAR_1970.get()) + .build(); + + if (contains(Http.Header.SET_COOKIE)) { + remove(Http.Header.SET_COOKIE, it -> { + List currentValues = it.allValues(); + String[] newValues = new String[currentValues.size()]; + boolean found = false; + for (int i = 0; i < currentValues.size(); i++) { + String currentValue = currentValues.get(i); + if (SetCookie.parse(currentValue).name().equals(name)) { + newValues[i] = expiredCookie.text(); + found = true; + } else { + newValues[i] = currentValue; + } + } + if (!found) { + String[] values = new String[newValues.length + 1]; + System.arraycopy(newValues, 0, values, 0, newValues.length); + values[values.length - 1] = expiredCookie.text(); + newValues = values; + } + + set(Http.HeaderValue.create(Http.Header.SET_COOKIE, newValues)); + }); + } else { + addCookie(expiredCookie); + } + return this; + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HeadersWritable.java b/common/http/src/main/java/io/helidon/common/http/HeadersWritable.java new file mode 100644 index 00000000000..78eff504b9a --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HeadersWritable.java @@ -0,0 +1,174 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.List; +import java.util.function.Consumer; + +import io.helidon.common.http.Http.HeaderName; +import io.helidon.common.http.Http.HeaderValue; +import io.helidon.common.media.type.MediaType; + +/** + * HTTP Headers that are mutable. + * + * @param type of the headers (for inheritance) + */ +public interface HeadersWritable> extends Headers { + /** + * Create a new instance of writable headers. + * + * @return mutable HTTP headers + */ + static HeadersWritable create() { + return new HeadersImpl<>(); + } + + /** + * Create a new instance of writable headers from existing headers. + * + * @param headers headers to add to the new mutable instance + * @return mutable HTTP headers + */ + static HeadersWritable create(Headers headers) { + return new HeadersImpl<>(headers); + } + + /** + * Set a value of a header unless it is already present. + * + * @param header header with value to set + * @return this instance + */ + B setIfAbsent(HeaderValue header); + + /** + * Add a header or add a header value if the header is already present. + * + * @param header header with value + * @return this instance + */ + B add(HeaderValue header); + + /** + * Add a header or add a header value if the header is already present. + * + * @param header header name + * @param value header value(s) + * @return this instance + */ + default B add(HeaderName header, String... value) { + return add(HeaderValue.create(header, value)); + } + + /** + * Remove a header. + * + * @param name name of the header to remove + * @return this instance + */ + B remove(HeaderName name); + + /** + * Remove a header. + * + * @param name name of the header to remove + * @param removedConsumer consumer to be called with existing header; if the header did not exist, consumer will not be + * called + * @return this instance + */ + B remove(HeaderName name, Consumer removedConsumer); + + /** + * Sets the MIME type of the response body. + * + * @param contentType Media type of the content. + * @return this instance + */ + default B contentType(MediaType contentType) { + return set(HeaderValue.create(HeaderEnum.CONTENT_TYPE, contentType.text())); + } + + /** + * Sets the MIME type of the response body. + * + * @param contentType Media type of the content. + * @return this instance + */ + default B contentType(HttpMediaType contentType) { + return set(HeaderValue.create(HeaderEnum.CONTENT_TYPE, contentType.text())); + } + + /** + * Set a header and replace it if it already existed. + * + * @param header header to set + * @return this instance + */ + B set(HeaderValue header); + + /** + * Set a header and replace it if it already existed. + * Use {@link #set(io.helidon.common.http.Http.HeaderValue)} for headers that are known in advance (use a constant), + * or for headers obtained from Helidon server or client. This method is intended for headers that are unknown or change + * value often. + * + * @param name header name to set + * @param values value(s) of the header + * @return this instance + */ + default B set(HeaderName name, String... values) { + return set(HeaderValue.create(name, true, false, values)); + } + + /** + * Set a header and replace it if it already existed. + * Use {@link #set(io.helidon.common.http.Http.HeaderValue)} for headers that are known in advance (use a constant), + * or for headers obtained from Helidon server or client. This method is intended for headers that are unknown or change + * value often. + * + * @param name header name to set + * @param values value(s) of the header + * @return this instance + */ + default B set(HeaderName name, List values) { + return set(HeaderValue.create(name, values)); + } + + /** + * Content length of the entity in bytes. + * If not configured and a non-streaming entity is used, it will be configured based on + * the entity length. For streaming entities without content length we switch to chunked + * encoding (HTTP/1). + * + * @param length length of the entity + * @return this instance + */ + default B contentLength(long length) { + return set(HeaderValue.create(HeaderEnum.CONTENT_LENGTH, + true, + false, + String.valueOf(length))); + } + + /** + * Clear all current headers. + * + * @return this instance + */ + B clear(); +} diff --git a/common/http/src/main/java/io/helidon/common/http/Http1HeadersParser.java b/common/http/src/main/java/io/helidon/common/http/Http1HeadersParser.java new file mode 100644 index 00000000000..3d17aeb6f41 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/Http1HeadersParser.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.nio.charset.StandardCharsets; + +import io.helidon.common.buffers.Bytes; +import io.helidon.common.buffers.DataReader; +import io.helidon.common.buffers.LazyString; + +/** + * Used by both HTTP server and client to parse headers from {@link io.helidon.common.buffers.DataReader}. + */ +public final class Http1HeadersParser { + // TODO expand set of fastpath headers + private static final byte[] HD_HOST = (HeaderEnum.HOST.defaultCase() + ": ").getBytes(StandardCharsets.UTF_8); + private static final byte[] HD_ACCEPT = (HeaderEnum.ACCEPT.defaultCase() + ": ").getBytes(StandardCharsets.UTF_8); + private static final byte[] HD_CONNECTION = + (HeaderEnum.CONNECTION.defaultCase() + ": ").getBytes(StandardCharsets.UTF_8); + + private Http1HeadersParser() { + } + + /** + * Read headers from the provided reader. + * + * @param reader reader to pull data from + * @param maxHeadersSize maximal size of all headers, in bytes + * @param validate whether to validate headers + * @return a new mutable headers instance containing all headers parsed from reader + */ + public static HeadersWritable readHeaders(DataReader reader, int maxHeadersSize, boolean validate) { + HeadersWritable headers = HeadersWritable.create(); + int maxLength = maxHeadersSize; + + while (true) { + if (reader.startsWithNewLine()) { // new line found at 0 + reader.skip(2); + return headers; + } + + Http.HeaderName header = readHeaderName(reader, headers, maxLength, validate); + maxLength -= header.defaultCase().length() + 2; + int eol = reader.findNewLine(maxLength); + if (eol == maxLength) { + throw new IllegalStateException("Header size exceeded"); + } + // we do not need the string until somebody asks for this header (unless validation is on) + LazyString value = reader.readLazyString(StandardCharsets.US_ASCII, eol); + reader.skip(2); + maxLength -= eol + 1; + + headers.add(Http.HeaderValue.create(header, value)); + if (maxLength < 0) { + throw new IllegalStateException("Header size exceeded"); + } + } + } + + private static Http.HeaderName readHeaderName(DataReader reader, + HeadersWritable headers, + int maxLength, + boolean validate) { + switch (reader.lookup()) { + case (byte) 'H' -> { + if (reader.startsWith(HD_HOST)) { + reader.skip(HD_HOST.length); + return HeaderEnum.HOST; + } + } + case (byte) 'A' -> { + if (reader.startsWith(HD_ACCEPT)) { + reader.skip(HD_ACCEPT.length); + return HeaderEnum.ACCEPT; + } + } + case (byte) 'C' -> { + if (reader.startsWith(HD_CONNECTION)) { + reader.skip(HD_CONNECTION.length); + return HeaderEnum.CONNECTION; + } + } + default -> { + } + } + int col = reader.findOrNewLine(Bytes.COLON_BYTE, maxLength); + if (col == maxLength) { + throw new IllegalStateException("Header size exceeded"); + } else if (col < 0) { + throw new IllegalArgumentException("Invalid header, missing colon: " + reader.debugDataHex()); + } + + String headerName = reader.readAsciiString(col); + if (validate) { + HttpToken.validate(headerName); + } + Http.HeaderName header = Http.Header.create(headerName); + reader.skip(1); + if (Bytes.SPACE_BYTE != reader.read()) { + throw new IllegalArgumentException("Invalid header, space not after colon: " + reader.debugDataHex()); + } + return header; + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HttpMediaType.java b/common/http/src/main/java/io/helidon/common/http/HttpMediaType.java new file mode 100644 index 00000000000..b851402f6a1 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HttpMediaType.java @@ -0,0 +1,332 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.Map; +import java.util.Optional; +import java.util.TreeMap; +import java.util.function.Predicate; + +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; + +/** + * Media type used in HTTP headers, in addition to the media type definition, these may contain additional + * parameters, such as {@link #QUALITY_FACTOR_PARAMETER} and {@link #CHARSET_PARAMETER}. + */ +public sealed interface HttpMediaType extends Predicate, + Comparable, + MediaType permits HttpMediaTypeImpl { + /** + * The media type {@value} parameter name. + */ + String CHARSET_PARAMETER = "charset"; + /** + * The media type quality factor {@value} parameter name. + */ + String QUALITY_FACTOR_PARAMETER = "q"; + /** + * {@code application/json} media type without parameters. + */ + HttpMediaType APPLICATION_JSON = HttpMediaType.create(MediaTypes.APPLICATION_JSON); + /** + * application/json media type with UTF-8 charset. + */ + HttpMediaType JSON_UTF_8 = HttpMediaType.builder() + .mediaType(MediaTypes.APPLICATION_JSON) + .charset("UTF-8") + .build(); + /** + * {@code text/plain} media type without parameters. + */ + HttpMediaType TEXT_PLAIN = HttpMediaType.create(MediaTypes.TEXT_PLAIN); + /** + * text/plain media type with UTF-8 charset. + */ + HttpMediaType PLAINTEXT_UTF_8 = HttpMediaType.builder() + .mediaType(MediaTypes.TEXT_PLAIN) + .charset("UTF-8") + .build(); + + /** + * {@code application/octet-stream} media type without parameters. + */ + HttpMediaType APPLICATION_OCTET_STREAM = HttpMediaType.create(MediaTypes.APPLICATION_OCTET_STREAM); + + /** + * Predicate to test if {@link MediaType} is {@code application/json} or has {@code json} suffix. + */ + Predicate JSON_PREDICATE = JSON_UTF_8 + .or(mt -> mt.hasSuffix("json")); + + /** + * Predicate to test if {@link MediaType} is {@code text/event-stream} without any parameter or with parameter "element-type". + * This "element-type" has to be equal to "application/json". + */ + Predicate JSON_EVENT_STREAM_PREDICATE = HttpMediaType.create(MediaTypes.TEXT_EVENT_STREAM) + .and(mt -> mt.hasSuffix("event-stream")) + .and(mt -> !mt.parameters().containsKey("element-type") + || "application/json".equals(mt.parameters().get("element-type"))); + + /** + * A fluent API builder for creating customized Media type instances. + * + * @return a new builder + */ + static Builder builder() { + return new Builder(); + } + + /** + * Create a new HTTP media type from media type. + * + * @param mediaType media type + * @return a new HTTP media type without any parameters + */ + static HttpMediaType create(MediaType mediaType) { + return HttpMediaType.builder() + .mediaType(mediaType) + .build(); + } + + /** + * Parse media type from the provided string. + * + * @param mediaTypeString media type string + * @return HTTP media type parsed from the string + */ + static HttpMediaType create(String mediaTypeString) { + return Builder.parse(mediaTypeString); + } + + /** + * The underlying media type. + * + * @return media type + */ + MediaType mediaType(); + + /** + * Quality factor, if not defined, defaults to 1. + * + * @return quality factor + */ + double qualityFactor(); + + /** + * Read-only parameter map. Keys are case-insensitive. + * + * @return an immutable map of parameters. + */ + Map parameters(); + + /** + * Gets {@link java.util.Optional} value of charset parameter. + * + * @return Charset parameter. + */ + default Optional charset() { + return Optional.ofNullable(parameters().get(CHARSET_PARAMETER)); + } + + /** + * Check if this media type is compatible with another media type. E.g. + * image/* is compatible with image/jpeg, image/png, etc. Media type + * parameters are ignored. The function is commutative. + * + * @param other the media type to compare with. + * @return true if the types are compatible, false otherwise. + */ + @Override + boolean test(HttpMediaType other); + + /** + * Check if this media type is compatible with another media type. E.g. + * image/* is compatible with image/jpeg, image/png, etc. Media type + * parameters are ignored. The function is commutative. + * + * @param mediaType the media type to compare with. + * @return true if the types are compatible, false otherwise. + */ + boolean test(MediaType mediaType); + + @Override + default String type() { + return mediaType().type(); + } + + @Override + default String subtype() { + return mediaType().subtype(); + } + + /** + * Create a new {@link io.helidon.common.http.HttpMediaType} instance with the same type, subtype and parameters + * copied from the original instance and the supplied {@value #CHARSET_PARAMETER} parameter. + * + * @param charset the {@value #CHARSET_PARAMETER} parameter value. If {@code null} or empty + * the {@value #CHARSET_PARAMETER} parameter will not be set or updated. + * @return copy of the current {@code MediaType} instance with the {@value #CHARSET_PARAMETER} + * parameter set to the supplied value. + */ + default HttpMediaType withCharset(String charset) { + return builder() + .mediaType(mediaType()) + .parameters(parameters()) + .charset(charset) + .build(); + } + + /** + * Text of this media type, to be used on the wire. + * + * @return text including all parameters + */ + String text(); + + /** + * Create a new {@link io.helidon.common.http.HttpMediaType} instance with the same type, subtype and parameters + * copied from the original instance and the supplied custom parameter. + * + * @param name name of the parameter + * @param value value of the parameter + * @return copy of the current {@code MediaType} instance with the {@value #CHARSET_PARAMETER} + * parameter set to the supplied value. + */ + default HttpMediaType withParameter(String name, String value) { + return builder() + .mediaType(mediaType()) + .parameters(parameters()) + .addParameter(name, value) + .build(); + } + + /** + * Fluent API builder for {@link io.helidon.common.http.HttpMediaType}. + */ + class Builder implements io.helidon.common.Builder { + private final Map parameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + private MediaType mediaType = MediaTypes.WILDCARD; + + private Builder() { + } + + @Override + public HttpMediaType build() { + return new HttpMediaTypeImpl(this); + } + + /** + * Media type to use. + * + * @param mediaType media type + * @return updated builder + */ + public Builder mediaType(MediaType mediaType) { + this.mediaType = mediaType; + return this; + } + + /** + * Charset parameter to use. + * + * @param charset charset + * @return updated builder + */ + public Builder charset(String charset) { + parameters.put(CHARSET_PARAMETER, charset); + return this; + } + + /** + * Add a new parameter to the parameter map. + * + * @param parameter name of the parameter to add + * @param value value of the parameter to add + * @return updated builder instance + */ + public Builder addParameter(String parameter, String value) { + parameters.put(parameter.toLowerCase(), value); + return this; + } + + /** + * Parameters of the media type. + * + * @param parameters a map of media type parameters, default is empty + * @return updated builder instance + */ + public Builder parameters(Map parameters) { + this.parameters.clear(); + parameters.forEach((key, value) -> this.parameters.put(key.toLowerCase(), value)); + + return this; + } + + /** + * Quality factor parameter to use. + * + * @param q quality factor + * @return updated builder + */ + public Builder q(double q) { + addParameter(QUALITY_FACTOR_PARAMETER, String.valueOf(q)); + return this; + } + + Map parameters() { + return parameters; + } + + MediaType mediaType() { + return mediaType; + } + + private static HttpMediaType parse(String mediaTypeString) { + // text/plain; charset=UTF-8 + + Builder b = builder(); + int index = mediaTypeString.indexOf(';'); + if (index != -1) { + b.mediaType(MediaTypes.create(mediaTypeString.substring(0, index))); + String[] params = mediaTypeString.substring(index + 1).split(";"); + // each param is key=value + for (String param : params) { + int eq = param.indexOf('='); + if (eq == -1) { + throw new IllegalArgumentException("Invalid media type, param does not contain ="); + } + String value = param.substring(eq + 1).trim(); + if (!value.isEmpty()) { + // in case the value is "text/plain; charset=" we treat it as if charset was not defined + // same for any other parameter + + // dequote + if (value.charAt(0) == '"' && value.charAt(value.length() - 1) == '"') { + value = value.substring(1, value.length() - 1); + } + b.addParameter(param.substring(0, eq).trim(), value); + } + } + } else { + b.mediaType(MediaTypes.create(mediaTypeString)); + } + return b.build(); + } + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HttpMediaTypeImpl.java b/common/http/src/main/java/io/helidon/common/http/HttpMediaTypeImpl.java new file mode 100644 index 00000000000..cc1e2826c71 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HttpMediaTypeImpl.java @@ -0,0 +1,154 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.Map; +import java.util.Objects; +import java.util.TreeMap; + +import io.helidon.common.media.type.MediaType; + +final class HttpMediaTypeImpl implements HttpMediaType { + private final Map parameters = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private final MediaType mediaType; + + private double q = -1; + + HttpMediaTypeImpl(Builder builder) { + this.parameters.putAll(builder.parameters()); + this.mediaType = builder.mediaType(); + } + + @Override + public int compareTo(HttpMediaType o) { + // decreasing order + int compared = Double.compare(o.qualityFactor(), this.qualityFactor()); + if (compared == 0) { + if (this.mediaType.isWildcardSubtype() && !o.mediaType().isWildcardSubtype()) { + return 1; // the other is more important + } + if (this.mediaType.isWildcardType() && !o.mediaType().isWildcardType()) { + return 1; // the other is more important + } + if (o.mediaType().isWildcardSubtype() && !this.mediaType.isWildcardSubtype()) { + return -1; // we are more important (other has wildcard subtype) + } + if (o.mediaType().isWildcardType() && !this.mediaType.isWildcardType()) { + return -1; // we are more important (other has wildcard type) + } + return 0; // we do not care - same importance + } else { + return compared; + } + } + + @Override + public MediaType mediaType() { + return mediaType; + } + + @Override + public double qualityFactor() { + if (q == -1) { + String q = parameters.get("q"); + if (q == null) { + this.q = 1; + } else { + this.q = Double.parseDouble(q); + } + } + return q; + } + + @Override + public Map parameters() { + return Map.copyOf(parameters); + } + + @Override + public boolean test(HttpMediaType other) { + if (mediaType == null) { + return false; + } + + if (typeMismatch(other)) { + return false; + } + + return !subtypeMismatch(other); + } + + @Override + public boolean test(MediaType mediaType) { + if (mediaType == null) { + return false; + } + + if (typeMismatch(mediaType)) { + return false; + } + + return !subtypeMismatch(mediaType); + } + + @Override + public String text() { + StringBuilder result = new StringBuilder(mediaType.fullType()); + for (Map.Entry param : parameters.entrySet()) { + result.append("; ") + .append(param.getKey()) + .append('=') + .append(param.getValue()); + } + return result.toString(); + } + + @Override + public int hashCode() { + return Objects.hash(parameters, mediaType); + } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof HttpMediaTypeImpl that)) { + return false; + } + return parameters.equals(that.parameters) && mediaType.equals(that.mediaType); + } + + @Override + public String toString() { + return text(); + } + + private boolean subtypeMismatch(MediaType other) { + if (mediaType.isWildcardSubtype() || other.isWildcardSubtype()) { + return false; + } + return !mediaType.subtype().equalsIgnoreCase(other.subtype()); + } + + private boolean typeMismatch(MediaType other) { + if (mediaType.isWildcardType() || other.isWildcardType()) { + return false; + } + return !mediaType.type().equalsIgnoreCase(other.type()); + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HttpPrologue.java b/common/http/src/main/java/io/helidon/common/http/HttpPrologue.java new file mode 100644 index 00000000000..d444f9d2aa2 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HttpPrologue.java @@ -0,0 +1,240 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.Objects; + +import io.helidon.common.uri.UriFragment; +import io.helidon.common.uri.UriPath; +import io.helidon.common.uri.UriQuery; + +/** + * A prologue of an HTTP protocol. + */ +public class HttpPrologue { + private final String protocol; + private final String protocolVersion; + private final Http.Method method; + private final UriPath uriPath; + private final String rawQuery; + private final String rawFragment; + + private UriQuery query; + private UriFragment fragment; + + /** + * @param protocol protocol name, should be {@code HTTP} in most cases + * @param protocolVersion HTTP protocol version of this request + * @param method HTTP method of this request + * @param path URI path + * @param rawQuery query as received over the network, may be {@code null} when query is not present + * @param rawFragment fragment as received over the network; may be {@code null} when the prologue does not contain a + * fragment + */ + private HttpPrologue(String protocol, + String protocolVersion, + Http.Method method, + UriPath path, + String rawQuery, + String rawFragment) { + this.protocol = protocol; + this.protocolVersion = protocolVersion; + this.method = method; + this.uriPath = path; + this.rawQuery = rawQuery; + this.rawFragment = rawFragment; + } + + private HttpPrologue(String protocol, + String protocolVersion, + Http.Method httpMethod, + UriPath uriPath, + UriQuery uriQuery, + UriFragment uriFragment) { + this.protocol = protocol; + this.protocolVersion = protocolVersion; + this.method = httpMethod; + this.uriPath = uriPath; + this.rawQuery = uriQuery.rawValue(); + this.rawFragment = uriFragment.rawValue(); + + this.fragment = uriFragment; + this.query = uriQuery; + } + + /** + * Create a new prologue. + * + * @param protocol protocol + * @param protocolVersion protocol version + * @param httpMethod HTTP Method + * @param unresolvedPath unresolved path + * @param validatePath whether to validate path (that it contains only allowed characters) + * @return a new prologue + */ + public static HttpPrologue create(String protocol, + String protocolVersion, + Http.Method httpMethod, + String unresolvedPath, + boolean validatePath) { + + String rawPath = unresolvedPath; + String rawFragment; + String rawQuery; + + int fragment = rawPath.lastIndexOf('#'); + if (fragment > -1) { + rawFragment = rawPath.substring(fragment + 1); + rawPath = rawPath.substring(0, fragment); + } else { + rawFragment = null; + } + int query = rawPath.indexOf('?'); + if (query > -1) { + rawQuery = rawPath.substring(query + 1); + rawPath = rawPath.substring(0, query); + } else { + rawQuery = null; + } + + UriPath uriPath = UriPath.create(rawPath); + + if (validatePath) { + uriPath.validate(); + } + + return new HttpPrologue(protocol, + protocolVersion, + httpMethod, + uriPath, + rawQuery, + rawFragment); + } + + /** + * Create a new prologue with decoded values. + * + * @param protocol protocol + * @param protocolVersion protocol version + * @param httpMethod HTTP Method + * @param uriPath resolved path + * @param uriQuery resolved query + * @param uriFragment resolved fragment + * @return a new prologue + */ + public static HttpPrologue create(String protocol, + String protocolVersion, + Http.Method httpMethod, + UriPath uriPath, + UriQuery uriQuery, + UriFragment uriFragment) { + return new HttpPrologue(protocol, protocolVersion, httpMethod, uriPath, uriQuery, uriFragment); + } + + /** + * Protocol name, should be {@code HTTP} in most cases. + * + * @return protocol + */ + public String protocol() { + return protocol; + } + + /** + * HTTP protocol version of this request. + * + * @return protocol + */ + public String protocolVersion() { + return protocolVersion; + } + + /** + * HTTP method of this request. + * + * @return method + */ + public Http.Method method() { + return method; + } + + /** + * Path or the request. + * + * @return path + */ + public UriPath uriPath() { + return uriPath; + } + + /** + * Query of the request. + * + * @return query + */ + public UriQuery query() { + if (query == null) { + query = rawQuery == null ? UriQuery.empty() : UriQuery.create(rawQuery); + } + return query; + } + + /** + * Fragment of the request. + * + * @return fragment + */ + public UriFragment fragment() { + if (fragment == null) { + fragment = rawFragment == null ? UriFragment.empty() : UriFragment.create(rawFragment); + } + return fragment; + } + + @Override + public int hashCode() { + return Objects.hash(protocol, protocolVersion, method, uriPath, query(), fragment()); + } + + @Override + public boolean equals(Object obj) { + if (obj == this) { + return true; + } + if (obj == null || obj.getClass() != this.getClass()) { + return false; + } + var that = (HttpPrologue) obj; + return Objects.equals(this.protocol, that.protocol()) + && Objects.equals(this.protocolVersion, that.protocolVersion()) + && Objects.equals(this.method, that.method()) + && Objects.equals(this.uriPath, that.uriPath()) + && Objects.equals(this.query, that.query()) + && Objects.equals(this.fragment, that.fragment()); + } + + @Override + public String toString() { + return "HttpPrologueRecord[" + + "protocol=" + protocol + ", " + + "protocolVersion=" + protocolVersion + ", " + + "method=" + method + ", " + + "uriPath=" + uriPath + ", " + + "query=" + query() + ", " + + "fragment=" + fragment() + ']'; + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/HttpToken.java b/common/http/src/main/java/io/helidon/common/http/HttpToken.java new file mode 100644 index 00000000000..40b57d36c02 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/HttpToken.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +/** + * HTTP Token utility. + * Token is defined by the HTTP specification and must not contain a set of characters. + */ +public final class HttpToken { + private HttpToken() { + } + + /** + * Validate if this is a good HTTP token. + * + * @param token token to validate + * @throws IllegalArgumentException in case the token is not valid + */ + public static void validate(String token) throws IllegalArgumentException { + char[] chars = token.toCharArray(); + for (char aChar : chars) { + if (aChar > 254) { + throw new IllegalArgumentException("Token contains non-ASCII character"); + } + if (Character.isISOControl(aChar)) { + throw new IllegalArgumentException("Token contains control character"); + } + if (Character.isWhitespace(aChar)) { + throw new IllegalArgumentException("Token contains whitespace character"); + } + switch (aChar) { + case '(', ')', '<', '>', '@', ',', ';', ':', '\\', '"', '/', '[', ']', '?', '=', '{', '}' -> { + throw new IllegalArgumentException( + "Token contains illegal character: " + aChar); + } + default -> { + // this is a valid character + } + } + } + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/IntSet.java b/common/http/src/main/java/io/helidon/common/http/IntSet.java new file mode 100644 index 00000000000..8d96afb7ae0 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/IntSet.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +/** + * A Set like implementation optimized for integers. + */ +class IntSet { + private final long[] data; + private int size; + + /** + * Create a new set. + * + * @param sizeInBits expected size + */ + IntSet(int sizeInBits) { + data = new long[(sizeInBits + 63) / 64]; + } + + /** + * Get next value. + * + *

+     * for(int i=bs.nextSetBit(0); i>=0; i=bs.nextSetBit(i+1)) {
+     *   // operate on index i here
+     * }
+     * 
+ * + * @param i index + * @return next value + */ + public int nextSetBit(int i) { + int x = i / 64; + if (x >= data.length) { + return -1; + } + long w = data[x]; + w >>>= (i % 64); + if (w != 0) { + return i + Long.numberOfTrailingZeros(w); + } + ++x; + for (; x < data.length; ++x) { + if (data[x] != 0) { + return x * 64 + Long.numberOfTrailingZeros(data[x]); + } + } + return -1; + } + + /** + * Add next value. + * + * @param i value + */ + public void add(int i) { + data[i / 64] |= (1L << (i % 64)); + size++; + } + + /** + * Remove a value. + * + * @param i value + */ + public void remove(int i) { + data[i / 64] &= ~(1L << (i % 64)); + size--; + } + + /** + * Current size. + * + * @return size + */ + public int size() { + return size; + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/MethodHelper.java b/common/http/src/main/java/io/helidon/common/http/MethodHelper.java new file mode 100644 index 00000000000..293ae7d5fd5 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/MethodHelper.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.common.http; + +import java.util.ArrayList; +import java.util.List; + +final class MethodHelper { + private static final List KNOWN = new ArrayList<>(10); + private static AsciiMethodPair[] methods; + + private MethodHelper() { + } + + static void add(Http.Method method) { + KNOWN.add(method); + } + + static void methodsDone() { + methods = new AsciiMethodPair[KNOWN.size()]; + for (int i = 0; i < KNOWN.size(); i++) { + methods[i] = AsciiMethodPair.create(KNOWN.get(i)); + } + KNOWN.clear(); + } + + static Http.Method byName(String upperCase) { + // optimization over Map (most commonly used methods fastest) + for (AsciiMethodPair method : methods) { + if (method.string().equals(upperCase)) { + return method.method(); + } + } + return null; + } + + private record AsciiMethodPair(String string, Http.Method method) { + public static AsciiMethodPair create(Http.Method method) { + return new AsciiMethodPair(method.text(), method); + } + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/MethodPredicates.java b/common/http/src/main/java/io/helidon/common/http/MethodPredicates.java new file mode 100644 index 00000000000..e10ac622710 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/MethodPredicates.java @@ -0,0 +1,115 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.Set; + +import io.helidon.common.http.Http.Method; +import io.helidon.common.http.Http.MethodPredicate; + +class MethodPredicates { + static class TruePredicate implements MethodPredicate { + private static final TruePredicate INSTANCE = new TruePredicate(); + + static MethodPredicate get() { + return INSTANCE; + } + + @Override + public boolean test(Http.Method t) { + return true; + } + + @Override + public Set acceptedMethods() { + return Set.of(); + } + } + + static class SingleMethodEnumPredicate implements MethodPredicate { + private final Method method; + private final Set accepted; + + SingleMethodEnumPredicate(Method method) { + this.method = method; + this.accepted = Set.of(method); + } + + @Override + public boolean test(Method method) { + return method == this.method; + } + + @Override + public Set acceptedMethods() { + return accepted; + } + + @Override + public String toString() { + return method.text(); + } + } + + static class SingleMethodPredicate implements MethodPredicate { + private final Method method; + private final Set accepted; + + SingleMethodPredicate(Method method) { + this.method = method; + this.accepted = Set.of(method); + } + + @Override + public boolean test(Method method) { + return method.equals(this.method); + } + + @Override + public Set acceptedMethods() { + return accepted; + } + + @Override + public String toString() { + return method.text(); + } + } + + static class MethodsPredicate implements MethodPredicate { + private final Set methods; + + MethodsPredicate(Method... methods) { + this.methods = Set.of(methods); + } + + @Override + public boolean test(Method method) { + return methods.contains(method); + } + + @Override + public Set acceptedMethods() { + return methods; + } + + @Override + public String toString() { + return String.join(", ", methods.stream().map(Method::text).toList()); + } + } +} diff --git a/common/http/src/main/java/io/helidon/common/http/StatusHelper.java b/common/http/src/main/java/io/helidon/common/http/StatusHelper.java new file mode 100644 index 00000000000..0ce802ee147 --- /dev/null +++ b/common/http/src/main/java/io/helidon/common/http/StatusHelper.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.common.http; + +import java.util.ArrayList; +import java.util.List; + +final class StatusHelper { + private static final List KNOWN = new ArrayList<>(40); + private static StatusPair[] statuses; + + private StatusHelper() { + } + + static Http.Status find(int statusCode) { + for (StatusPair status : statuses) { + if (status.code == statusCode) { + return status.status; + } + } + + return null; + } + + static void add(Http.Status status) { + KNOWN.add(status); + } + + static void statusesDone() { + statuses = new StatusPair[KNOWN.size()]; + for (int i = 0; i < KNOWN.size(); i++) { + statuses[i] = StatusPair.create(KNOWN.get(i)); + } + KNOWN.clear(); + } + + private record StatusPair(int code, Http.Status status) { + public static StatusPair create(Http.Status status) { + return new StatusPair(status.code(), status); + } + } +} diff --git a/common/http/src/test/java/io/helidon/common/http/ContentDispositionTest.java b/common/http/src/test/java/io/helidon/common/http/ContentDispositionTest.java new file mode 100644 index 00000000000..bbe5c44ccbf --- /dev/null +++ b/common/http/src/test/java/io/helidon/common/http/ContentDispositionTest.java @@ -0,0 +1,246 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.time.ZoneId; +import java.time.ZonedDateTime; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.equalTo; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertThrows; + +class ContentDispositionTest { + private static final ZonedDateTime ZDT = ZonedDateTime.of(2008, 6, 3, 11, 5, 30, 0, ZoneId.of("Z")); + + @Test + void testNoType() { + assertThrows(IllegalArgumentException.class, () -> ContentDisposition.parse("foo=\"bar\"; bar=\"foo\"")); + } + + @Test + void testWhiteSpaces() { + ContentDisposition cd = ContentDisposition.parse(" inline;foo=bar; bar=foo ; abc=xyz "); + assertThat(cd.type(), is(equalTo("inline"))); + assertThat(cd.parameters(), is(notNullValue())); + assertThat(cd.parameters().size(), is(equalTo(3))); + assertThat(cd.parameters().get("foo"), is(equalTo("bar"))); + assertThat(cd.parameters().get("bar"), is(equalTo("foo"))); + assertThat(cd.parameters().get("abc"), is(equalTo("xyz"))); + } + + @Test + void testQuotedString() { + ContentDisposition cd = ContentDisposition.parse("inline; foo=\" b a r\""); + assertThat(cd.type(), is(equalTo("inline"))); + assertThat(cd.parameters(), is(notNullValue())); + assertThat(cd.parameters().size(), is(equalTo(1))); + assertThat(cd.parameters().get("foo"), is(equalTo(" b a r"))); + } + + @Test + void testName() { + ContentDisposition cd = ContentDisposition.parse("form-data; name=user"); + assertThat(cd.type(), is(equalTo("form-data"))); + assertThat(cd.contentName().isPresent(), is(equalTo(true))); + assertThat(cd.contentName().get(), is(equalTo("user"))); + assertThat(cd.parameters(), is(notNullValue())); + assertThat(cd.parameters().size(), is(equalTo(1))); + } + + @Test + void testFilename() { + ContentDisposition cd = ContentDisposition.parse("attachment; filename=index.html"); + assertThat(cd.type(), is(equalTo("attachment"))); + assertThat(cd.filename().isPresent(), is(equalTo(true))); + assertThat(cd.filename().get(), is(equalTo("index.html"))); + assertThat(cd.parameters(), is(notNullValue())); + assertThat(cd.parameters().size(), is(equalTo(1))); + } + + @Test + void testCreationDate() { + ContentDisposition cd = ContentDisposition.parse("attachment; creation-date=\"Tue, 3 Jun 2008 11:05:30 GMT\""); + assertThat(cd.type(), is(equalTo("attachment"))); + assertThat(cd.creationDate().isPresent(), is(equalTo(true))); + assertThat(cd.creationDate().get(), is(equalTo(ZDT))); + assertThat(cd.parameters(), is(notNullValue())); + assertThat(cd.parameters().size(), is(equalTo(1))); + } + + @Test + void testModificationDate() { + ContentDisposition cd = ContentDisposition.parse("attachment; modification-date=\"Tue, 3 Jun 2008 11:05:30 GMT\""); + assertThat(cd.type(), is(equalTo("attachment"))); + assertThat(cd.modificationDate().isPresent(), is(equalTo(true))); + assertThat(cd.modificationDate().get(), is(equalTo(ZDT))); + assertThat(cd.parameters(), is(notNullValue())); + assertThat(cd.parameters().size(), is(equalTo(1))); + } + + @Test + void testReadDate() { + ContentDisposition cd = ContentDisposition.parse("attachment; read-date=\"Tue, 3 Jun 2008 11:05:30 GMT\""); + assertThat(cd.type(), is(equalTo("attachment"))); + assertThat(cd.readDate().isPresent(), is(equalTo(true))); + assertThat(cd.readDate().get(), is(equalTo(ZDT))); + assertThat(cd.parameters(), is(notNullValue())); + assertThat(cd.parameters().size(), is(equalTo(1))); + } + + @Test + void testSize() { + ContentDisposition cd = ContentDisposition.parse("inline; size=128"); + assertThat(cd.type(), is(equalTo("inline"))); + assertThat(cd.size().isPresent(), is(equalTo(true))); + assertThat(cd.size().getAsLong(), is(equalTo(128L))); + assertThat(cd.parameters(), is(notNullValue())); + assertThat(cd.parameters().size(), is(equalTo(1))); + } + + @Test + void testPercentEncodedFilename() { + ContentDisposition cd = ContentDisposition.parse("inline; filename=the%20great%20file.html"); + assertThat(cd.type(), is(equalTo("inline"))); + assertThat(cd.filename().isPresent(), is(equalTo(true))); + assertThat(cd.filename().get(), is(equalTo("the great file.html"))); + assertThat(cd.parameters(), is(notNullValue())); + assertThat(cd.parameters().size(), is(equalTo(1))); + } + + @Test + void testFilenameWithBackslash() { + ContentDisposition cd = ContentDisposition.parse("inline; filename=\"C:\\index.html\""); + assertThat(cd.type(), is(equalTo("inline"))); + assertThat(cd.filename().isPresent(), is(equalTo(true))); + assertThat(cd.filename().get(), is(equalTo("C:\\index.html"))); + assertThat(cd.parameters(), is(notNullValue())); + assertThat(cd.parameters().size(), is(equalTo(1))); + } + + @Test + void testParamsWithQuotedPair() { + ContentDisposition cd = ContentDisposition.parse("inline; foo=\"\\\\\"; bar=\"\\\"\"; car=\"\\;\""); + assertThat(cd.type(), is(equalTo("inline"))); + assertThat(cd.parameters(), is(notNullValue())); + assertThat(cd.parameters().size(), is(equalTo(3))); + assertThat(cd.parameters().get("foo"), is(equalTo("\\"))); + assertThat(cd.parameters().get("bar"), is(equalTo("\""))); + assertThat(cd.parameters().get("car"), is(";")); + } + + @Test + void testCaseInsensitiveType() { + ContentDisposition cd = ContentDisposition.parse("aTTachMENT; name=bar"); + assertThat(cd.type(), is(equalTo("attachment"))); + assertThat(cd.contentName().isPresent(), is(equalTo(true))); + assertThat(cd.contentName().get(), is(equalTo("bar"))); + assertThat(cd.parameters(), is(notNullValue())); + assertThat(cd.parameters().size(), is(equalTo(1))); + } + + @Test + void testBuilderWithFilenameEncoded() { + ContentDisposition cd = ContentDisposition.builder() + .type("inline") + .filename("filename with spaces.txt") + .build(); + assertThat(cd.type(), is(equalTo("inline"))); + assertThat(cd.filename().isPresent(), is(equalTo(true))); + assertThat(cd.filename().get(), is(equalTo("filename with spaces.txt"))); + assertThat(cd.size().isPresent(), is(equalTo(false))); + } + + @Test + void testBuilderWithDates() { + ContentDisposition cd = ContentDisposition.builder() + .type("inline") + .modificationDate(ZDT) + .creationDate(ZDT) + .readDate(ZDT) + .build(); + assertThat(cd.type(), is(equalTo("inline"))); + assertThat(cd.modificationDate().isPresent(), is(equalTo(true))); + assertThat(cd.modificationDate().get(), is(equalTo(ZDT))); + assertThat(cd.creationDate().isPresent(), is(equalTo(true))); + assertThat(cd.creationDate().get(), is(equalTo(ZDT))); + assertThat(cd.readDate().isPresent(), is(equalTo(true))); + assertThat(cd.readDate().get(), is(equalTo(ZDT))); + } + + @Test + void testBuilderWithSize() { + ContentDisposition cd = ContentDisposition.builder() + .type("inline") + .size(128) + .build(); + assertThat(cd.type(), is(equalTo("inline"))); + assertThat(cd.size().isPresent(), is(equalTo(true))); + assertThat(cd.size().getAsLong(), is(equalTo(128L))); + } + + @Test + void testBuilderWithCustomParam() { + ContentDisposition cd = ContentDisposition.builder() + .type("inline") + .parameter("foo", "bar") + .build(); + assertThat(cd.type(), is(equalTo("inline"))); + assertThat(cd.parameters().get("foo"), is(equalTo("bar"))); + } + + @Test + void testContentDispositionDefault() { + ContentDisposition cd = ContentDisposition.builder().build(); + assertThat(cd.type(), is(equalTo("form-data"))); + assertThat(cd.parameters().size(), is(0)); + } + + @Test + void testQuotes() { + String template = "form-data;" + + "name=\"someName\";" + + "filename=\"file.txt\";" + + "size=300"; + ContentDisposition cd = ContentDisposition.builder() + .name("someName") + .filename("file.txt") + .size(300) + .build(); + assertThat(cd.value(), is(equalTo(template))); + } + + @Test + void testDateQuotes() { + ZonedDateTime zonedDateTime = ZonedDateTime.now(); + String date = zonedDateTime.format(Http.DateTime.RFC_1123_DATE_TIME); + // order is in order of insertion backed by LinkedMap -> we want to preserve this + String template = "form-data;" + + "creation-date=\"" + date + "\";" + + "modification-date=\"" + date + "\";" + + "read-date=\"" + date + "\""; + ContentDisposition cd = ContentDisposition.builder() + .creationDate(zonedDateTime) + .modificationDate(zonedDateTime) + .readDate(zonedDateTime) + .build(); + assertThat(cd.toString(), is(equalTo(template))); + } +} \ No newline at end of file diff --git a/common/http/src/test/java/io/helidon/common/http/CookieParserTest.java b/common/http/src/test/java/io/helidon/common/http/CookieParserTest.java new file mode 100644 index 00000000000..e6cc95e23d1 --- /dev/null +++ b/common/http/src/test/java/io/helidon/common/http/CookieParserTest.java @@ -0,0 +1,74 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import io.helidon.common.parameters.Parameters; + +import org.junit.jupiter.api.Test; + +import static io.helidon.common.http.Http.Header.COOKIE; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; + +class CookieParserTest { + @Test + public void rfc2965() throws Exception { + String header = "$version=1; foo=bar; $Domain=google.com, aaa=bbb, c=cool; $Domain=google.com; $Path=\"/foo\""; + Parameters p = CookieParser.parse(COOKIE.withValue(header)); + assertThat(p, notNullValue()); + assertThat(p.all("foo"), contains("bar")); + assertThat(p.all("aaa"), contains("bbb")); + assertThat(p.all("c"), contains("cool")); + assertThat(p.contains("$Domain"), is(false)); + assertThat(p.contains("$Path"), is(false)); + assertThat(p.contains("$Version"), is(false)); + } + + @Test + public void unquote() throws Exception { + Parameters p = CookieParser.parse(COOKIE.withValue("foo=\"bar\"; aaa=bbb; c=\"what_the_hell\"; aaa=\"ccc\"")); + assertThat(p, notNullValue()); + assertThat(p.all("foo"), contains("bar")); + assertThat(p.all("aaa"), contains("bbb", "ccc")); + } + + @Test + void testEmpty() { + Parameters empty = CookieParser.empty(); + assertThat(empty.isEmpty(), is(true)); + } + + @Test + void testMultiValueSingleHeader() { + Parameters cookies = CookieParser.parse(COOKIE.withValue("foo=bar; aaa=bbb; c=here; aaa=ccc")); + assertThat(cookies, notNullValue()); + assertThat(cookies.all("foo"), contains("bar")); + assertThat(cookies.all("aaa"), contains("bbb", "ccc")); + assertThat(cookies.all("c"), contains("here")); + } + + @Test + void testMultiValueMultiHeader() { + Parameters cookies = CookieParser.parse(COOKIE.withValue("foo=bar; aaa=bbb; c=here", "aaa=ccc")); + assertThat(cookies, notNullValue()); + assertThat(cookies.all("foo"), contains("bar")); + assertThat(cookies.all("aaa"), contains("bbb", "ccc")); + assertThat(cookies.all("c"), contains("here")); + } +} \ No newline at end of file diff --git a/common/http/src/test/java/io/helidon/common/http/HeaderNamesTest.java b/common/http/src/test/java/io/helidon/common/http/HeaderNamesTest.java new file mode 100644 index 00000000000..1b16f56af4d --- /dev/null +++ b/common/http/src/test/java/io/helidon/common/http/HeaderNamesTest.java @@ -0,0 +1,76 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsEmptyCollection.emptyCollectionOf; +import static org.junit.jupiter.api.Assertions.assertAll; + +class HeaderNamesTest { + private static final Class clazz = Http.Header.class; + private static final Set constants = Stream.of(clazz.getDeclaredFields()) + .filter(it -> Modifier.isStatic(it.getModifiers())) + .filter(it -> Modifier.isFinal(it.getModifiers())) + .filter(it -> Modifier.isPublic(it.getModifiers())) + .map(Field::getName) + .collect(Collectors.toSet()); + + @Test + void testAllEnumValuesHaveConstants() { + HeaderEnum[] expectedNames = HeaderEnum.values(); + + Set missing = new LinkedHashSet<>(); + + for (HeaderEnum expectedName : expectedNames) { + String name = expectedName.name(); + if (!constants.contains(name)) { + missing.add(name); + } + } + + assertThat(missing, emptyCollectionOf(String.class)); + } + + @Test + void testAllConstantsAreValid() throws NoSuchFieldException, IllegalAccessException { + // this is to test correct initialization (there may be an issue when the constants + // are defined on the interface and implemented by enum outside of it) + for (String constant : constants) { + Http.HeaderName value = (Http.HeaderName) clazz.getField(constant) + .get(null); + + assertAll( + () -> assertThat(value, notNullValue()), + () -> assertThat(value.defaultCase(), notNullValue()), + () -> assertThat(value.lowerCase(), notNullValue()), + () -> assertThat(value.index(), not(-1)) + ); + + } + } +} \ No newline at end of file diff --git a/common/http/src/test/java/io/helidon/common/http/Http1HeadersParserTest.java b/common/http/src/test/java/io/helidon/common/http/Http1HeadersParserTest.java new file mode 100644 index 00000000000..b7da4dbc986 --- /dev/null +++ b/common/http/src/test/java/io/helidon/common/http/Http1HeadersParserTest.java @@ -0,0 +1,56 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.nio.charset.StandardCharsets; + +import io.helidon.common.buffers.DataReader; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.hasItems; +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class Http1HeadersParserTest { + @Test + void testHeadersAreCaseInsensitive() { + DataReader reader = new DataReader(() -> ( + "Set-Cookie: c1=v1\r\nSet-Cookie: c2=v2\r\n" + + "Header: hv1\r\nheader: hv2\r\nheaDer: hv3\r\n" + + "\r\n").getBytes(StandardCharsets.US_ASCII)); + HeadersWritable headers = Http1HeadersParser.readHeaders(reader, 1024, true); + + testHeader(headers, "Set-Cookie", "c1=v1", "c2=v2"); + testHeader(headers, "set-cookie", "c1=v1", "c2=v2"); + testHeader(headers, "SET-CooKIE", "c1=v1", "c2=v2"); + + testHeader(headers, "header", "hv1", "hv2", "hv3"); + testHeader(headers, "Header", "hv1", "hv2", "hv3"); + testHeader(headers, "HeADer", "hv1", "hv2", "hv3"); + } + + private void testHeader(Headers headers, String header, String... values) { + Http.HeaderName headerName = Http.Header.create(header); + assertThat("Headers should contain header: " + headerName.lowerCase(), + headers.contains(headerName), + is(true)); + assertThat("Header " + headerName.lowerCase(), + headers.get(headerName).allValues(), + hasItems(values)); + } +} \ No newline at end of file diff --git a/common/http/src/test/java/io/helidon/common/http/HttpMediaTypeImplTest.java b/common/http/src/test/java/io/helidon/common/http/HttpMediaTypeImplTest.java new file mode 100644 index 00000000000..f9bc589479c --- /dev/null +++ b/common/http/src/test/java/io/helidon/common/http/HttpMediaTypeImplTest.java @@ -0,0 +1,69 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; + +import org.hamcrest.core.IsCollectionContaining; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.MatcherAssert.assertThat; + +class HttpMediaTypeImplTest { + @Test + void testCorrectOrder() { + MediaType textWildcard = MediaTypes.create("text/*"); + MediaType appWildcard = MediaTypes.create("application/*"); + + List list = new ArrayList<>(); + list.add(HttpMediaType.builder().mediaType(textWildcard).q(0.2).build()); + list.add(HttpMediaType.builder().mediaType(MediaTypes.APPLICATION_ATOM_XML).q(0.1).build()); + list.add(HttpMediaType.builder().mediaType(MediaTypes.WILDCARD).q(0.2).build()); + list.add(HttpMediaType.builder().mediaType(MediaTypes.APPLICATION_JSON).q(0.2).build()); + list.add(HttpMediaType.builder().mediaType(MediaTypes.APPLICATION_JAVASCRIPT).q(0.5).build()); + list.add(HttpMediaType.builder().mediaType(MediaTypes.APPLICATION_YAML).q(1).build()); + list.add(HttpMediaType.builder().mediaType(appWildcard).build()); + + Collections.sort(list); + List mediaTypes = list.stream() + .map(HttpMediaType::mediaType) + .toList(); + /* + order MUST be as follows: + app/yaml + app/* (wildcard is less important than explicit type, but q is 1, so above javascript) + app/javascript + app/json (explicit) + text/* (explicit type) + * /* (full wildcard) + app/xml (last, lowest q) + */ + + assertThat(mediaTypes, IsCollectionContaining.hasItems(MediaTypes.APPLICATION_YAML, + appWildcard, + MediaTypes.APPLICATION_JAVASCRIPT, + MediaTypes.APPLICATION_JSON, + textWildcard, + MediaTypes.WILDCARD, + MediaTypes.APPLICATION_ATOM_XML)); + } +} \ No newline at end of file diff --git a/common/http/src/test/java/io/helidon/common/http/HttpMethodTest.java b/common/http/src/test/java/io/helidon/common/http/HttpMethodTest.java new file mode 100644 index 00000000000..ec45e5ece76 --- /dev/null +++ b/common/http/src/test/java/io/helidon/common/http/HttpMethodTest.java @@ -0,0 +1,58 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class HttpMethodTest { + private static final Class clazz = Http.Method.class; + private static final Set constants = Stream.of(clazz.getDeclaredFields()) + .filter(it -> Modifier.isStatic(it.getModifiers())) + .filter(it -> Modifier.isFinal(it.getModifiers())) + .filter(it -> Modifier.isPublic(it.getModifiers())) + .map(Field::getName) + .collect(Collectors.toSet()); + + + @Test + void testAllConstantsAreValid() throws NoSuchFieldException, IllegalAccessException { + // this is to test correct initialization (there may be an issue when the constants + // are defined on the interface and implemented by enum outside of it) + for (String constant : constants) { + Http.Method value = (Http.Method) clazz.getField(constant) + .get(null); + + assertAll( + () -> assertThat(value, notNullValue()), + () -> assertThat(value.text(), notNullValue()), + () -> assertThat(value.length(), not(0)) + ); + + } + } +} \ No newline at end of file diff --git a/common/http/src/test/java/io/helidon/common/http/HttpPrologueTest.java b/common/http/src/test/java/io/helidon/common/http/HttpPrologueTest.java new file mode 100644 index 00000000000..b57a2a9c64f --- /dev/null +++ b/common/http/src/test/java/io/helidon/common/http/HttpPrologueTest.java @@ -0,0 +1,60 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class HttpPrologueTest { + @Test + void testPrologueWithAll() { + HttpPrologue prologue = HttpPrologue.create("HTTP", + "1.1", + Http.Method.GET, + "/admin;a=b/list;c=d;e=f?first=second#fragment", + true); + + assertThat(prologue.protocol(), is("HTTP")); + assertThat(prologue.protocolVersion(), is("1.1")); + assertThat(prologue.method(), is(Http.Method.GET)); + assertThat(prologue.uriPath().rawPathNoParams(), is("/admin/list")); + assertThat(prologue.query().rawValue(), is("first=second")); + assertThat(prologue.fragment().hasValue(), is(true)); + assertThat(prologue.fragment().value(), is("fragment")); + } + + @Test + void testPrologueEncodedPath() { + String path = "/one/two?a=b%26c=d&e=f&e=g&h=x%63%23e%3c#a%20frag%23ment"; + + HttpPrologue prologue = HttpPrologue.create("HTTP", + "1.1", + Http.Method.GET, + path, + true); + + assertThat(prologue.protocol(), is("HTTP")); + assertThat(prologue.protocolVersion(), is("1.1")); + assertThat(prologue.method(), is(Http.Method.GET)); + assertThat(prologue.uriPath().rawPathNoParams(), is("/one/two")); + assertThat(prologue.query().rawValue(), is("a=b%26c=d&e=f&e=g&h=x%63%23e%3c")); + assertThat(prologue.fragment().hasValue(), is(true)); + assertThat(prologue.fragment().value(), is("a frag#ment")); + } +} \ No newline at end of file diff --git a/common/http/src/test/java/io/helidon/common/http/HttpStatusTest.java b/common/http/src/test/java/io/helidon/common/http/HttpStatusTest.java new file mode 100644 index 00000000000..bd35cd72ed7 --- /dev/null +++ b/common/http/src/test/java/io/helidon/common/http/HttpStatusTest.java @@ -0,0 +1,83 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.CoreMatchers.not; +import static org.hamcrest.CoreMatchers.notNullValue; +import static org.hamcrest.CoreMatchers.sameInstance; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +class HttpStatusTest { + private static final Class clazz = Http.Status.class; + private static final Set constants = Stream.of(clazz.getDeclaredFields()) + .filter(it -> Modifier.isStatic(it.getModifiers())) + .filter(it -> Modifier.isFinal(it.getModifiers())) + .filter(it -> Modifier.isPublic(it.getModifiers())) + .map(Field::getName) + .collect(Collectors.toSet()); + + Http.Status custom999_1 = Http.Status.create(999); + Http.Status custom999_2 = Http.Status.create(999); + + @Test + void testSameInstanceForKnownStatus() { + Http.Status ok = Http.Status.create(200); + Http.Status okFromBoth = Http.Status.create(200, Http.Status.OK_200.reasonPhrase()); + Http.Status custom = Http.Status.create(200, "Very-Fine"); + + assertThat("Status from code must be the enum instance", ok, sameInstance(Http.Status.OK_200)); + assertThat("Status from code an reason phrase that matches must be the enum instance", + okFromBoth, + sameInstance(Http.Status.OK_200)); + assertThat("Status from code with custom phrase must differ", custom, not(sameInstance(Http.Status.OK_200))); + assertThat("Custom reason phrase should be present", custom.reasonPhrase(), is("Very-Fine")); + } + + @Test + void testEqualsAndHashCodeForCustomStatus() { + assertThat(custom999_1, is(custom999_2)); + assertThat(custom999_1.hashCode(), is(custom999_2.hashCode())); + } + + @Test + void testAllConstantsAreValid() throws NoSuchFieldException, IllegalAccessException { + // this is to test correct initialization (there may be an issue when the constants + // are defined on the interface and implemented by enum outside of it) + for (String constant : constants) { + Http.Status value = (Http.Status) clazz.getField(constant) + .get(null); + + assertAll( + () -> assertThat(value, notNullValue()), + () -> assertThat(value.reasonPhrase(), notNullValue()), + () -> assertThat(value.codeText(), notNullValue()), + () -> assertThat(value.code(), not(0)) + ); + + } + } +} \ No newline at end of file diff --git a/common/http/src/test/java/io/helidon/common/http/IntSetTest.java b/common/http/src/test/java/io/helidon/common/http/IntSetTest.java new file mode 100644 index 00000000000..41c4b0ff432 --- /dev/null +++ b/common/http/src/test/java/io/helidon/common/http/IntSetTest.java @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.common.http; + +import java.util.HashSet; +import java.util.Random; + +import org.junit.jupiter.api.RepeatedTest; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; + +class IntSetTest { + @RepeatedTest(20) + void test() { + IntSet set = new IntSet(14); + HashSet expected = new HashSet<>(); + Random random = new Random(); + + for (int i = 0; i < 14; i++) { + if (random.nextBoolean()) { + expected.add(i); + set.add(i); + } + } + + HashSet actual = new HashSet<>(); + for (int i = set.nextSetBit(0); i >= 0; i = set.nextSetBit(i + 1)) { + actual.add(i); + } + + assertThat(actual, is(expected)); + } +} \ No newline at end of file From caec6f6c466416e841de8f38c356e65d1c171243 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 22:37:22 +0200 Subject: [PATCH 12/54] Switch reactive media types to new MediaType abstraction --- .../media/common/ByteChannelBodyWriter.java | 6 +- .../common/CharSequenceBodyStreamWriter.java | 6 +- .../media/common/CharSequenceBodyWriter.java | 6 +- .../helidon/media/common/ContentReaders.java | 75 +--- .../media/common/ContentTypeCharset.java | 71 ---- .../media/common/DefaultMediaSupport.java | 16 +- .../helidon/media/common/FileBodyWriter.java | 6 +- .../media/common/FormParamsBodyReader.java | 37 +- .../media/common/FormParamsBodyWriter.java | 50 +-- .../common/MessageBodyReadableContent.java | 41 +-- .../common/MessageBodyReaderContext.java | 98 +---- .../common/MessageBodyWriterContext.java | 110 +++--- .../helidon/media/common/PathBodyWriter.java | 6 +- .../common/ReadableByteChannelPublisher.java | 229 ------------ .../media/common/ThrowableBodyWriter.java | 6 +- .../io/helidon/media/common/package-info.java | 4 +- .../media/common/ContentCharsetTest.java | 12 +- .../media/common/ContentReadersTest.java | 56 +-- .../ReadableByteChannelPublisherTest.java | 346 ------------------ .../jackson/JacksonBodyStreamWriter.java | 6 +- .../media/jackson/JacksonBodyWriter.java | 6 +- .../jackson/JacksonEsBodyStreamWriter.java | 19 +- .../jackson/JacksonNdBodyStreamWriter.java | 18 +- .../helidon/media/jackson/JacksonSupport.java | 15 +- .../media/jsonb/JsonbBodyStreamWriter.java | 4 +- .../helidon/media/jsonb/JsonbBodyWriter.java | 4 +- .../media/jsonb/JsonbEsBodyStreamWriter.java | 4 +- .../media/jsonb/JsonbNdBodyStreamWriter.java | 4 +- .../media/jsonb/TestJsonBindingSupport.java | 4 +- .../media/jsonp/JsonpBodyStreamWriter.java | 4 +- .../helidon/media/jsonp/JsonpBodyWriter.java | 4 +- .../media/jsonp/JsonpEsBodyStreamWriter.java | 4 +- .../media/jsonp/JsonpNdBodyStreamWriter.java | 4 +- .../helidon/media/jsonp/JsonSupportTest.java | 4 +- .../multipart/BodyPartBodyStreamReader.java | 4 +- .../multipart/BodyPartBodyStreamWriter.java | 4 +- .../media/multipart/BodyPartHeaders.java | 4 +- .../media/multipart/MultiPartBodyWriter.java | 4 +- .../multipart/ReadableBodyPartHeaders.java | 4 +- .../multipart/WriteableBodyPartHeaders.java | 4 +- .../media/multipart/BodyPartHeadersTest.java | 4 +- .../media/multipart/MultiPartEncoderTest.java | 4 +- 42 files changed, 272 insertions(+), 1045 deletions(-) delete mode 100644 media/common/src/main/java/io/helidon/media/common/ContentTypeCharset.java delete mode 100644 media/common/src/main/java/io/helidon/media/common/ReadableByteChannelPublisher.java delete mode 100644 media/common/src/test/java/io/helidon/media/common/ReadableByteChannelPublisherTest.java diff --git a/media/common/src/main/java/io/helidon/media/common/ByteChannelBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/ByteChannelBodyWriter.java index e94311eb1db..8c781276f7e 100644 --- a/media/common/src/main/java/io/helidon/media/common/ByteChannelBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/ByteChannelBodyWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; import io.helidon.common.mapper.Mapper; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.IoMulti; import io.helidon.common.reactive.RetrySchema; import io.helidon.common.reactive.Single; @@ -59,7 +59,7 @@ public Publisher write(Single content, GenericType type, MessageBodyWriterContext context) { - context.contentType(MediaType.APPLICATION_OCTET_STREAM); + context.contentType(MediaTypes.APPLICATION_OCTET_STREAM); return content.flatMap(mapper); } diff --git a/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyStreamWriter.java b/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyStreamWriter.java index c493dfe141e..9aa4e8cd709 100644 --- a/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyStreamWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyStreamWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Multi; final class CharSequenceBodyStreamWriter implements MessageBodyStreamWriter { @@ -38,7 +38,7 @@ static CharSequenceBodyStreamWriter create() { public Flow.Publisher write(final Flow.Publisher publisher, final GenericType type, final MessageBodyWriterContext context) { - context.contentType(MediaType.TEXT_PLAIN); + context.contentType(MediaTypes.TEXT_PLAIN); return Multi.create(publisher).map(s -> DataChunk.create(true, context.charset().encode(s.toString()))); } diff --git a/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyWriter.java index c37266a0b8c..e5c65f82f89 100644 --- a/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/CharSequenceBodyWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,8 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; import io.helidon.common.mapper.Mapper; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Single; /** @@ -47,7 +47,7 @@ public Publisher write(Single content, GenericType type, MessageBodyWriterContext context) { - context.contentType(MediaType.TEXT_PLAIN); + context.contentType(MediaTypes.TEXT_PLAIN); return content.flatMap(new CharSequenceToChunks(context.charset())); } diff --git a/media/common/src/main/java/io/helidon/media/common/ContentReaders.java b/media/common/src/main/java/io/helidon/media/common/ContentReaders.java index a5ab4a340c1..8632419c52a 100644 --- a/media/common/src/main/java/io/helidon/media/common/ContentReaders.java +++ b/media/common/src/main/java/io/helidon/media/common/ContentReaders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,16 +18,13 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; -import java.io.InputStream; +import java.io.OutputStream; import java.net.URLDecoder; import java.nio.ByteBuffer; import java.nio.charset.Charset; -import java.util.concurrent.CompletableFuture; import java.util.concurrent.Flow.Publisher; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.Reader; -import io.helidon.common.http.Utils; import io.helidon.common.mapper.Mapper; import io.helidon.common.reactive.Collector; import io.helidon.common.reactive.Multi; @@ -80,62 +77,6 @@ public static Single readURLEncodedString(Publisher chunks, return readString(chunks, charset).map(new StringToDecodedString(charset)); } - /** - * Get a reader that converts a {@link DataChunk} publisher to a - * {@link String}. - * - * @param charset the charset to use with the returned string content reader - * @return a string content reader - * @deprecated since 2.0.0, use {@link #readString(Publisher, Charset)}} - * or {@link DefaultMediaSupport#stringReader()} instead - */ - @Deprecated(since = "2.0.0") - public static Reader stringReader(Charset charset) { - return (chunks, type) -> readString(chunks, charset).toStage(); - } - - /** - * Gets a reader that converts a {@link DataChunk} publisher to a {@link String} processed - * through URL decoding. - * - * @param charset the charset to use with the returned string content reader - * @return the URL-decoded string content reader - * @deprecated since 2.0.0, use {@link #readURLEncodedString(Publisher, Charset)} instead - */ - @Deprecated(since = "2.0.0") - public static Reader urlEncodedStringReader(Charset charset) { - return (chunks, type) -> readURLEncodedString(chunks, charset).toStage(); - } - - /** - * Get a reader that converts a {@link DataChunk} publisher to an array of - * bytes. - * - * @return reader that transforms a publisher of byte buffers to a - * completion stage that might end exceptionally with - * @deprecated since 2.0.0, use {@link #readBytes(Publisher)} instead - */ - @Deprecated(since = "2.0.0") - public static Reader byteArrayReader() { - return (publisher, clazz) -> readBytes(publisher).toStage(); - } - - /** - * Get a reader that converts a {@link DataChunk} publisher to a blocking - * Java {@link InputStream}. The resulting - * {@link java.util.concurrent.CompletionStage} is already completed; - * however, the referenced {@link InputStream} in it may not already have - * all the data available; in such case, the read method (e.g., - * {@link InputStream#read()}) block. - * - * @return a input stream content reader - * @deprecated since 2.0.0, use {@link DefaultMediaSupport#inputStreamReader()} - */ - @Deprecated(since = "2.0.0") - public static Reader inputStreamReader() { - return (publisher, clazz) -> CompletableFuture.completedFuture(new DataChunkInputStream(publisher)); - } - /** * Implementation of {@link Mapper} that converts a {@code byte[]} into * a {@link String} using a given {@link Charset}. @@ -186,7 +127,7 @@ private static final class BytesCollector implements Collector { - try { - return Charset.forName(sch); - } catch (Exception e) { - return null; - } - }) - .orElse(defaultCharset); - } -} diff --git a/media/common/src/main/java/io/helidon/media/common/DefaultMediaSupport.java b/media/common/src/main/java/io/helidon/media/common/DefaultMediaSupport.java index 8d421dee14c..13f5639f704 100644 --- a/media/common/src/main/java/io/helidon/media/common/DefaultMediaSupport.java +++ b/media/common/src/main/java/io/helidon/media/common/DefaultMediaSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import java.util.List; import java.util.Objects; -import io.helidon.common.http.FormParams; +import io.helidon.common.parameters.Parameters; import io.helidon.common.reactive.RetrySchema; import io.helidon.config.Config; @@ -136,20 +136,20 @@ public static MessageBodyWriter fileWriter() { } /** - * Return {@link FormParams} writer instance. + * Return {@link Parameters} writer instance. * - * @return {@link FormParams} writer + * @return {@link Parameters} writer */ - public static MessageBodyWriter formParamWriter() { + public static MessageBodyWriter formParamWriter() { return FormParamsBodyWriter.create(); } /** - * Return {@link FormParams} reader instance. + * Return {@link Parameters} reader instance. * - * @return {@link FormParams} reader + * @return {@link Parameters} reader */ - public static MessageBodyReader formParamReader() { + public static MessageBodyReader formParamReader() { return FormParamsBodyReader.create(); } diff --git a/media/common/src/main/java/io/helidon/media/common/FileBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/FileBodyWriter.java index 60163345943..d856e3d1baf 100644 --- a/media/common/src/main/java/io/helidon/media/common/FileBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/FileBodyWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,8 +25,8 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; import io.helidon.common.mapper.Mapper; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Single; /** @@ -79,7 +79,7 @@ private static final class FileToChunks implements Mapper map(File file) { try { Path path = file.toPath(); - context.contentType(MediaType.APPLICATION_OCTET_STREAM); + context.contentType(MediaTypes.APPLICATION_OCTET_STREAM); context.contentLength(Files.size(path)); FileChannel fc = FileChannel.open(path, StandardOpenOption.READ); return ContentWriters.byteChannelWriter().apply(fc); diff --git a/media/common/src/main/java/io/helidon/media/common/FormParamsBodyReader.java b/media/common/src/main/java/io/helidon/media/common/FormParamsBodyReader.java index c55c86c8ef6..d77ee1dbc68 100644 --- a/media/common/src/main/java/io/helidon/media/common/FormParamsBodyReader.java +++ b/media/common/src/main/java/io/helidon/media/common/FormParamsBodyReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,20 +26,22 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.FormParams; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; +import io.helidon.common.parameters.Parameters; import io.helidon.common.reactive.Single; /** - * Message body reader for {@link FormParams}. + * Message body reader for {@link Parameters} (Form parameters). */ -class FormParamsBodyReader implements MessageBodyReader { +class FormParamsBodyReader implements MessageBodyReader { private static final FormParamsBodyReader DEFAULT = new FormParamsBodyReader(); private static final Map PATTERNS = Map.of( - MediaType.APPLICATION_FORM_URLENCODED, preparePattern("&"), - MediaType.TEXT_PLAIN, preparePattern("\n")); + MediaTypes.APPLICATION_FORM_URLENCODED, preparePattern("&"), + MediaTypes.TEXT_PLAIN, preparePattern("\n")); private FormParamsBodyReader() { } @@ -55,28 +57,29 @@ private static Pattern preparePattern(String assignmentSeparator) { @Override public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { return context.contentType() - .filter(mediaType -> mediaType == MediaType.APPLICATION_FORM_URLENCODED - || mediaType == MediaType.TEXT_PLAIN) - .map(it -> PredicateResult.supports(FormParams.class, type)) + .map(HttpMediaType::mediaType) + .filter(mediaType -> mediaType == MediaTypes.APPLICATION_FORM_URLENCODED + || mediaType == MediaTypes.TEXT_PLAIN) + .map(it -> PredicateResult.supports(Parameters.class, type)) .orElse(PredicateResult.NOT_SUPPORTED); } @Override @SuppressWarnings("unchecked") - public Single read(Flow.Publisher publisher, + public Single read(Flow.Publisher publisher, GenericType type, MessageBodyReaderContext context) { - MediaType mediaType = context.contentType().orElseThrow(); + HttpMediaType mediaType = context.contentType().orElseThrow(); Charset charset = mediaType.charset().map(Charset::forName).orElse(StandardCharsets.UTF_8); - Function decoder = decoder(mediaType, charset); + Function decoder = decoder(mediaType.mediaType(), charset); return (Single) ContentReaders.readString(publisher, charset) .map(formStr -> create(formStr, mediaType, decoder)); } - private FormParams create(String paramAssignments, MediaType mediaType, Function decoder) { - FormParams.Builder builder = FormParams.builder(); - Matcher m = PATTERNS.get(mediaType).matcher(paramAssignments); + private Parameters create(String paramAssignments, HttpMediaType mediaType, Function decoder) { + Parameters.Builder builder = Parameters.builder("form-params"); + Matcher m = PATTERNS.get(mediaType.mediaType()).matcher(paramAssignments); while (m.find()) { final String key = m.group(1); final String value = m.group(2); @@ -90,7 +93,7 @@ private FormParams create(String paramAssignments, MediaType mediaType, Function } private Function decoder(MediaType mediaType, Charset charset) { - if (mediaType == MediaType.TEXT_PLAIN) { + if (mediaType == MediaTypes.TEXT_PLAIN) { return (s) -> s; } else { return (s) -> URLDecoder.decode(s, charset); diff --git a/media/common/src/main/java/io/helidon/media/common/FormParamsBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/FormParamsBodyWriter.java index 96419032d62..b9f6777a180 100644 --- a/media/common/src/main/java/io/helidon/media/common/FormParamsBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/FormParamsBodyWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,30 +19,31 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.concurrent.Flow; import java.util.function.Function; import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.FormParams; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.mapper.Mapper; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; +import io.helidon.common.parameters.Parameters; import io.helidon.common.reactive.Single; /** - * Message body writer for {@link FormParams}. + * Message body writer for {@link Parameters} (Form parameters). */ -class FormParamsBodyWriter implements MessageBodyWriter { +class FormParamsBodyWriter implements MessageBodyWriter { private static final FormParamsBodyWriter DEFAULT = new FormParamsBodyWriter(); - private static final MediaType DEFAULT_FORM_MEDIA_TYPE = MediaType.APPLICATION_FORM_URLENCODED; + private static final HttpMediaType DEFAULT_FORM_MEDIA_TYPE = HttpMediaType.create(MediaTypes.APPLICATION_FORM_URLENCODED); private FormParamsBodyWriter() { } - static MessageBodyWriter create() { + static MessageBodyWriter create() { return DEFAULT; } @@ -51,26 +52,27 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont //User didn't have to set explicit content type. In that case set default and class filters out unsupported types. return context.contentType() .or(() -> Optional.of(DEFAULT_FORM_MEDIA_TYPE)) - .filter(mediaType -> mediaType == MediaType.APPLICATION_FORM_URLENCODED - || mediaType == MediaType.TEXT_PLAIN) - .map(it -> PredicateResult.supports(FormParams.class, type)) + .map(HttpMediaType::mediaType) + .filter(mediaType -> mediaType == MediaTypes.APPLICATION_FORM_URLENCODED + || mediaType == MediaTypes.TEXT_PLAIN) + .map(it -> PredicateResult.supports(Parameters.class, type)) .orElse(PredicateResult.NOT_SUPPORTED); } @Override - public Flow.Publisher write(Single single, - GenericType type, + public Flow.Publisher write(Single single, + GenericType type, MessageBodyWriterContext context) { - MediaType mediaType = context.contentType().orElseGet(() -> { + HttpMediaType mediaType = context.contentType().orElseGet(() -> { context.contentType(DEFAULT_FORM_MEDIA_TYPE); return DEFAULT_FORM_MEDIA_TYPE; }); Charset charset = mediaType.charset().map(Charset::forName).orElse(StandardCharsets.UTF_8); - return single.flatMap(new FormParamsToChunks(mediaType, charset)); + return single.flatMap(new FormParamsToChunks(mediaType.mediaType(), charset)); } - static final class FormParamsToChunks implements Mapper> { + static final class FormParamsToChunks implements Mapper> { private final MediaType mediaType; private final Charset charset; @@ -81,27 +83,27 @@ static final class FormParamsToChunks implements Mapper map(FormParams formParams) { + public Flow.Publisher map(Parameters formParams) { return ContentWriters.writeCharSequence(transform(formParams), charset); } - private String transform(FormParams formParams) { + private String transform(Parameters formParams) { char separator = separator(); Function encoder = encoder(); StringBuilder result = new StringBuilder(); - for (Map.Entry> entry : formParams.toMap().entrySet()) { - List values = entry.getValue(); + for (String name : formParams.names()) { + List values = formParams.all(name); if (values.size() == 0) { if (result.length() > 0) { result.append(separator); } - result.append(encoder.apply(entry.getKey())); + result.append(encoder.apply(name)); } else { for (String value : values) { if (result.length() > 0) { result.append(separator); } - result.append(encoder.apply(entry.getKey())); + result.append(encoder.apply(name)); result.append("="); result.append(encoder.apply(value)); } @@ -111,7 +113,7 @@ private String transform(FormParams formParams) { } private char separator() { - if (mediaType == MediaType.TEXT_PLAIN) { + if (mediaType == MediaTypes.TEXT_PLAIN) { return '\n'; } else { return '&'; @@ -119,7 +121,7 @@ private char separator() { } private Function encoder() { - if (mediaType == MediaType.TEXT_PLAIN) { + if (mediaType == MediaTypes.TEXT_PLAIN) { return (s) -> s; } else { return (s) -> URLEncoder.encode(s, charset); diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyReadableContent.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyReadableContent.java index bf735018b32..5c648937f89 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyReadableContent.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyReadableContent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,6 @@ import java.util.Objects; import java.util.concurrent.Flow.Publisher; import java.util.concurrent.Flow.Subscriber; -import java.util.function.Function; -import java.util.function.Predicate; import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; @@ -31,7 +29,7 @@ */ @SuppressWarnings("deprecation") public final class MessageBodyReadableContent - implements MessageBodyReaders, MessageBodyFilters, MessageBodyContent, io.helidon.common.http.Content { + implements MessageBodyReaders, MessageBodyFilters, MessageBodyContent { private final Publisher publisher; private final MessageBodyReaderContext context; @@ -85,24 +83,6 @@ public MessageBodyReadableContent registerReader(MessageBodyStreamReader read return this; } - @Deprecated - @Override - public void registerFilter(Function, Publisher> function) { - context.registerFilter(p -> function.apply(p)); - } - - @Deprecated - @Override - public void registerReader(Class type, io.helidon.common.http.Reader reader) { - context.registerReader(type, reader); - } - - @Deprecated - @Override - public void registerReader(Predicate> predicate, io.helidon.common.http.Reader reader) { - context.registerReader(predicate, reader); - } - @Override public void subscribe(Subscriber subscriber) { try { @@ -112,7 +92,22 @@ public void subscribe(Subscriber subscriber) { } } - @Override + /** + * Consumes and converts the request content into a completion stage of the requested type. + *

+ * The conversion requires an appropriate reader to be already registered + * (see {@link #registerReader(MessageBodyReader)}). If no such reader is found, the + * resulting completion stage ends exceptionally. + *

+ * Any callback related to the returned value, should not be blocking. Blocking operation could cause deadlock. + * If you need to use blocking API such as {@link java.io.InputStream} it is highly recommended to do so out of + * the scope of reactive chain, or to use methods like + * {@link java.util.concurrent.CompletionStage#thenAcceptAsync(java.util.function.Consumer, java.util.concurrent.Executor)}. + * + * @param the requested type + * @param type the requested type class + * @return a completion stage of the requested type + */ public Single as(final Class type) { return context.unmarshall(publisher, GenericType.create(type)); } diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyReaderContext.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyReaderContext.java index 9695deb6a48..09d479f4a22 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyReaderContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyReaderContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,12 +23,12 @@ import java.util.Optional; import java.util.concurrent.Flow; import java.util.concurrent.Flow.Publisher; -import java.util.function.Predicate; import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.ReadOnlyParameters; +import io.helidon.common.http.Headers; +import io.helidon.common.http.HeadersWritable; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; @@ -44,16 +44,18 @@ public final class MessageBodyReaderContext extends MessageBodyContext implement */ static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - private final ReadOnlyParameters headers; - private final Optional contentType; + private final Headers headers; + private final Optional contentType; private final MessageBodyOperators> readers; private final MessageBodyOperators> sreaders; /** * Private to enforce the use of the static factory methods. */ - private MessageBodyReaderContext(MessageBodyReaderContext parent, EventListener eventListener, ReadOnlyParameters headers, - Optional contentType) { + private MessageBodyReaderContext(MessageBodyReaderContext parent, + EventListener eventListener, + Headers headers, + Optional contentType) { super(parent, eventListener); Objects.requireNonNull(headers, "headers cannot be null!"); @@ -75,7 +77,7 @@ private MessageBodyReaderContext(MessageBodyReaderContext parent, EventListener */ private MessageBodyReaderContext() { super(null, null); - this.headers = ReadOnlyParameters.empty(); + this.headers = HeadersWritable.create(); this.contentType = Optional.empty(); this.readers = new MessageBodyOperators<>(); this.sreaders = new MessageBodyOperators<>(); @@ -112,30 +114,6 @@ public MessageBodyReaderContext registerReader(MessageBodyStreamReader reader return this; } - /** - * Register a reader function with the given type. - * @param supported type - * @param type class representing the supported type - * @param reader reader function - * @deprecated since 2.0.0 use {@link #registerReader(MessageBodyReader) } instead - */ - @Deprecated - public void registerReader(Class type, io.helidon.common.http.Reader reader) { - readers.registerFirst(new ReaderAdapter<>(type, reader)); - } - - /** - * Register a reader function with the type predicate. - * @param supported type - * @param predicate class predicate - * @param reader reader function - * @deprecated since 2.0.0 use {@link #registerReader(MessageBodyReader) } instead - */ - @Deprecated - public void registerReader(Predicate> predicate, io.helidon.common.http.Reader reader) { - readers.registerFirst(new ReaderAdapter<>(predicate, reader)); - } - /** * Convert a given HTTP payload into a publisher by selecting a reader that * accepts the specified type and current context. @@ -258,7 +236,7 @@ public Publisher unmarshallStream(Publisher payload, MessageBo * * @return Parameters, never {@code null} */ - public ReadOnlyParameters headers() { + public Headers headers() { return headers; } @@ -267,7 +245,7 @@ public ReadOnlyParameters headers() { * * @return Optional, never {@code null} */ - public Optional contentType() { + public Optional contentType() { return contentType; } @@ -304,7 +282,7 @@ public static MessageBodyReaderContext create(MessageBodyReaderContext parent) { * @return MessageBodyReaderContext */ public static MessageBodyReaderContext create(MediaContext mediaContext, EventListener eventListener, - ReadOnlyParameters headers, Optional contentType) { + Headers headers, Optional contentType) { if (mediaContext == null) { return new MessageBodyReaderContext(null, eventListener, headers, contentType); @@ -322,7 +300,7 @@ public static MessageBodyReaderContext create(MediaContext mediaContext, EventLi * @return MessageBodyReaderContext */ public static MessageBodyReaderContext create(MessageBodyReaderContext parent, EventListener eventListener, - ReadOnlyParameters headers, Optional contentType) { + Headers headers, Optional contentType) { return new MessageBodyReaderContext(parent, eventListener, headers, contentType); } @@ -349,50 +327,4 @@ private static Single readerNotFound(String type) { private static Single transformationFailed(Throwable ex) { return Single.error(new IllegalStateException("Transformation failed!", ex)); } - - /** - * Message body reader adapter for the old deprecated reader. - * @param reader type - */ - @SuppressWarnings("deprecation") - private static final class ReaderAdapter implements MessageBodyReader { - - private final io.helidon.common.http.Reader reader; - private final Predicate> predicate; - private final Class clazz; - - ReaderAdapter(Predicate> predicate, io.helidon.common.http.Reader reader) { - Objects.requireNonNull(predicate, "predicate cannot be null!"); - Objects.requireNonNull(reader, "reader cannot be null!"); - this.reader = reader; - this.predicate = predicate; - this.clazz = null; - } - - ReaderAdapter(Class clazz, io.helidon.common.http.Reader reader) { - Objects.requireNonNull(clazz, "clazz cannot be null!"); - Objects.requireNonNull(reader, "reader cannot be null!"); - this.reader = reader; - this.clazz = clazz; - this.predicate = null; - } - - @SuppressWarnings("unchecked") - @Override - public Single read(Publisher publisher, GenericType type, - MessageBodyReaderContext context) { - - return Single.create(reader.applyAndCast(publisher, (Class) type.rawType())); - } - - @Override - public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { - if (predicate != null) { - return predicate.test(type.rawType()) - ? PredicateResult.SUPPORTED - : PredicateResult.NOT_SUPPORTED; - } - return PredicateResult.supports(clazz, type); - } - } } diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java index 1acd4c6f2df..8156af11181 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,11 +28,12 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; +import io.helidon.common.http.HeadersWritable; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; -import io.helidon.common.http.ReadOnlyParameters; +import io.helidon.common.http.Http.HeaderValue; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.mapper.Mapper; +import io.helidon.common.media.type.MediaType; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; @@ -51,20 +52,22 @@ public final class MessageBodyWriterContext extends MessageBodyContext implement */ private static final Charset DEFAULT_CHARSET = StandardCharsets.UTF_8; - private final Parameters headers; - private final List acceptedTypes; + private final HeadersWritable headers; + private final List acceptedTypes; private final MessageBodyOperators> writers; private final MessageBodyOperators> swriters; private boolean contentTypeCached; - private Optional contentTypeCache; + private Optional contentTypeCache; private boolean charsetCached; private Charset charsetCache; /** * Private to enforce the use of the static factory methods. */ - private MessageBodyWriterContext(MessageBodyWriterContext parent, EventListener eventListener, Parameters headers, - List acceptedTypes) { + private MessageBodyWriterContext(MessageBodyWriterContext parent, + EventListener eventListener, + HeadersWritable headers, + List acceptedTypes) { super(parent, eventListener); Objects.requireNonNull(headers, "headers cannot be null!"); @@ -87,7 +90,7 @@ private MessageBodyWriterContext(MessageBodyWriterContext parent, EventListener * Create a new standalone (non parented) context. * @param headers backing headers, may not be {@code null} */ - private MessageBodyWriterContext(Parameters headers) { + private MessageBodyWriterContext(HeadersWritable headers) { super(null, null); Objects.requireNonNull(headers, "headers cannot be null!"); this.headers = headers; @@ -101,7 +104,7 @@ private MessageBodyWriterContext(Parameters headers) { */ private MessageBodyWriterContext() { super(null, null); - this.headers = ReadOnlyParameters.empty(); + this.headers = HeadersWritable.create(); this.writers = new MessageBodyOperators<>(); this.swriters = new MessageBodyOperators<>(); this.acceptedTypes = List.of(); @@ -111,7 +114,7 @@ private MessageBodyWriterContext() { this.charsetCached = true; } - private MessageBodyWriterContext(MessageBodyWriterContext writerContext, Parameters headers) { + private MessageBodyWriterContext(MessageBodyWriterContext writerContext, HeadersWritable headers) { super(writerContext); Objects.requireNonNull(headers, "headers cannot be null!"); this.headers = headers; @@ -135,8 +138,10 @@ private MessageBodyWriterContext(MessageBodyWriterContext writerContext, Paramet * @param acceptedTypes accepted types, may be {@code null} * @return MessageBodyWriterContext */ - public static MessageBodyWriterContext create(MediaContext mediaContext, EventListener eventListener, Parameters headers, - List acceptedTypes) { + public static MessageBodyWriterContext create(MediaContext mediaContext, + EventListener eventListener, + HeadersWritable headers, + List acceptedTypes) { if (mediaContext == null) { return new MessageBodyWriterContext(null, eventListener, headers, acceptedTypes); @@ -155,7 +160,7 @@ public static MessageBodyWriterContext create(MediaContext mediaContext, EventLi * @return MessageBodyWriterContext */ public static MessageBodyWriterContext create(MessageBodyWriterContext parent, EventListener eventListener, - Parameters headers, List acceptedTypes) { + HeadersWritable headers, List acceptedTypes) { return new MessageBodyWriterContext(parent, eventListener, headers, acceptedTypes); } @@ -165,7 +170,7 @@ public static MessageBodyWriterContext create(MessageBodyWriterContext parent, E * @param headers headers * @return MessageBodyWriterContext */ - public static MessageBodyWriterContext create(Parameters headers) { + public static MessageBodyWriterContext create(HeadersWritable headers) { return new MessageBodyWriterContext(headers); } @@ -186,7 +191,7 @@ public static MessageBodyWriterContext create(MessageBodyWriterContext parent) { * @param headers headers * @return MessageBodyWriterContext */ - public static MessageBodyWriterContext create(MessageBodyWriterContext parent, Parameters headers) { + public static MessageBodyWriterContext create(MessageBodyWriterContext parent, HeadersWritable headers) { return new MessageBodyWriterContext(parent, headers); } @@ -197,7 +202,7 @@ public static MessageBodyWriterContext create(MessageBodyWriterContext parent, P * @return MessageBodyWriterContext */ public static MessageBodyWriterContext create() { - return new MessageBodyWriterContext(ReadOnlyParameters.empty()); + return new MessageBodyWriterContext(HeadersWritable.create()); } @Override @@ -237,7 +242,7 @@ public MessageBodyWriterContext registerWriter(Class type, Function MessageBodyWriterContext registerWriter(Class type, MediaType contentType, + public MessageBodyWriterContext registerWriter(Class type, HttpMediaType contentType, Function> function) { writers.registerFirst(new WriterAdapter<>(function, type, contentType)); @@ -270,7 +275,7 @@ public MessageBodyWriterContext registerWriter(Predicate accept, Function * @deprecated since 2.0.0, use {@link #registerWriter(MessageBodyWriter) } instead */ @Deprecated - public MessageBodyWriterContext registerWriter(Predicate accept, MediaType contentType, + public MessageBodyWriterContext registerWriter(Predicate accept, HttpMediaType contentType, Function> function) { writers.registerFirst(new WriterAdapter<>(function, accept, contentType)); @@ -390,7 +395,7 @@ public Publisher marshallStream(Publisher content, MessageBody * * @return Parameters, never {@code null} */ - public Parameters headers() { + public HeadersWritable headers() { return headers; } @@ -399,14 +404,12 @@ public Parameters headers() { * * @return Optional, never {@code null} */ - public Optional contentType() { + public Optional contentType() { if (contentTypeCached) { return contentTypeCache; } - contentTypeCache = Optional.ofNullable(headers - .first(Http.Header.CONTENT_TYPE) - .map(MediaType::parse) - .orElse(null)); + contentTypeCache = headers.contentType(); + contentTypeCached = true; return contentTypeCache; } @@ -416,8 +419,8 @@ public Optional contentType() { * * @return List never {@code null} */ - public List acceptedTypes() { - return acceptedTypes; + public List acceptedTypes() { + return headers.acceptedTypes(); } /** @@ -427,10 +430,21 @@ public List acceptedTypes() { * @param contentType {@code Content-Type} value to set, must not be * {@code null} */ - public void contentType(MediaType contentType) { - if (contentType != null) { - headers.putIfAbsent(Http.Header.CONTENT_TYPE, contentType.toString()); - } + public void contentType(HttpMediaType contentType) { + Objects.requireNonNull(contentType); + headers.setIfAbsent(HeaderValue.create(Http.Header.CONTENT_TYPE, false, false, contentType.text())); + } + + /** + * Set the {@code Content-Type} header value in the underlying headers if + * not present. + * + * @param mediaType {@code Content-Type} value to set, must not be + * {@code null} + */ + public void contentType(MediaType mediaType) { + Objects.requireNonNull(mediaType); + headers.setIfAbsent(HeaderValue.create(Http.Header.CONTENT_TYPE, false, false, mediaType.fullType())); } /** @@ -442,7 +456,8 @@ public void contentType(MediaType contentType) { */ public void contentLength(long contentLength) { if (contentLength >= 0) { - headers.putIfAbsent(Http.Header.CONTENT_LENGTH, String.valueOf(contentLength)); + headers.setIfAbsent(HeaderValue.create(Http.Header.CONTENT_LENGTH, true, false, + String.valueOf(contentLength))); } } @@ -464,20 +479,23 @@ public void contentLength(long contentLength) { * @return MediaType, never {@code null} * @throws IllegalStateException if no media type can be returned */ - public MediaType findAccepted(Predicate predicate, MediaType defaultType) throws IllegalStateException { + public HttpMediaType findAccepted(Predicate predicate, HttpMediaType defaultType) + throws IllegalStateException { Objects.requireNonNull(predicate, "predicate cannot be null"); Objects.requireNonNull(defaultType, "defaultType cannot be null"); - MediaType contentType = contentType().orElse(null); + + HttpMediaType contentType = contentType().orElse(null); if (contentType == null) { if (acceptedTypes.isEmpty()) { return defaultType; } else { - for (final MediaType acceptedType : acceptedTypes) { + for (HttpMediaType acceptedType : acceptedTypes) { if (predicate.test(acceptedType)) { - if (acceptedType.isWildcardType() || acceptedType.isWildcardSubtype()) { + MediaType mt = acceptedType.mediaType(); + if (mt.isWildcardType() || mt.isWildcardSubtype()) { return defaultType; } - return MediaType.create(acceptedType.type(), acceptedType.subtype()); + return acceptedType; } } } @@ -496,10 +514,10 @@ public MediaType findAccepted(Predicate predicate, MediaType defaultT * @return MediaType, never {@code null} * @throws IllegalStateException if the media type is not found */ - public MediaType findAccepted(MediaType mediaType) throws IllegalStateException { + public HttpMediaType findAccepted(HttpMediaType mediaType) throws IllegalStateException { Objects.requireNonNull(mediaType, "mediaType cannot be null"); - for (MediaType acceptedType : acceptedTypes) { - if (mediaType.equals(acceptedType)) { + for (HttpMediaType acceptedType : acceptedTypes) { + if (mediaType.test(acceptedType)) { return acceptedType; } } @@ -511,7 +529,7 @@ public Charset charset() throws IllegalStateException { if (charsetCached) { return charsetCache; } - MediaType contentType = contentType().orElse(null); + HttpMediaType contentType = contentType().orElse(null); if (contentType != null) { try { charsetCache = contentType.charset().map(Charset::forName).orElse(DEFAULT_CHARSET); @@ -536,10 +554,10 @@ private static final class WriterAdapter implements MessageBodyWriter { private final Function> function; private final Predicate predicate; private final Class type; - private final MediaType contentType; + private final HttpMediaType contentType; @SuppressWarnings("unchecked") - WriterAdapter(Function> function, Predicate predicate, MediaType contentType) { + WriterAdapter(Function> function, Predicate predicate, HttpMediaType contentType) { Objects.requireNonNull(function, "function cannot be null!"); Objects.requireNonNull(predicate, "predicate cannot be null!"); this.function = function; @@ -549,7 +567,7 @@ private static final class WriterAdapter implements MessageBodyWriter { } @SuppressWarnings("unchecked") - WriterAdapter(Function> function, Class type, MediaType contentType) { + WriterAdapter(Function> function, Class type, HttpMediaType contentType) { Objects.requireNonNull(function, "function cannot be null!"); Objects.requireNonNull(type, "type cannot be null!"); this.function = (Function>) function; @@ -570,7 +588,7 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont return PredicateResult.NOT_SUPPORTED; } } - MediaType ct = context.contentType().orElse(null); + HttpMediaType ct = context.contentType().orElse(null); if (!(contentType != null && ct != null && !ct.test(contentType))) { context.contentType(contentType); return PredicateResult.SUPPORTED; diff --git a/media/common/src/main/java/io/helidon/media/common/PathBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/PathBodyWriter.java index da11f9dd1ac..02e984e757f 100644 --- a/media/common/src/main/java/io/helidon/media/common/PathBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/PathBodyWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,8 +24,8 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; import io.helidon.common.mapper.Mapper; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Single; /** @@ -76,7 +76,7 @@ private static final class PathToChunks implements Mapper map(Path path) { try { - context.contentType(MediaType.APPLICATION_OCTET_STREAM); + context.contentType(MediaTypes.APPLICATION_OCTET_STREAM); context.contentLength(Files.size(path)); FileChannel fc = FileChannel.open(path, StandardOpenOption.READ); return ContentWriters.byteChannelWriter().apply(fc); diff --git a/media/common/src/main/java/io/helidon/media/common/ReadableByteChannelPublisher.java b/media/common/src/main/java/io/helidon/media/common/ReadableByteChannelPublisher.java deleted file mode 100644 index bdb5a614ac9..00000000000 --- a/media/common/src/main/java/io/helidon/media/common/ReadableByteChannelPublisher.java +++ /dev/null @@ -1,229 +0,0 @@ -/* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.media.common; - -import java.nio.ByteBuffer; -import java.nio.channels.ReadableByteChannel; -import java.util.concurrent.Executors; -import java.util.concurrent.Flow; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.logging.Level; -import java.util.logging.Logger; - -import io.helidon.common.LazyValue; -import io.helidon.common.http.DataChunk; -import io.helidon.common.reactive.RequestedCounter; -import io.helidon.common.reactive.RetrySchema; -import io.helidon.common.reactive.SingleSubscriberHolder; - -/** - * Publish a channel content to a single {@link Flow.Subscriber subscriber}. If channel doesn't offer data, then it is requested - * again after some period defined be retry schema. - *

- * Only first subscriber is accepted. - * - * @deprecated Will be removed. Please use - * {@link io.helidon.common.reactive.IoMulti#multiFromByteChannel(java.nio.channels.ReadableByteChannel)} instead - */ -@Deprecated(since = "2.0.0", forRemoval = true) -public class ReadableByteChannelPublisher implements Flow.Publisher { - - private static final Logger LOGGER = Logger.getLogger(ReadableByteChannelPublisher.class.getName()); - - private static final int DEFAULT_CHUNK_CAPACITY = 1024 * 8; - - private final ReadableByteChannel channel; - private final RetrySchema retrySchema; - private final boolean externalExecutor; - private final int chunkCapacity; - private final LazyValue executor; - - private final SingleSubscriberHolder subscriber = new SingleSubscriberHolder<>(); - private final RequestedCounter requested = new RequestedCounter(); - private final AtomicBoolean publishing = new AtomicBoolean(false); - private final AtomicInteger retryCounter = new AtomicInteger(); - - private volatile long lastRetryDelay = 0; - private volatile DataChunk currentChunk; - - /** - * Creates new instance. - * - * @param channel a channel to read and publish - * @param retrySchema a retry schema functional interface used in case, that channel read retrieved zero bytes. - * @deprecated please use - * {@link io.helidon.common.reactive.IoMulti#multiFromByteChannel(java.nio.channels.ReadableByteChannel)} - */ - @Deprecated(since = "2.0.0", forRemoval = true) - public ReadableByteChannelPublisher(ReadableByteChannel channel, RetrySchema retrySchema) { - this.channel = channel; - this.retrySchema = retrySchema; - this.executor = LazyValue.create(() -> Executors.newScheduledThreadPool(1)); - this.externalExecutor = false; - this.chunkCapacity = DEFAULT_CHUNK_CAPACITY; - } - - @Override - public void subscribe(Flow.Subscriber subscriberParam) { - if (subscriber.register(subscriberParam)) { - publishing.set(true); // prevent onNext from inside of onSubscribe - - try { - subscriberParam.onSubscribe(new Flow.Subscription() { - @Override - public void request(long n) { - requested.increment(n, t -> tryComplete(t)); - tryPublish(); - } - - @Override - public void cancel() { - subscriber.cancel(); - closeExecutor(); - } - }); - } finally { - publishing.set(false); - } - - tryPublish(); // give onNext a chance in case request has been invoked in onSubscribe - } - } - - // for tests - LazyValue executor() { - return executor; - } - - private DataChunk allocateNewChunk() { - return DataChunk.create(false, ByteBuffer.allocate(chunkCapacity)); - } - - /** - * It publish a single item or complete or both. If next item is not yet available but it can be in the future then returns - * {@code false} and call will be rescheduled based on {@link RetrySchema}. - * - * @param subscr a subscriber to publish on - * @return {@code true} if next item was published or subscriber was completed otherwise {@code false} - * @throws Exception if any error happens and {@code onError()} must be called on the subscriber - */ - private boolean publishSingleOrFinish(Flow.Subscriber subscr) throws Exception { - DataChunk chunk; - if (currentChunk == null) { - chunk = allocateNewChunk(); - } else { - chunk = currentChunk; - currentChunk = null; - } - - ByteBuffer bb = chunk.data()[0]; - int count = 0; - while (bb.remaining() > 0) { - count = channel.read(bb); - if (count <= 0) { - break; - } - } - // Send or store - if (bb.capacity() > bb.remaining()) { - bb.flip(); - subscr.onNext(chunk); - } else { - currentChunk = chunk; - } - // Last or not - if (count < 0) { - try { - channel.close(); - } catch (Exception e) { - LOGGER.log(Level.WARNING, "Cannot close readable byte channel! (Close attempt after fully read channel.)", e); - } - tryComplete(); - if (currentChunk != null) { - currentChunk.release(); - } - return true; - } else { - return count > 0; - } - } - - private void tryPublish() { - boolean immediateRetry = true; - while (immediateRetry) { - immediateRetry = false; - - // Publish, if can - if (!subscriber.isClosed() && requested.get() > 0 && publishing.compareAndSet(false, true)) { - try { - Flow.Subscriber sub = this.subscriber.get(); // blocking retrieval - while (!subscriber.isClosed() && requested.tryDecrement()) { - if (!publishSingleOrFinish(sub)) { - // Not yet published but can be done in the future - requested.increment(1, this::tryComplete); - break; - } - } - } catch (InterruptedException e) { - Thread.currentThread().interrupt(); - tryComplete(e); - } catch (Exception e) { - tryComplete(e); - } finally { - publishing.set(false); // give a chance to some other thread to publish - } - - // Execute in different thread if needed - if (!subscriber.isClosed() && requested.get() > 0) { - long nextDelay = retrySchema.nextDelay(retryCounter.getAndIncrement(), lastRetryDelay); - lastRetryDelay = nextDelay; - if (nextDelay < 0) { - tryComplete(new TimeoutException("Wait for the next item timeout!")); - } else if (nextDelay == 0) { - immediateRetry = true; - } else { - planNextTry(nextDelay); - } - } - } - } - } - - private synchronized void planNextTry(long afterMillis) { - executor.get().schedule(this::tryPublish, afterMillis, TimeUnit.MILLISECONDS); - } - - private void tryComplete() { - subscriber.close(Flow.Subscriber::onComplete); - closeExecutor(); - } - - private void tryComplete(Throwable t) { - subscriber.close(sub -> sub.onError(t)); - closeExecutor(); - } - - private synchronized void closeExecutor() { - if (!externalExecutor && executor.isLoaded()) { - executor.get().shutdownNow(); - } - } -} diff --git a/media/common/src/main/java/io/helidon/media/common/ThrowableBodyWriter.java b/media/common/src/main/java/io/helidon/media/common/ThrowableBodyWriter.java index 7d2690c1f5f..c201ce7a7a2 100644 --- a/media/common/src/main/java/io/helidon/media/common/ThrowableBodyWriter.java +++ b/media/common/src/main/java/io/helidon/media/common/ThrowableBodyWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,8 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; import io.helidon.common.mapper.Mapper; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Single; /** @@ -52,7 +52,7 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont public Publisher write(Single content, GenericType type, MessageBodyWriterContext context) { - context.contentType(MediaType.TEXT_PLAIN); + context.contentType(MediaTypes.TEXT_PLAIN); if (includeStackTraces) { return content.flatMap(new ThrowableToChunks(context.charset())); } else { diff --git a/media/common/src/main/java/io/helidon/media/common/package-info.java b/media/common/src/main/java/io/helidon/media/common/package-info.java index 4f3a783ea85..a5e31612bde 100644 --- a/media/common/src/main/java/io/helidon/media/common/package-info.java +++ b/media/common/src/main/java/io/helidon/media/common/package-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,6 +14,6 @@ * limitations under the License. */ /** - * Common classes for processing content with a specific {@link io.helidon.common.http.MediaType}. + * Common classes for processing content with a specific {@link io.helidon.common.http.HttpMediaType}. */ package io.helidon.media.common; diff --git a/media/common/src/test/java/io/helidon/media/common/ContentCharsetTest.java b/media/common/src/test/java/io/helidon/media/common/ContentCharsetTest.java index a8c0a605d16..a2966922f04 100644 --- a/media/common/src/test/java/io/helidon/media/common/ContentCharsetTest.java +++ b/media/common/src/test/java/io/helidon/media/common/ContentCharsetTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,8 +20,8 @@ import java.nio.charset.UnsupportedCharsetException; import java.util.Optional; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.ReadOnlyParameters; +import io.helidon.common.http.HeadersWritable; +import io.helidon.common.http.HttpMediaType; import org.junit.jupiter.api.Test; @@ -72,13 +72,13 @@ public void missingContentType() { * @return MessageBodyReaderContext */ private MessageBodyReaderContext readerContext(String contentTypeValue) { - Optional contentType; + Optional contentType; if (contentTypeValue == null) { contentType = Optional.empty(); } else { - contentType = Optional.of(MediaType.parse(contentTypeValue)); + contentType = Optional.of(HttpMediaType.create(contentTypeValue)); } return MessageBodyReaderContext.create((MediaContext) null, null, - ReadOnlyParameters.empty(), contentType); + HeadersWritable.create(), contentType); } } diff --git a/media/common/src/test/java/io/helidon/media/common/ContentReadersTest.java b/media/common/src/test/java/io/helidon/media/common/ContentReadersTest.java index d08c45281bd..1fe27077420 100644 --- a/media/common/src/test/java/io/helidon/media/common/ContentReadersTest.java +++ b/media/common/src/test/java/io/helidon/media/common/ContentReadersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,15 +16,13 @@ package io.helidon.media.common; -import java.io.InputStream; import java.net.URLEncoder; -import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.TimeUnit; +import java.time.Duration; import io.helidon.common.http.DataChunk; import io.helidon.common.reactive.Multi; +import io.helidon.common.reactive.Single; import org.junit.jupiter.api.Test; @@ -34,60 +32,28 @@ /** * Unit test for {@link ContentReaders}. */ -@SuppressWarnings("deprecation") class ContentReadersTest { @Test - void testStringReader() throws Exception { - Multi chunks = Multi.singleton(DataChunk.create(new byte[] {(byte) 225, (byte) 226, (byte) 227})); - - CompletableFuture future = - ContentReaders.stringReader(Charset.forName("cp1250")) - .apply(chunks) - .toCompletableFuture(); - - String s = future.get(10, TimeUnit.SECONDS); - assertThat(s, is("áâă")); - } - - @Test - void testByteArrayReader() throws Exception { - String original = "Popokatepetl"; - byte[] bytes = original.getBytes(StandardCharsets.UTF_8); - - CompletableFuture future = ContentReaders.byteArrayReader() - .apply(Multi.singleton(DataChunk.create(bytes))) - .toCompletableFuture(); - - byte[] actualBytes = future.get(10, TimeUnit.SECONDS); - assertThat(actualBytes, is(bytes)); - } - - @Test - void test() throws Exception { + void testByteArrayReader() { String original = "Popokatepetl"; byte[] bytes = original.getBytes(StandardCharsets.UTF_8); - CompletableFuture future = ContentReaders.inputStreamReader() - .apply(Multi.singleton(DataChunk.create(bytes))) - .toCompletableFuture(); + Single future = ContentReaders.readBytes(Multi.singleton(DataChunk.create(bytes))); - InputStream inputStream = future.get(10, TimeUnit.SECONDS); - byte[] actualBytes = inputStream.readAllBytes(); + byte[] actualBytes = future.await(Duration.ofSeconds(10)); assertThat(actualBytes, is(bytes)); } @Test - void testURLDecodingReader() throws Exception { + void testURLDecodingReader() { String original = "myParam=\"Now@is'the/time"; - String encoded = URLEncoder.encode(original, "UTF-8"); + String encoded = URLEncoder.encode(original, StandardCharsets.UTF_8); Multi chunks = Multi.singleton(DataChunk.create(encoded.getBytes(StandardCharsets.UTF_8))); - CompletableFuture future = - ContentReaders.urlEncodedStringReader(StandardCharsets.UTF_8) - .apply(chunks) - .toCompletableFuture(); + Single future = + ContentReaders.readURLEncodedString(chunks, StandardCharsets.UTF_8); - String s = future.get(10, TimeUnit.SECONDS); + String s = future.await(Duration.ofSeconds(10)); assertThat(s, is(original)); } } diff --git a/media/common/src/test/java/io/helidon/media/common/ReadableByteChannelPublisherTest.java b/media/common/src/test/java/io/helidon/media/common/ReadableByteChannelPublisherTest.java deleted file mode 100644 index 19e160a5925..00000000000 --- a/media/common/src/test/java/io/helidon/media/common/ReadableByteChannelPublisherTest.java +++ /dev/null @@ -1,346 +0,0 @@ -/* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.media.common; - -import java.io.IOException; -import java.nio.ByteBuffer; -import java.nio.channels.ClosedChannelException; -import java.nio.channels.ReadableByteChannel; -import java.nio.charset.StandardCharsets; -import java.util.HashSet; -import java.util.Set; -import java.util.concurrent.CompletionException; -import java.util.concurrent.CountDownLatch; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.Flow.Publisher; -import java.util.concurrent.Flow.Subscriber; -import java.util.concurrent.Flow.Subscription; -import java.util.concurrent.ScheduledExecutorService; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; -import java.util.concurrent.atomic.AtomicBoolean; -import java.util.concurrent.atomic.AtomicReference; -import java.util.function.IntFunction; - -import io.helidon.common.LazyValue; -import io.helidon.common.http.DataChunk; -import io.helidon.common.reactive.RetrySchema; -import io.helidon.common.reactive.Single; - -import org.junit.jupiter.api.Disabled; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.CoreMatchers.nullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; -import static org.junit.jupiter.api.Assertions.fail; - -/** - * Tests {@link io.helidon.media.common.ReadableByteChannelPublisher}. - */ -public class ReadableByteChannelPublisherTest { - - private static final int TEST_DATA_SIZE = 250 * 1024; - - @Test - void allData() throws Exception { - PeriodicalChannel pc = new PeriodicalChannel(i -> 256, TEST_DATA_SIZE); - CountingOnNextDelegatingPublisher publisher = new CountingOnNextDelegatingPublisher( - new ReadableByteChannelPublisher(pc, RetrySchema.constant(5))); - // assert - byte[] bytes = ContentReaders.readBytes(publisher).get(5, TimeUnit.SECONDS); - assertThat(bytes.length, is(TEST_DATA_SIZE)); - assertByteSequence(bytes); - assertThat(pc.threads.size(), is(1)); - assertThat(pc.isOpen(), is(false)); - assertThat("Publisher did not concatenate read results to minimize output chunks!", - pc.readMethodCallCounter > (publisher.onNextCounter() * 2), - is(true)); - - } - - @Test - void chunky() throws Exception { - PeriodicalChannel pc = createChannelWithNoAvailableData(25, 3); - ReadableByteChannelPublisher publisher = new ReadableByteChannelPublisher(pc, RetrySchema.constant(2)); - // assert - byte[] bytes = ContentReaders.readBytes(publisher).get(5, TimeUnit.SECONDS); - assertThat(bytes.length, is(TEST_DATA_SIZE)); - assertByteSequence(bytes); - assertThat(pc.threads.size(), is(2)); - assertThat(pc.isOpen(), is(false)); - - LazyValue executor = publisher.executor(); - assertThat("Executor should have been used", executor.isLoaded(), is(true)); - assertThat("Executor should have been shut down", executor.get().isShutdown(), is(true)); - } - - @Test - void chunkyNoDelay() throws Exception { - PeriodicalChannel pc = createChannelWithNoAvailableData(10, 3); - ReadableByteChannelPublisher publisher = new ReadableByteChannelPublisher(pc, RetrySchema.constant(0)); - // assert - byte[] bytes = ContentReaders.readBytes(publisher).get(5, TimeUnit.SECONDS); - assertThat(bytes.length, is(TEST_DATA_SIZE)); - assertByteSequence(bytes); - assertThat(pc.threads.size(), is(1)); - assertThat(pc.isOpen(), is(false)); - } - - @Test - void onClosedChannel() throws Exception { - PeriodicalChannel pc = new PeriodicalChannel(i -> 1024, TEST_DATA_SIZE); - pc.close(); - ReadableByteChannelPublisher publisher = new ReadableByteChannelPublisher(pc, RetrySchema.constant(0)); - // assert - try { - ContentReaders.readBytes(publisher).get(5, TimeUnit.SECONDS); - fail("Did not throw expected ExecutionException!"); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(ClosedChannelException.class)); - } - - LazyValue executor = publisher.executor(); - assertThat("Executor should have not been used", executor.isLoaded(), is(false)); - } - - @Test - @Disabled("This test uses a sleep, so could cause issues on slow environments") - void onClosedInProgress() throws Exception { - PeriodicalChannel pc = createChannelWithNoAvailableData(5, 2); - - RetrySchema schema = RetrySchema.constant(TimeUnit.SECONDS.toMillis(2)); - ReadableByteChannelPublisher publisher = new ReadableByteChannelPublisher(pc, schema); - - // start reading (this will cause 2 second delay) - Single data = ContentReaders.readBytes(publisher); - // run the stream - data.thenRun(() -> { - }); - Thread.sleep(1000); - // immediately close the channel, so we fail reading - pc.close(); - - CompletionException c = assertThrows(CompletionException.class, () -> data.await(5, TimeUnit.SECONDS)); - assertThat(c.getCause(), instanceOf(ClosedChannelException.class)); - - LazyValue executor = publisher.executor(); - assertThat("Executor should have been used", executor.isLoaded(), is(true)); - assertThat("Executor should have been shut down", executor.get().isShutdown(), is(true)); - } - - @Test - void testCancelled() throws Exception { - PeriodicalChannel pc = createChannelWithNoAvailableData(5, 1); - - ReadableByteChannelPublisher publisher = new ReadableByteChannelPublisher(pc, RetrySchema.constant(100)); - - AtomicReference subscriptionRef = new AtomicReference<>(); - final CountDownLatch onNextCalled = new CountDownLatch(1); - AtomicReference failure = new AtomicReference<>(); - AtomicBoolean completeCalled = new AtomicBoolean(); - - publisher.subscribe(new Subscriber<>() { - @Override - public void onSubscribe(Subscription subscription) { - subscriptionRef.set(subscription); - subscription.request(1); - } - - @Override - public void onNext(DataChunk item) { - onNextCalled.countDown(); - } - - @Override - public void onError(Throwable throwable) { - failure.set(throwable); - } - - @Override - public void onComplete() { - completeCalled.set(true); - } - }); - - onNextCalled.await(5, TimeUnit.SECONDS); - subscriptionRef.get().cancel(); - - assertThat("Should not complete", completeCalled.get(), is(false)); - assertThat("Exception should be null", failure.get(), is(nullValue())); - - LazyValue executor = publisher.executor(); - assertThat("Executor should have been used", executor.isLoaded(), is(true)); - assertThat("Executor should have been shut down", executor.get().isShutdown(), is(true)); - } - - @Test - void negativeDelay() throws Exception { - PeriodicalChannel pc = createChannelWithNoAvailableData(10, 1); - - RetrySchema schema = (i, delay) -> i >= 3 ? -10 : 0; - ReadableByteChannelPublisher publisher = new ReadableByteChannelPublisher(pc, schema); - - // assert - try { - ContentReaders.readBytes(publisher).get(5, TimeUnit.SECONDS); - fail("Did not throw expected ExecutionException!"); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(TimeoutException.class)); - } - } - - private static PeriodicalChannel createChannelWithNoAvailableData(int hasDataCount, int noDataCount) { - - return new PeriodicalChannel(i -> { - int subIndex = i % (hasDataCount + noDataCount); - return subIndex < hasDataCount ? 512 : 0; - }, TEST_DATA_SIZE); - } - - private void assertByteSequence(byte[] bytes) { - assertThat(bytes, notNullValue()); - int index = 0; - for (int i = 0; i < bytes.length; i++) { - if (bytes[i] != PeriodicalChannel.SEQUENCE[index]) { - fail("Invalid (unexpected) byte in an array on position: " + i); - } - index++; - if (index == PeriodicalChannel.SEQUENCE.length) { - index = 0; - } - } - } - - private static class CountingOnNextSubscriber implements Subscriber { - - private final Subscriber delegate; - private volatile int onNextCount; - - CountingOnNextSubscriber(Subscriber delegate) { - this.delegate = delegate; - } - - @Override - public void onSubscribe(Subscription subscription) { - delegate.onSubscribe(subscription); - } - - @Override - public void onNext(DataChunk item) { - onNextCount++; - delegate.onNext(item); - } - - @Override - public void onError(Throwable throwable) { - delegate.onError(throwable); - } - - @Override - public void onComplete() { - delegate.onComplete(); - } - } - - static class CountingOnNextDelegatingPublisher implements Publisher { - - private final Publisher delegate; - private CountingOnNextSubscriber subscriber; - - CountingOnNextDelegatingPublisher(Publisher delegate) { - this.delegate = delegate; - } - - @Override - public void subscribe(Subscriber subscriber) { - if (this.subscriber == null) { - this.subscriber = new CountingOnNextSubscriber(subscriber); - } - delegate.subscribe(this.subscriber); - } - - int onNextCounter() { - return subscriber == null ? 0 : subscriber.onNextCount; - } - } - - static class PeriodicalChannel implements ReadableByteChannel { - - static final byte[] SEQUENCE = "abcdefghijklmnopqrstuvwxyz".getBytes(StandardCharsets.US_ASCII); - - private boolean open = true; - private int pointer = 0; - - private final IntFunction maxChunkSize; - private final long size; - - long count; - int readMethodCallCounter = 0; - final Set threads = new HashSet<>(); - - PeriodicalChannel(IntFunction maxChunkSize, long size) { - this.maxChunkSize = maxChunkSize; - this.size = size; - } - - @Override - public synchronized int read(ByteBuffer dst) throws IOException { - threads.add(Thread.currentThread()); - readMethodCallCounter++; - if (!open) { - throw new ClosedChannelException(); - } - if (dst == null || dst.remaining() == 0) { - return 0; - } - if (count >= size) { - return -1; - } - // Do read - int chunkSizeLimit = maxChunkSize.apply(readMethodCallCounter); - int writeCounter = 0; - while (count < size && writeCounter < chunkSizeLimit && dst.remaining() > 0) { - count++; - writeCounter++; - dst.put(pick()); - } - return writeCounter; - } - - private byte pick() { - byte result = SEQUENCE[pointer++]; - if (pointer >= SEQUENCE.length) { - pointer = 0; - } - return result; - } - - @Override - public synchronized boolean isOpen() { - return open; - } - - @Override - public synchronized void close() throws IOException { - this.open = false; - } - } -} diff --git a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyStreamWriter.java b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyStreamWriter.java index b8fd26301e0..7395193887a 100644 --- a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyStreamWriter.java +++ b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyStreamWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Multi; import io.helidon.media.common.MessageBodyStreamWriter; import io.helidon.media.common.MessageBodyWriterContext; @@ -58,7 +58,7 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont @Override public Multi write(Flow.Publisher publisher, GenericType type, MessageBodyWriterContext context) { - MediaType contentType = context.findAccepted(MediaType.JSON_PREDICATE, MediaType.APPLICATION_JSON); + HttpMediaType contentType = context.findAccepted(HttpMediaType.JSON_PREDICATE, HttpMediaType.JSON_UTF_8); context.contentType(contentType); AtomicBoolean first = new AtomicBoolean(true); diff --git a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyWriter.java b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyWriter.java index 1f31fa91833..1496ac785d3 100644 --- a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyWriter.java +++ b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonBodyWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.mapper.Mapper; import io.helidon.common.reactive.Single; import io.helidon.media.common.CharBuffer; @@ -56,7 +56,7 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont public Publisher write(Single content, GenericType type, MessageBodyWriterContext context) { - MediaType contentType = context.findAccepted(MediaType.JSON_PREDICATE, MediaType.APPLICATION_JSON); + HttpMediaType contentType = context.findAccepted(HttpMediaType.JSON_PREDICATE, HttpMediaType.JSON_UTF_8); context.contentType(contentType); return content.flatMap(new ObjectToChunks(objectMapper, context.charset())); } diff --git a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonEsBodyStreamWriter.java b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonEsBodyStreamWriter.java index a7fdfe513be..f3f6cbb9be0 100644 --- a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonEsBodyStreamWriter.java +++ b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonEsBodyStreamWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,9 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Multi; import io.helidon.media.common.MessageBodyStreamWriter; import io.helidon.media.common.MessageBodyWriterContext; @@ -31,12 +33,13 @@ /** * Message body stream writer supporting object binding with Jackson. - * This writer is for {@link MediaType#TEXT_EVENT_STREAM} with no element-type parameter or element-type="application/json". + * This writer is for {@link io.helidon.common.media.type.MediaTypes#TEXT_EVENT_STREAM} with no element-type parameter + * or element-type="application/json". */ class JacksonEsBodyStreamWriter implements MessageBodyStreamWriter { - private static final MediaType TEXT_EVENT_STREAM_JSON = MediaType - .parse("text/event-stream;element-type=\"application/json\""); + private static final HttpMediaType TEXT_EVENT_STREAM_JSON = HttpMediaType + .create("text/event-stream;element-type=\"application/json\""); private static final byte[] DATA = "data: ".getBytes(StandardCharsets.UTF_8); private static final byte[] NL = "\n\n".getBytes(StandardCharsets.UTF_8); @@ -57,7 +60,7 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont } return context.contentType() .or(() -> findMediaType(context)) - .filter(mediaType -> mediaType.equals(TEXT_EVENT_STREAM_JSON) || mediaType.equals(MediaType.TEXT_EVENT_STREAM)) + .filter(mediaType -> mediaType.equals(TEXT_EVENT_STREAM_JSON) || mediaType.equals(MediaTypes.TEXT_EVENT_STREAM)) .map(it -> PredicateResult.COMPATIBLE) .orElse(PredicateResult.NOT_SUPPORTED); } @@ -78,9 +81,9 @@ public Multi write(Flow.Publisher publisher, GenericType type, ); } - private Optional findMediaType(MessageBodyWriterContext context) { + private Optional findMediaType(MessageBodyWriterContext context) { try { - return Optional.of(context.findAccepted(MediaType.JSON_EVENT_STREAM_PREDICATE, TEXT_EVENT_STREAM_JSON)); + return Optional.of(context.findAccepted(HttpMediaType.JSON_EVENT_STREAM_PREDICATE, TEXT_EVENT_STREAM_JSON)); } catch (IllegalStateException ignore) { //Not supported. Ignore exception. return Optional.empty(); diff --git a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonNdBodyStreamWriter.java b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonNdBodyStreamWriter.java index 2b712e63842..1196b3e331c 100644 --- a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonNdBodyStreamWriter.java +++ b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonNdBodyStreamWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,9 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyStreamWriter; @@ -33,10 +35,10 @@ /** * Message body stream writer supporting object binding with Jackson. - * This writer is for {@link MediaType#APPLICATION_X_NDJSON} media type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_NDJSON} media type. */ class JacksonNdBodyStreamWriter implements MessageBodyStreamWriter { - + private static final HttpMediaType X_ND_JSON = HttpMediaType.create(MediaTypes.APPLICATION_X_NDJSON); private static final byte[] NL = "\n".getBytes(StandardCharsets.UTF_8); private final ObjectMapper objectMapper; @@ -56,14 +58,14 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont } return context.contentType() .or(() -> findMediaType(context)) - .filter(mediaType -> mediaType.equals(MediaType.APPLICATION_X_NDJSON)) + .filter(mediaType -> mediaType.equals(MediaTypes.APPLICATION_X_NDJSON)) .map(it -> PredicateResult.COMPATIBLE) .orElse(PredicateResult.NOT_SUPPORTED); } @Override public Multi write(Flow.Publisher publisher, GenericType type, MessageBodyWriterContext context) { - MediaType contentType = MediaType.APPLICATION_X_NDJSON; + MediaType contentType = MediaTypes.APPLICATION_X_NDJSON; context.contentType(contentType); JacksonBodyWriter.ObjectToChunks objectToChunks = new JacksonBodyWriter.ObjectToChunks(objectMapper, context.charset()); AtomicBoolean first = new AtomicBoolean(true); @@ -79,9 +81,9 @@ public Multi write(Flow.Publisher publisher, GenericType type, }); } - private Optional findMediaType(MessageBodyWriterContext context) { + private Optional findMediaType(MessageBodyWriterContext context) { try { - return Optional.of(context.findAccepted(MediaType.APPLICATION_X_NDJSON)); + return Optional.of(context.findAccepted(X_ND_JSON)); } catch (IllegalStateException ignore) { //Not supported. Ignore exception. return Optional.empty(); diff --git a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonSupport.java b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonSupport.java index 9338c7f35f9..906745db894 100644 --- a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonSupport.java +++ b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -139,7 +139,7 @@ public static MessageBodyStreamWriter streamWriter(ObjectMapper objectMa /** * Return a default Jackson entity event stream writer. - * This writer is for {@link io.helidon.common.http.MediaType#TEXT_EVENT_STREAM} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#TEXT_EVENT_STREAM} content type. * * @return new Jackson body stream writer instance */ @@ -149,7 +149,7 @@ public static MessageBodyStreamWriter eventStreamWriter() { /** * Create a new Jackson entity stream writer based on {@link ObjectMapper} instance. - * This writer is for {@link io.helidon.common.http.MediaType#TEXT_EVENT_STREAM} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#TEXT_EVENT_STREAM} content type. * * @param objectMapper object mapper instance * @return new Jackson body stream writer instance @@ -161,7 +161,7 @@ public static MessageBodyStreamWriter eventStreamWriter(ObjectMapper obj /** * Return a default Jackson entity event stream writer. - * This writer is for {@link io.helidon.common.http.MediaType#APPLICATION_X_NDJSON} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_NDJSON} content type. * * @return new Jackson body stream writer instance */ @@ -171,7 +171,7 @@ public static MessageBodyStreamWriter ndJsonStreamWriter() { /** * Create a new Jackson entity stream writer based on {@link ObjectMapper} instance. - * This writer is for {@link io.helidon.common.http.MediaType#APPLICATION_X_NDJSON} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_NDJSON} content type. * * @param objectMapper object mapper instance * @return new Jackson body stream writer instance @@ -209,7 +209,7 @@ public MessageBodyStreamWriter streamWriterInstance() { } /** - * Return Jackson stream writer instance for {@link io.helidon.common.http.MediaType#TEXT_EVENT_STREAM} content type. + * Return Jackson stream writer instance for {@link io.helidon.common.media.type.MediaTypes#TEXT_EVENT_STREAM} content type. * * @return Jackson event stream writer instance */ @@ -218,7 +218,8 @@ public MessageBodyStreamWriter eventStreamWriterInstance() { } /** - * Return Jackson stream writer instance for {@link io.helidon.common.http.MediaType#APPLICATION_X_NDJSON} content type. + * Return Jackson stream writer instance for {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_NDJSON} content + * type. * * @return Jackson event stream writer instance */ diff --git a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyStreamWriter.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyStreamWriter.java index cc367f3d167..ac5cf780a81 100644 --- a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyStreamWriter.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyStreamWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Multi; import io.helidon.media.common.MessageBodyStreamWriter; import io.helidon.media.common.MessageBodyWriterContext; diff --git a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyWriter.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyWriter.java index b368fdcd722..e6e4dfc816d 100644 --- a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyWriter.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.mapper.Mapper; import io.helidon.common.reactive.Single; import io.helidon.media.common.CharBuffer; diff --git a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbEsBodyStreamWriter.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbEsBodyStreamWriter.java index 27e8934b11d..992c574b28e 100644 --- a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbEsBodyStreamWriter.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbEsBodyStreamWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Multi; import io.helidon.media.common.MessageBodyStreamWriter; import io.helidon.media.common.MessageBodyWriterContext; diff --git a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbNdBodyStreamWriter.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbNdBodyStreamWriter.java index a83441a7ee0..a8528d041d9 100644 --- a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbNdBodyStreamWriter.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbNdBodyStreamWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyStreamWriter; diff --git a/media/jsonb/src/test/java/io/helidon/media/jsonb/TestJsonBindingSupport.java b/media/jsonb/src/test/java/io/helidon/media/jsonb/TestJsonBindingSupport.java index 76482bd418a..84e46a81803 100644 --- a/media/jsonb/src/test/java/io/helidon/media/jsonb/TestJsonBindingSupport.java +++ b/media/jsonb/src/test/java/io/helidon/media/jsonb/TestJsonBindingSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.webserver.Handler; import io.helidon.webserver.Routing; import io.helidon.webserver.testsupport.MediaPublisher; diff --git a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyStreamWriter.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyStreamWriter.java index 9cc8eedf525..f3e60dc7a49 100644 --- a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyStreamWriter.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyStreamWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Multi; import io.helidon.media.common.MessageBodyStreamWriter; import io.helidon.media.common.MessageBodyWriterContext; diff --git a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyWriter.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyWriter.java index 7f0c8a12c0c..af80302fb9d 100644 --- a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyWriter.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.mapper.Mapper; import io.helidon.common.reactive.Single; import io.helidon.media.common.CharBuffer; diff --git a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpEsBodyStreamWriter.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpEsBodyStreamWriter.java index 480956a6fa9..c63e7ca3c51 100644 --- a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpEsBodyStreamWriter.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpEsBodyStreamWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Multi; import io.helidon.media.common.MessageBodyStreamWriter; import io.helidon.media.common.MessageBodyWriterContext; diff --git a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpNdBodyStreamWriter.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpNdBodyStreamWriter.java index 91c9207aa84..7ce36bc6ec5 100644 --- a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpNdBodyStreamWriter.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpNdBodyStreamWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyStreamWriter; diff --git a/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonSupportTest.java b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonSupportTest.java index ed5aecf605c..1ba883e6848 100644 --- a/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonSupportTest.java +++ b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonSupportTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import java.util.concurrent.TimeUnit; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.webserver.Handler; import io.helidon.webserver.Routing; import io.helidon.webserver.testsupport.MediaPublisher; diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java index 077eefe4a42..e0d9e42e227 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.media.common.MessageBodyReaderContext; import io.helidon.media.common.MessageBodyStreamReader; diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java index 006c59068fa..9bd8eb5d10a 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.media.common.MessageBodyStreamWriter; import io.helidon.media.common.MessageBodyWriterContext; diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartHeaders.java b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartHeaders.java index 6998d20974f..20fec79e207 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartHeaders.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package io.helidon.media.multipart; import io.helidon.common.http.Headers; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; /** * Body part headers. diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java index d52068818a2..cd09d0c8b42 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.mapper.Mapper; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/ReadableBodyPartHeaders.java b/media/multipart/src/main/java/io/helidon/media/multipart/ReadableBodyPartHeaders.java index 42f825eccfe..ea4c7562b38 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/ReadableBodyPartHeaders.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/ReadableBodyPartHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import java.util.TreeMap; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.http.ReadOnlyParameters; /** diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/WriteableBodyPartHeaders.java b/media/multipart/src/main/java/io/helidon/media/multipart/WriteableBodyPartHeaders.java index a4532d185aa..033a5b19609 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/WriteableBodyPartHeaders.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/WriteableBodyPartHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import io.helidon.common.http.HashParameters; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; /** * Writeable body part headers. diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartHeadersTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartHeadersTest.java index 5157ff3c8cf..2521ca68476 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartHeadersTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartHeadersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ */ package io.helidon.media.multipart; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import org.junit.jupiter.api.Test; diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTest.java index 449f8bab4ba..49940694da9 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import java.util.stream.LongStream; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Multi; import io.helidon.media.common.ContentReaders; import io.helidon.media.multipart.MultiPartDecoderTest.DataChunkSubscriber; From 3001bda4ece6805adfcdbe96f435bfa67f0a4ab9 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 22:39:21 +0200 Subject: [PATCH 13/54] Remove coverage configuration --- pom.xml | 109 -------------------------------------------------------- 1 file changed, 109 deletions(-) diff --git a/pom.xml b/pom.xml index 5500b664fa9..551f5a3c1ac 100644 --- a/pom.xml +++ b/pom.xml @@ -1362,115 +1362,6 @@ helidon-parent,helidon-dependencies,helidon-bom,helidon-se,helidon-mp,io.grpc,he - - coverage - - true - - - ${top.parent.basedir}/target - ${jacoco.outputDir}/jacoco.exec - ${jacoco.outputDir}/jacoco-it.exec - - - ${jacoco.agent.ut.arg} - - ${jacoco.agent.it.arg} - true - - - - - - org.apache.maven.plugins - maven-surefire-plugin - - 10 - - - listener - org.sonar.java.jacoco.JUnitListener - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - 10 - - - listener - org.sonar.java.jacoco.JUnitListener - - - ${project.build.directory}/surefire-reports - - - - org.jacoco - jacoco-maven-plugin - ${version.plugin.jacoco} - - - - - - org.jacoco - jacoco-maven-plugin - - - prepare-ut-agent - process-test-classes - - prepare-agent - - - ${jacoco.report.path} - jacoco.agent.ut.arg - true - - - - prepare-it-agent - pre-integration-test - - prepare-agent - - - ${jacoco.report.it.path} - jacoco.agent.it.arg - true - - - - jacoco-report-unit-tests - test - - report - - - ${jacoco.report.path} - ${project.build.directory}/jacoco - - - - jacoco-report-integration-tests - post-integration-test - - report-integration - - - ${jacoco.report.it.path} - ${project.build.directory}/jacoco-it - - - - - - - release From b2b2738f7bff2de17233e825ee6e8830fab30f87 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 10 Aug 2022 22:40:46 +0200 Subject: [PATCH 14/54] HTTP testing module --- common/testing/http-junit5/pom.xml | 52 ++++ .../testing/http/HttpHeaderMatcher.java | 249 ++++++++++++++++++ .../common/testing/http/package-info.java | 25 ++ .../src/main/java/module-info.java | 27 ++ common/testing/pom.xml | 1 + config/config/pom.xml | 5 + config/testing/pom.xml | 4 + 7 files changed, 363 insertions(+) create mode 100644 common/testing/http-junit5/pom.xml create mode 100644 common/testing/http-junit5/src/main/java/io/helidon/common/testing/http/HttpHeaderMatcher.java create mode 100644 common/testing/http-junit5/src/main/java/io/helidon/common/testing/http/package-info.java create mode 100644 common/testing/http-junit5/src/main/java/module-info.java diff --git a/common/testing/http-junit5/pom.xml b/common/testing/http-junit5/pom.xml new file mode 100644 index 00000000000..6287c01cdca --- /dev/null +++ b/common/testing/http-junit5/pom.xml @@ -0,0 +1,52 @@ + + + + + 4.0.0 + + io.helidon.common.testing + helidon-common-testing-project + 4.0.0-SNAPSHOT + + + helidon-common-testing-http-junit5 + Helidon Common Testing HTTP JUnit5 + + + + io.helidon.common + helidon-common-http + + + io.helidon.common.testing + helidon-common-testing-junit5 + + + org.hamcrest + hamcrest-all + provided + + + org.junit.jupiter + junit-jupiter-api + provided + + + diff --git a/common/testing/http-junit5/src/main/java/io/helidon/common/testing/http/HttpHeaderMatcher.java b/common/testing/http-junit5/src/main/java/io/helidon/common/testing/http/HttpHeaderMatcher.java new file mode 100644 index 00000000000..bfbabcdbd4a --- /dev/null +++ b/common/testing/http-junit5/src/main/java/io/helidon/common/testing/http/HttpHeaderMatcher.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package io.helidon.common.testing.http; + +import java.util.List; + +import io.helidon.common.http.Headers; +import io.helidon.common.http.Http; +import io.helidon.common.http.Http.HeaderName; + +import org.hamcrest.Description; +import org.hamcrest.Matcher; +import org.hamcrest.Matchers; +import org.hamcrest.TypeSafeMatcher; + +/** + * Matchers for {@link io.helidon.common.http.Headers}. + */ +public final class HttpHeaderMatcher { + private HttpHeaderMatcher() { + } + + /** + * A matcher for an {@link io.helidon.common.http.Headers} that checks that the header is not present. + *

+ * Usage example: + *

+     *     assertThat(httpHeaders, noHeader(HeaderNames.CONNECTION));
+     * 
+ * + * @param name header name + * @return matcher validating the {@link io.helidon.common.http.Headers} does not contain the provided header + */ + public static Matcher noHeader(HeaderName name) { + return new NoHeaderMatcher(name); + } + + /** + * A matcher for an {@link io.helidon.common.http.Headers} that checks that the header is present, ignoring its value. + *

+ * Usage example: + *

+     *     assertThat(httpHeaders, hasHeader(HeaderNames.CONNECTION));
+     * 
+ * + * @param name header name + * @return matcher validating the {@link io.helidon.common.http.Headers} does contain the provided header regardless of its value(s) + */ + public static Matcher hasHeader(HeaderName name) { + return new HasHeaderMatcher(name); + } + + /** + * A matcher for an {@link io.helidon.common.http.Headers} that checks that the header is present and has the defined + * value. + *

+ * Usage example: + *

+     *     assertThat(httpHeaders, hasHeader(HeaderValues.CONNECTION_CLOSE));
+     * 
+ * + * @param header http header with values + * @return matcher validating the {@link io.helidon.common.http.Headers} does contain the provided header regardless of its value(s) + */ + public static Matcher hasHeader(Http.HeaderValue header) { + return new HasValueMatcher(header); + } + + /** + * A matcher for an {@link io.helidon.common.http.Headers} that checks that the header is present and values + * match the provided matcher. + *

+ * Usage example: + *

+     *     assertThat(httpHeaders, hasHeader(HeaderNames.CONNECTION, contains("close")));
+     * 
+ * + * @param name header name + * @param valuesMatcher matcher to validate the values are OK + * @return matcher validating the {@link io.helidon.common.http.Headers} does contain the expected values + */ + public static Matcher hasHeader(HeaderName name, Matcher> valuesMatcher) { + return new HasValueMatcher(name, valuesMatcher); + } + + /** + * A matcher for an {@link io.helidon.common.http.Headers} that checks that the header is present and its single value + * matches the provided matcher. + *

+ * Usage example: + *

+     *     assertThat(httpHeaders, hasHeaderValue(HeaderNames.CONNECTION, startsWith("c")));
+     * 
+ * + * @param name header name + * @param valueMatcher matcher to validate the value is OK + * @return matcher validating the {@link io.helidon.common.http.Headers} does contain the expected value + */ + public static Matcher hasHeaderValue(HeaderName name, Matcher valueMatcher) { + return new HasSingleValueMatcher(name, valueMatcher); + } + + private static class HasHeaderMatcher extends TypeSafeMatcher { + private final HeaderName name; + + HasHeaderMatcher(HeaderName header) { + this.name = header; + } + + @Override + public void describeTo(Description description) { + description.appendValue(name.defaultCase()) + .appendText(" should be present"); + } + + @Override + protected boolean matchesSafely(Headers httpHeaders) { + return httpHeaders.contains(name); + } + + @Override + protected void describeMismatchSafely(Headers item, Description mismatchDescription) { + mismatchDescription.appendValue(name.defaultCase()).appendText(" header is not present"); + } + } + + private static class HasValueMatcher extends TypeSafeMatcher { + private final HeaderName name; + private final Matcher> valuesMatcher; + + HasValueMatcher(Http.HeaderValue header) { + this.name = header.headerName(); + this.valuesMatcher = Matchers.containsInAnyOrder(header.allValues().toArray(new String[0])); + } + + HasValueMatcher(HeaderName name, Matcher> valuesMatcher) { + this.name = name; + this.valuesMatcher = valuesMatcher; + } + + @Override + public void describeTo(Description description) { + description.appendValue(name.defaultCase()).appendText(" should be present and values should match "); + valuesMatcher.describeTo(description); + } + + @Override + protected boolean matchesSafely(Headers httpHeaders) { + if (httpHeaders.contains(name)) { + return valuesMatcher.matches(httpHeaders.all(name, List::of)); + } + return false; + } + + @Override + protected void describeMismatchSafely(Headers item, Description mismatchDescription) { + if (item.contains(name)) { + List all = item.all(name, List::of); + mismatchDescription.appendValue(name.defaultCase()).appendText(" header is present with wrong values "); + valuesMatcher.describeMismatch(all, mismatchDescription); + } else { + mismatchDescription.appendValue(name.defaultCase()).appendText("header is not present"); + } + } + } + + private static class HasSingleValueMatcher extends TypeSafeMatcher { + private final HeaderName name; + private final Matcher valuesMatcher; + + HasSingleValueMatcher(HeaderName name, Matcher valuesMatcher) { + this.name = name; + this.valuesMatcher = valuesMatcher; + } + + @Override + public void describeTo(Description description) { + description.appendValue(name.defaultCase()).appendText(" should be present and value should match "); + valuesMatcher.describeTo(description); + } + + @Override + protected boolean matchesSafely(Headers httpHeaders) { + if (httpHeaders.contains(name)) { + Http.HeaderValue headerValue = httpHeaders.get(name); + if (headerValue.allValues().size() == 1) { + return valuesMatcher.matches(headerValue.value()); + } + return false; + } + return false; + } + + @Override + protected void describeMismatchSafely(Headers item, Description mismatchDescription) { + if (item.contains(name)) { + List all = item.all(name, List::of); + if (all.size() == 1) { + mismatchDescription.appendValue(name.defaultCase()).appendText(" header is present with wrong value "); + valuesMatcher.describeMismatch(all, mismatchDescription); + } else { + mismatchDescription.appendValue(name.defaultCase()).appendText(" header is present with more than one value"); + } + } else { + mismatchDescription.appendValue(name.defaultCase()).appendText("header is not present"); + } + } + } + + private static class NoHeaderMatcher extends TypeSafeMatcher { + private final HeaderName name; + + private NoHeaderMatcher(HeaderName name) { + this.name = name; + } + + @Override + public void describeTo(Description description) { + description.appendValue(name.defaultCase()).appendText(" should not be present"); + } + + @Override + protected boolean matchesSafely(Headers httpHeaders) { + return !httpHeaders.contains(name); + } + + @Override + protected void describeMismatchSafely(Headers item, Description mismatchDescription) { + if (item.contains(name)) { + mismatchDescription.appendValue(name.defaultCase()) + .appendText(" header is present, and its value is "); + mismatchDescription.appendValue(item.get(name).allValues()); + } + } + } +} diff --git a/common/testing/http-junit5/src/main/java/io/helidon/common/testing/http/package-info.java b/common/testing/http-junit5/src/main/java/io/helidon/common/testing/http/package-info.java new file mode 100644 index 00000000000..380120b2515 --- /dev/null +++ b/common/testing/http-junit5/src/main/java/io/helidon/common/testing/http/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Testing support for HTTP. + *

+ * Hamcrest Matchers: + *

    + *
  • {@link io.helidon.common.testing.http.HttpHeaderMatcher}
  • + *
+ */ +package io.helidon.common.testing.http; diff --git a/common/testing/http-junit5/src/main/java/module-info.java b/common/testing/http-junit5/src/main/java/module-info.java new file mode 100644 index 00000000000..803d76c8e9a --- /dev/null +++ b/common/testing/http-junit5/src/main/java/module-info.java @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Hamcrest matchers for HTTP. + */ +module io.helidon.common.testing.http { + requires transitive io.helidon.common.testing; + requires io.helidon.common.http; + requires hamcrest.all; + requires org.junit.jupiter.api; + + exports io.helidon.common.testing.http; +} \ No newline at end of file diff --git a/common/testing/pom.xml b/common/testing/pom.xml index d5548ac6ef2..5c4f4502c72 100644 --- a/common/testing/pom.xml +++ b/common/testing/pom.xml @@ -33,5 +33,6 @@ junit5 + http-junit5 diff --git a/config/config/pom.xml b/config/config/pom.xml index 3019e178400..fdf25fb9c45 100644 --- a/config/config/pom.xml +++ b/config/config/pom.xml @@ -56,6 +56,11 @@ true provided + + io.helidon.common.testing + helidon-common-testing-junit5 + test + org.junit.jupiter junit-jupiter-api diff --git a/config/testing/pom.xml b/config/testing/pom.xml index 2df02939ae7..533b41d7591 100644 --- a/config/testing/pom.xml +++ b/config/testing/pom.xml @@ -38,6 +38,10 @@ io.helidon.config helidon-config + + io.helidon.common.testing + helidon-common-testing-junit5 + org.junit.jupiter junit-jupiter-api From 897d1db044037430c04e8baee89867639101c9ca Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Tue, 3 May 2022 10:34:28 +0200 Subject: [PATCH 15/54] WebClient refactored to new common. --- webclient/webclient/pom.xml | 9 + .../io/helidon/webclient/FileSubscriber.java | 128 ------------ .../io/helidon/webclient/NettyClient.java | 4 +- .../helidon/webclient/NettyClientHandler.java | 28 ++- .../webclient/RedirectInterceptor.java | 7 +- .../java/io/helidon/webclient/WebClient.java | 5 +- .../webclient/WebClientConfiguration.java | 18 +- .../webclient/WebClientCookieManager.java | 7 +- .../webclient/WebClientQueryParams.java | 166 ---------------- .../webclient/WebClientRequestBuilder.java | 125 ++++++++++-- .../WebClientRequestBuilderImpl.java | 183 ++++-------------- .../webclient/WebClientRequestHeaders.java | 116 +++++------ .../WebClientRequestHeadersImpl.java | 178 +++++++++-------- .../webclient/WebClientRequestImpl.java | 20 +- .../helidon/webclient/WebClientResponse.java | 4 +- .../webclient/WebClientResponseHeaders.java | 23 +-- .../WebClientResponseHeadersImpl.java | 80 +++++--- .../webclient/WebClientResponseImpl.java | 24 +-- .../webclient/WebClientServiceRequest.java | 71 ++++++- .../WebClientServiceRequestImpl.java | 13 +- .../webclient/WebClientServiceResponse.java | 4 +- .../WebClientServiceResponseImpl.java | 8 +- .../webclient/src/main/java/module-info.java | 3 + .../ClientRequestHeadersImplTest.java | 97 ++++++---- .../helidon/webclient/ConfigurationTest.java | 18 +- .../helidon/webclient/OrderOfWritesTest.java | 2 +- .../helidon/webclient/WebClientTlsTest.java | 11 +- 27 files changed, 594 insertions(+), 758 deletions(-) delete mode 100644 webclient/webclient/src/main/java/io/helidon/webclient/FileSubscriber.java delete mode 100644 webclient/webclient/src/main/java/io/helidon/webclient/WebClientQueryParams.java diff --git a/webclient/webclient/pom.xml b/webclient/webclient/pom.xml index 9409877425c..2121002935c 100644 --- a/webclient/webclient/pom.xml +++ b/webclient/webclient/pom.xml @@ -51,6 +51,10 @@ io.helidon.common helidon-common-reactive + + io.helidon.common + helidon-common-parameters + io.helidon.config helidon-config @@ -96,6 +100,11 @@ mockito-core test + + io.helidon.common.testing + helidon-common-testing-http + test + io.helidon.config helidon-config-metadata diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/FileSubscriber.java b/webclient/webclient/src/main/java/io/helidon/webclient/FileSubscriber.java deleted file mode 100644 index e1ea3f4d356..00000000000 --- a/webclient/webclient/src/main/java/io/helidon/webclient/FileSubscriber.java +++ /dev/null @@ -1,128 +0,0 @@ -/* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.webclient; - -import java.io.IOException; -import java.nio.channels.FileChannel; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.StandardOpenOption; -import java.util.concurrent.CompletableFuture; -import java.util.concurrent.CompletionStage; -import java.util.concurrent.Flow; -import java.util.logging.Level; -import java.util.logging.Logger; - -import io.helidon.common.http.DataChunk; - -/** - * A file writer that subscribes to chunks of data. - * - * @deprecated use {@link io.helidon.common.reactive.IoMulti#writeToFile(java.nio.file.Path)} instead - */ -@Deprecated(forRemoval = true) -public final class FileSubscriber implements Flow.Subscriber { - private static final Logger LOGGER = Logger.getLogger(FileSubscriber.class.getName()); - - private final CompletableFuture resultFuture = new CompletableFuture<>(); - private final Path filePath; - private final Path tempPath; - private final FileChannel channel; - - private Flow.Subscription subscription; - - private FileSubscriber(Path filePath, Path tempPath, FileChannel channel) { - this.filePath = filePath; - this.tempPath = tempPath; - this.channel = channel; - } - - /** - * Subscribes this instance to the obtained publisher. - * - * @param publisher publisher - * @return completion stage of the saving process - */ - public CompletionStage subscribeTo(Flow.Publisher publisher) { - publisher.subscribe(this); - return resultFuture; - } - - /** - * Create a subscriber that consumes {@link DataChunk DataChunks} and writes them to a file. - * A temporary file is created first to download the whole content and it is then moved to the final - * destination. - * - * @param filePath path of the final file - * @return subscriber to consume {@link DataChunk} - */ - public static FileSubscriber create(Path filePath) { - // make sure we can write the path - if (Files.exists(filePath)) { - throw new WebClientException("Path " + filePath.toAbsolutePath() + " already exists, cannot download into it"); - } - - try { - Path tempPath = Files.createTempFile("helidon-large", ".tmp"); - FileChannel channel = FileChannel.open(tempPath, StandardOpenOption.WRITE); - return new FileSubscriber(filePath, tempPath, channel); - } catch (IOException e) { - throw new WebClientException("Failed to open temporary file", e); - } - } - - @Override - public void onSubscribe(Flow.Subscription subscription) { - this.subscription = subscription; - subscription.request(1); - } - - @Override - public void onNext(DataChunk item) { - try { - channel.write(item.data()); - - subscription.request(1); - } catch (IOException e) { - throw new WebClientException("Failed to write data to temporary file: " + tempPath.toAbsolutePath(), e); - } finally { - item.release(); - } - } - - @Override - public void onError(Throwable throwable) { - try { - channel.close(); - } catch (IOException e) { - LOGGER.log(Level.WARNING, "Received an onError", e); - } - - resultFuture.completeExceptionally(throwable); - } - - @Override - public void onComplete() { - try { - channel.close(); - Files.move(tempPath, filePath); - resultFuture.complete(filePath); - } catch (IOException e) { - throw new WebClientException("Failed to move file from temp to final. Temp: " + tempPath - .toAbsolutePath() + ", final: " + filePath.toAbsolutePath(), e); - } - } -} diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/NettyClient.java b/webclient/webclient/src/main/java/io/helidon/webclient/NettyClient.java index d728912b253..cc84571f104 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/NettyClient.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/NettyClient.java @@ -156,11 +156,11 @@ public WebClientRequestBuilder head() { @Override public WebClientRequestBuilder method(String method) { - return WebClientRequestBuilderImpl.create(EVENT_GROUP.get(), configuration, Http.RequestMethod.create(method)); + return WebClientRequestBuilderImpl.create(EVENT_GROUP.get(), configuration, Http.Method.create(method)); } @Override - public WebClientRequestBuilder method(Http.RequestMethod method) { + public WebClientRequestBuilder method(Http.Method method) { return WebClientRequestBuilderImpl.create(EVENT_GROUP.get(), configuration, method); } diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/NettyClientHandler.java b/webclient/webclient/src/main/java/io/helidon/webclient/NettyClientHandler.java index 8709249a420..d3b3094d19d 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/NettyClientHandler.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/NettyClientHandler.java @@ -17,7 +17,9 @@ import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.atomic.AtomicBoolean; @@ -36,6 +38,7 @@ import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.handler.codec.http.HttpContent; +import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.handler.codec.http.HttpHeaders; import io.netty.handler.codec.http.HttpObject; @@ -131,7 +134,7 @@ protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws IO responseBuilder.addHeader(name, values); } - String connection = nettyHeaders.get(Http.Header.CONNECTION, HttpHeaderValues.CLOSE.toString()); + String connection = nettyHeaders.get(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE.toString()); if (connection.equals(HttpHeaderValues.CLOSE.toString())) { ctx.channel().attr(WILL_CLOSE).set(true); } @@ -154,12 +157,21 @@ protected void channelRead0(ChannelHandlerContext ctx, HttpObject msg) throws IO } } + Map> cookieMap = new HashMap<>(); + WebClientResponseHeaders responseHeaders = clientResponse.headers(); + + if (responseHeaders.contains(Http.Header.SET_COOKIE)) { + cookieMap.put(Http.Header.SET_COOKIE.defaultCase(), responseHeaders.get(Http.Header.SET_COOKIE).allValues()); + } + if (responseHeaders.contains(Http.Header.SET_COOKIE2)) { + cookieMap.put(Http.Header.SET_COOKIE2.defaultCase(), responseHeaders.get(Http.Header.SET_COOKIE2).allValues()); + } requestConfiguration.cookieManager().put(requestConfiguration.requestURI(), - clientResponse.headers().toMap()); + cookieMap); WebClientServiceResponse clientServiceResponse = new WebClientServiceResponseImpl(requestConfiguration.context().get(), - clientResponse.headers(), + responseHeaders, clientResponse.status()); channel.attr(SERVICE_RESPONSE).set(clientServiceResponse); @@ -252,9 +264,9 @@ && noContentLength(headers) } private boolean noContentLength(WebClientResponseHeaders headers) { - return headers.contentLength() - .map(value -> value == 0) - .orElse(true); + long l = headers.contentLength().orElse(0); + + return l == 0; } private boolean notChunked(WebClientResponseHeaders headers) { @@ -274,8 +286,8 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { ctx.close(); } - private Http.ResponseStatus helidonStatus(HttpResponseStatus nettyStatus) { - return Http.ResponseStatus.create(nettyStatus.code(), nettyStatus.reasonPhrase()); + private Http.Status helidonStatus(HttpResponseStatus nettyStatus) { + return Http.Status.create(nettyStatus.code(), nettyStatus.reasonPhrase()); } private static final class HttpResponsePublisher extends BufferedEmittingPublisher { diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/RedirectInterceptor.java b/webclient/webclient/src/main/java/io/helidon/webclient/RedirectInterceptor.java index 026048ef297..e7a176ac517 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/RedirectInterceptor.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/RedirectInterceptor.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import io.helidon.common.http.Http; +import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpResponse; import io.netty.handler.codec.http.HttpResponseStatus; @@ -36,9 +37,9 @@ class RedirectInterceptor implements HttpInterceptor { public void handleInterception(HttpResponse httpResponse, WebClientRequestImpl clientRequest, CompletableFuture responseFuture) { - if (httpResponse.headers().contains(Http.Header.LOCATION)) { + if (httpResponse.headers().contains(HttpHeaderNames.LOCATION)) { long requestId = clientRequest.configuration().requestId(); - String newUri = httpResponse.headers().get(Http.Header.LOCATION); + String newUri = httpResponse.headers().get(HttpHeaderNames.LOCATION); LOGGER.finest(() -> "(client reqID: " + requestId + ") Redirecting to " + newUri); WebClientRequestBuilder requestBuilder = WebClientRequestBuilderImpl .create(clientRequest); diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClient.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClient.java index 8ccb55c327b..8bcc75816ba 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClient.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClient.java @@ -20,7 +20,6 @@ import java.net.URL; import java.time.Duration; import java.util.ArrayList; -import java.util.Arrays; import java.util.List; import java.util.ServiceLoader; import java.util.concurrent.TimeUnit; @@ -137,7 +136,7 @@ static Builder builder() { * @param method request method * @return client request builder */ - WebClientRequestBuilder method(Http.RequestMethod method); + WebClientRequestBuilder method(Http.Method method); /** * Fluent API builder for {@link io.helidon.webclient.WebClient}. @@ -318,7 +317,7 @@ public Builder addCookie(String name, String value) { * @return updated builder instance */ public Builder addHeader(String header, String... value) { - configuration.defaultHeader(header, Arrays.asList(value)); + configuration.defaultHeader(Http.HeaderValue.create(Http.Header.create(header), value)); return this; } diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientConfiguration.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientConfiguration.java index 6452ba1220f..9a8c1258291 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientConfiguration.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientConfiguration.java @@ -34,6 +34,8 @@ import io.helidon.common.LazyValue; import io.helidon.common.context.Context; +import io.helidon.common.http.Http.Header; +import io.helidon.common.http.Http.HeaderValue; import io.helidon.config.Config; import io.helidon.config.DeprecatedConfig; import io.helidon.config.metadata.Configured; @@ -486,16 +488,15 @@ public B defaultCookie(String key, String value) { /** * Adds default header to every request. * - * @param key header name - * @param values header value + * @param value header value * @return updated builder instance */ @ConfiguredOption(key = "headers", type = Map.class, description = "Default headers to be used in each request. " + "Each list entry has to have \"name\" and \"value\" node") - public B defaultHeader(String key, List values) { - clientHeaders.put(key, values); + public B defaultHeader(HeaderValue value) { + clientHeaders.set(value); return me; } @@ -736,8 +737,13 @@ public B update(WebClientConfiguration configuration) { private void headers(Config configHeaders) { configHeaders.asNodeList() .ifPresent(headers -> headers - .forEach(header -> defaultHeader(header.get("name").asString().get(), - header.get("value").asList(String.class).get()))); + .forEach(header -> + defaultHeader(HeaderValue.create(Header.create(header.get("name") + .asString() + .get()), + header.get("value") + .asList(String.class) + .get())))); } private void cookies(Config cookies) { diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientCookieManager.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientCookieManager.java index 77d07aeab73..d503591a064 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientCookieManager.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientCookieManager.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,7 +58,8 @@ public Map> get(URI uri, Map> requestH addAllDefaultHeaders(toReturn); if (acceptCookies) { Map> cookies = super.get(uri, requestHeaders); - cookies.get(Http.Header.COOKIE).forEach(s -> toReturn.get(Http.Header.COOKIE).add(s)); + cookies.get(Http.Header.COOKIE.defaultCase()) + .forEach(s -> toReturn.get(Http.Header.COOKIE.defaultCase()).add(s)); } return Collections.unmodifiableMap(toReturn); } @@ -66,7 +67,7 @@ public Map> get(URI uri, Map> requestH private void addAllDefaultHeaders(Map> toReturn) { List defaultCookieList = new ArrayList<>(); defaultCookies.forEach((key, value) -> defaultCookieList.add(key + "=" + value)); - toReturn.put(Http.Header.COOKIE, defaultCookieList); + toReturn.put(Http.Header.COOKIE.defaultCase(), defaultCookieList); } @Override diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientQueryParams.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientQueryParams.java deleted file mode 100644 index 16b4807162c..00000000000 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientQueryParams.java +++ /dev/null @@ -1,166 +0,0 @@ -/* - * Copyright (c) 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.webclient; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.function.Function; - -import io.helidon.common.http.HashParameters; -import io.helidon.common.http.Parameters; - -class WebClientQueryParams implements Parameters { - - private final Parameters rawParams; - private final Parameters encodedParams; - - private boolean skipEncoding = false; - - WebClientQueryParams() { - rawParams = HashParameters.create(); - encodedParams = HashParameters.create(); - } - - @Override - public Optional first(String name) { - String key = name; - if (!skipEncoding) { - key = encode(name); - } - return pickCorrectParameters().first(key); - } - - @Override - public List all(String name) { - String key = name; - if (!skipEncoding) { - key = encode(name); - } - return pickCorrectParameters().all(key); - } - - @Override - public List put(String key, String... values) { - List toEncode = values == null ? null : Arrays.asList(values); - encodedParams.put(encode(key), encodeIterable(toEncode)); - return rawParams.put(key, values); - } - - @Override - public List put(String key, Iterable values) { - encodedParams.put(encode(key), encodeIterable(values)); - return rawParams.put(key, values); - } - - @Override - public List putIfAbsent(String key, String... values) { - List toEncode = values == null ? null : Arrays.asList(values); - encodedParams.putIfAbsent(encode(key), encodeIterable(toEncode)); - return rawParams.putIfAbsent(key, values); - } - - @Override - public List putIfAbsent(String key, Iterable values) { - encodedParams.putIfAbsent(encode(key), encodeIterable(values)); - return rawParams.putIfAbsent(key, values); - } - - @Override - public List computeIfAbsent(String key, - Function> values) { - encodedParams.computeIfAbsent(encode(key), k -> encodeIterable(values.apply(key))); - return rawParams.computeIfAbsent(key, values); - } - - @Override - public List computeSingleIfAbsent(String key, - Function value) { - encodedParams.computeSingleIfAbsent(encode(key), k -> encode(value.apply(k))); - return rawParams.computeSingleIfAbsent(key, value); - } - - @Override - public WebClientQueryParams putAll(Parameters parameters) { - if (parameters == null) { - return this; - } - rawParams.putAll(parameters); - parameters.toMap().forEach((key, value) -> encodedParams.put(encode(key), encodeIterable(value))); - return this; - } - - @Override - public WebClientQueryParams add(String key, String... values) { - rawParams.add(key, values); - encodedParams.add(encode(key), encodeIterable(Arrays.asList(values))); - return this; - } - - @Override - public WebClientQueryParams add(String key, Iterable values) { - rawParams.add(key, values); - encodedParams.add(encode(key), encodeIterable(values)); - return this; - } - - @Override - public WebClientQueryParams addAll(Parameters parameters) { - if (parameters == null) { - return this; - } - rawParams.addAll(parameters); - parameters.toMap().forEach((key, value) -> encodedParams.add(key, encodeIterable(value))); - return this; - } - - private Iterable encodeIterable(Iterable iterable) { - if (iterable == null) { - return null; - } - List toReturn = new ArrayList<>(); - for (String value : iterable) { - toReturn.add(encode(value)); - } - return toReturn; - } - - private String encode(String value) { - return UriComponentEncoder.encode(value, UriComponentEncoder.Type.QUERY_PARAM); - } - - @Override - public List remove(String key) { - encodedParams.remove(encode(key)); - return rawParams.remove(key); - } - - @Override - public Map> toMap() { - return rawParams.toMap(); - } - - void skipEncoding() { - this.skipEncoding = true; - } - - Parameters pickCorrectParameters() { - return skipEncoding ? rawParams : encodedParams; - } -} diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilder.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilder.java index 0a2005fb948..f5e85f8434a 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilder.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import java.net.URI; import java.net.URL; +import java.util.List; import java.util.Map; import java.util.concurrent.CompletionStage; import java.util.concurrent.Flow; @@ -28,10 +29,10 @@ import io.helidon.common.http.DataChunk; import io.helidon.common.http.Headers; import io.helidon.common.http.Http; -import io.helidon.common.http.HttpRequest; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Single; +import io.helidon.common.uri.UriPath; +import io.helidon.common.uri.UriQuery; import io.helidon.media.common.MessageBodyReaderContext; import io.helidon.media.common.MessageBodyWriterContext; import io.helidon.webclient.spi.WebClientService; @@ -159,7 +160,6 @@ public interface WebClientRequestBuilder { * @return this instance of {@link WebClientRequestBuilder} * @throws NullPointerException if the specified name is null. * @see #headers() - * @see Parameters#add(String, String...) * @see Http.Header header names constants */ default WebClientRequestBuilder addHeader(String name, String... values) { @@ -175,11 +175,45 @@ default WebClientRequestBuilder addHeader(String name, String... values) { * @return this instance of {@link WebClientRequestBuilder} * @throws NullPointerException if the specified name is null. * @see #headers() - * @see Parameters#add(String, Iterable) * @see Http.Header header names constants */ - default WebClientRequestBuilder addHeader(String name, Iterable values) { - headers().add(name, values); + default WebClientRequestBuilder addHeader(String name, List values) { + addHeader(Http.Header.create(name), values); + return this; + } + + /** + * Adds header value. + * + * @param value header value + * @return updated builder + */ + default WebClientRequestBuilder addHeader(Http.HeaderValue value) { + headers().add(value); + return this; + } + + /** + * Adds header values for a specified name. + * + * @param name header name + * @param values value(s) + * @return updated builder + */ + default WebClientRequestBuilder addHeader(Http.HeaderName name, String... values) { + headers().add(Http.HeaderValue.create(name, values)); + return this; + } + + /** + * Adds header values for a specified name. + * + * @param name header name + * @param values value(s) + * @return updated builder + */ + default WebClientRequestBuilder addHeader(Http.HeaderName name, List values) { + headers().add(Http.HeaderValue.create(name, values)); return this; } @@ -190,9 +224,8 @@ default WebClientRequestBuilder addHeader(String name, Iterable values) * @return this instance of {@link WebClientRequestBuilder} * @throws NullPointerException if the specified {@code parameters} are null. * @see #headers() - * @see Parameters#addAll(Parameters) */ - default WebClientRequestBuilder addHeaders(Parameters parameters){ + default WebClientRequestBuilder addHeaders(Headers parameters){ headers().addAll(parameters); return this; } @@ -202,12 +235,12 @@ default WebClientRequestBuilder addHeaders(Parameters parameters){ * * Appends these query parameters to the query parameters defined in the request uri. * - * Copy all query parameters from supplied {@link Parameters} instance. + * Copy all query parameters from supplied {@link io.helidon.common.uri.UriQuery} instance. * * @param queryParams to copy * @return updated builder instance */ - WebClientRequestBuilder queryParams(Parameters queryParams); + WebClientRequestBuilder queryParams(UriQuery queryParams); /** * Returns reader context of the request builder. @@ -267,7 +300,7 @@ default WebClientRequestBuilder addHeaders(Parameters parameters){ * @param path path * @return updated builder instance */ - WebClientRequestBuilder path(HttpRequest.Path path); + WebClientRequestBuilder path(UriPath path); /** * Path of the request. @@ -285,7 +318,7 @@ default WebClientRequestBuilder addHeaders(Parameters parameters){ * @param contentType content type * @return updated builder instance */ - WebClientRequestBuilder contentType(MediaType contentType); + WebClientRequestBuilder contentType(HttpMediaType contentType); /** * Media types which are accepted in the response. @@ -293,7 +326,7 @@ default WebClientRequestBuilder addHeaders(Parameters parameters){ * @param mediaTypes media types * @return updated builder instance */ - WebClientRequestBuilder accept(MediaType... mediaTypes); + WebClientRequestBuilder accept(HttpMediaType... mediaTypes); /** * Whether connection should be kept alive after request. @@ -422,8 +455,68 @@ default WebClientRequestBuilder addHeaders(Parameters parameters){ /** * Request to a server. Contains all information about used request headers, configuration etc. */ - interface ClientRequest extends HttpRequest { + interface ClientRequest { + /** + * Returns an HTTP request method. See also {@link Http.Method HTTP standard methods} utility class. + * + * @return an HTTP method + * @see Http.Method + */ + Http.Method method(); + /** + * Returns an HTTP version from the request line. + *

+ * See {@link Http.Version HTTP Version} enumeration for supported versions. + *

+ * If communication starts as a {@code HTTP/1.1} with {@code h2c} upgrade, then it will be automatically + * upgraded and this method returns {@code HTTP/2.0}. + * + * @return an HTTP version + */ + Http.Version version(); + + /** + * Returns a Request-URI (or alternatively path) as defined in request line. + * + * @return a request URI + */ + URI uri(); + + /** + * Returns an encoded query string without leading '?' character. + * + * @return an encoded query string + */ + String query(); + + /** + * Returns query parameters. + * + * @return an parameters representing query parameters + */ + UriQuery queryParams(); + + /** + * Returns a path which was accepted by matcher in actual routing. It is path without a context root + * of the routing. + *

+ * Use {@link io.helidon.common.uri.UriPath#absolute()} method to obtain absolute request URI path representation. + *

+ * Returned {@link io.helidon.common.uri.UriPath} also provides access to path template parameters. + * An absolute path then provides access to + * all (including) context parameters if any. In case of conflict between parameter names, most recent value is returned. + * + * @return a path + */ + UriPath path(); + + /** + * Returns a decoded request URI fragment without leading hash '#' character. + * + * @return a decoded URI fragment + */ + String fragment(); /** * Headers which are used in current request. * diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilderImpl.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilderImpl.java index c444be769c3..3225f6dd003 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilderImpl.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilderImpl.java @@ -31,7 +31,6 @@ import java.util.Optional; import java.util.ServiceLoader; import java.util.Set; -import java.util.StringTokenizer; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; import java.util.concurrent.ConcurrentHashMap; @@ -51,10 +50,11 @@ import io.helidon.common.http.DataChunk; import io.helidon.common.http.Headers; import io.helidon.common.http.Http; -import io.helidon.common.http.HttpRequest; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Single; +import io.helidon.common.uri.UriPath; +import io.helidon.common.uri.UriQuery; +import io.helidon.common.uri.UriQueryWriteable; import io.helidon.media.common.MessageBodyReadableContent; import io.helidon.media.common.MessageBodyReaderContext; import io.helidon.media.common.MessageBodyWriterContext; @@ -117,9 +117,9 @@ class WebClientRequestBuilderImpl implements WebClientRequestBuilder { private final Map properties; private final NioEventLoopGroup eventGroup; private final WebClientConfiguration configuration; - private final Http.RequestMethod method; + private final Http.Method method; private final WebClientRequestHeaders headers; - private final WebClientQueryParams queryParams; + private final UriQueryWriteable queryParams; private final MessageBodyReaderContext readerContext; private final MessageBodyWriterContext writerContext; @@ -133,7 +133,7 @@ class WebClientRequestBuilderImpl implements WebClientRequestBuilder { private boolean skipUriEncoding; private int redirectionCount; private RequestConfiguration requestConfiguration; - private HttpRequest.Path path; + private UriPath path; private List services; private Duration readTimeout; private Duration connectTimeout; @@ -143,7 +143,7 @@ class WebClientRequestBuilderImpl implements WebClientRequestBuilder { private WebClientRequestBuilderImpl(NioEventLoopGroup eventGroup, WebClientConfiguration configuration, - Http.RequestMethod method) { + Http.Method method) { this.properties = new HashMap<>(); this.eventGroup = eventGroup; this.configuration = configuration; @@ -151,10 +151,10 @@ private WebClientRequestBuilderImpl(NioEventLoopGroup eventGroup, this.uri = configuration.uri(); this.skipUriEncoding = false; this.allowChunkedEncoding = true; - this.path = ClientPath.create(null, "", new HashMap<>()); + this.path = UriPath.create(""); //Default headers added to the current headers of the request this.headers = new WebClientRequestHeadersImpl(this.configuration.headers()); - this.queryParams = new WebClientQueryParams(); + this.queryParams = UriQueryWriteable.create(); this.httpVersion = Http.Version.V1_1; this.redirectionCount = 0; this.services = configuration.clientServices(); @@ -174,7 +174,7 @@ private WebClientRequestBuilderImpl(NioEventLoopGroup eventGroup, static WebClientRequestBuilder create(NioEventLoopGroup eventGroup, WebClientConfiguration configuration, - Http.RequestMethod method) { + Http.Method method) { return new WebClientRequestBuilderImpl(eventGroup, configuration, method); } @@ -266,7 +266,6 @@ public WebClientRequestBuilder uri(URI uri) { @Override public WebClientRequestBuilder skipUriEncoding() { this.skipUriEncoding = true; - this.queryParams.skipEncoding(); return this; } @@ -295,7 +294,7 @@ public WebClientRequestHeaders headers() { @Override public WebClientRequestBuilder queryParam(String name, String... values) { - queryParams.add(name, values); + queryParams.set(name, values); return this; } @@ -322,9 +321,12 @@ public WebClientRequestBuilder headers(Function queryParam(name, params.toArray(new String[0]))); + for (String name : queryParams.names()) { + queryParam(name, queryParams.all(name).toArray(new String[0])); + } + return this; } @@ -353,26 +355,26 @@ public WebClientRequestBuilder fragment(String fragment) { } @Override - public WebClientRequestBuilder path(HttpRequest.Path path) { + public WebClientRequestBuilder path(UriPath path) { this.path = path; return this; } @Override public WebClientRequestBuilder path(String path) { - this.path = ClientPath.create(null, path, new HashMap<>()); + this.path = UriPath.create(path); return this; } @Override - public WebClientRequestBuilder contentType(MediaType contentType) { + public WebClientRequestBuilder contentType(HttpMediaType contentType) { this.headers.contentType(contentType); this.writerContext.contentType(contentType); return this; } @Override - public WebClientRequestBuilder accept(MediaType... mediaTypes) { + public WebClientRequestBuilder accept(HttpMediaType... mediaTypes) { Arrays.stream(mediaTypes).forEach(headers::addAccept); return this; } @@ -459,7 +461,7 @@ long requestId() { return requestId; } - Http.RequestMethod method() { + Http.Method method() { return method; } @@ -471,7 +473,7 @@ URI uri() { return finalUri; } - Parameters queryParams() { + UriQuery queryParams() { return queryParams; } @@ -480,27 +482,17 @@ String query() { } String queryFromParams() { - StringBuilder queries = new StringBuilder(); - for (Map.Entry> entry : queryParams.pickCorrectParameters().toMap().entrySet()) { - for (String value : entry.getValue()) { - if (queries.length() > 0) { - queries.append("&"); - } - if (entry.getKey().isEmpty()) { - queries.append(value); - } else { - queries.append(entry.getKey()).append("=").append(value); - } - } + if (skipUriEncoding) { + return queryParams.toString(); } - return queries.toString(); + return queryParams.rawValue(); } String fragment() { return fragment; } - HttpRequest.Path path() { + UriPath path() { return path; } @@ -693,7 +685,7 @@ private URI prepareFinalURI() { + "Please specify correct port to use."); } String path = resolvePath(); - this.path = ClientPath.create(null, path, new HashMap<>()); + this.path = UriPath.create(path); //We need null values for query and fragment if we dont want to have trailing ?# chars String query = resolveQuery(); String fragment = resolveFragment(); @@ -787,7 +779,7 @@ private String resolveQuery() { private String resolvePath() { String uriPath = uri.getRawPath(); - String extendedPath = this.path.toRawString(); + String extendedPath = this.path.rawPath(); String finalPath; if (uriPath.endsWith("/") && extendedPath.startsWith("/")) { finalPath = uriPath.substring(0, uriPath.length() - 1) + extendedPath; @@ -804,7 +796,7 @@ private String resolvePath() { return UriComponentEncoder.encode(finalPath, UriComponentEncoder.Type.PATH); } - private HttpMethod toNettyMethod(Http.RequestMethod method) { + private HttpMethod toNettyMethod(Http.Method method) { //This method creates also new netty HttpMethod. return HttpMethod.valueOf(method.name()); } @@ -817,15 +809,17 @@ private HttpHeaders toNettyHttpHeaders() { HttpHeaders headers = new DefaultHttpHeaders(this.configuration.validateHeaders()); try { Map> cookieHeaders = this.configuration.cookieManager().get(finalUri, new HashMap<>()); - List cookies = new ArrayList<>(cookieHeaders.get(Http.Header.COOKIE)); - cookies.addAll(this.headers.values(Http.Header.COOKIE)); + List cookies = new ArrayList<>(cookieHeaders.get(Http.Header.COOKIE.defaultCase())); + cookies.addAll(this.headers.all(Http.Header.COOKIE, List::of)); if (!cookies.isEmpty()) { - headers.add(Http.Header.COOKIE, String.join("; ", cookies)); + headers.set(HttpHeaderNames.COOKIE, String.join("; ", cookies)); } } catch (IOException e) { throw new WebClientException("An error occurred while setting cookies.", e); } - this.headers.toMap().forEach(headers::add); + for (Http.HeaderValue header : this.headers) { + headers.add(header.name(), header.allValues()); + } addHeaderIfAbsent(headers, HttpHeaderNames.HOST, finalUri.getHost() + ":" + finalUri.getPort()); addHeaderIfAbsent(headers, HttpHeaderNames.CONNECTION, keepAlive ? HttpHeaderValues.KEEP_ALIVE : HttpHeaderValues.CLOSE); addHeaderIfAbsent(headers, HttpHeaderNames.ACCEPT_ENCODING, HttpHeaderValues.GZIP); @@ -839,111 +833,6 @@ private void addHeaderIfAbsent(HttpHeaders headers, AsciiString header, Object h } } - /** - * {@link HttpRequest.Path} client implementation. - * Temporal implementation until {@link HttpRequest.Path} has implementation in common. - */ - private static class ClientPath implements HttpRequest.Path { - - private final String path; - private final String rawPath; - private final Map params; - private final ClientPath absolutePath; - private List segments; - - /** - * Creates new instance. - * - * @param path actual relative URI path. - * @param rawPath actual relative URI path without any decoding. - * @param params resolved path parameters. - * @param absolutePath absolute path. - */ - ClientPath(String path, String rawPath, Map params, - ClientPath absolutePath) { - - this.path = path; - this.rawPath = rawPath; - this.params = params == null ? Collections.emptyMap() : params; - this.absolutePath = absolutePath; - } - - @Override - public String param(String name) { - return params.get(name); - } - - @Override - public List segments() { - List result = segments; - // No synchronisation needed, worth case is multiple splitting. - if (result == null) { - StringTokenizer stok = new StringTokenizer(path, "/"); - result = new ArrayList<>(); - while (stok.hasMoreTokens()) { - result.add(stok.nextToken()); - } - this.segments = result; - } - return result; - } - - @Override - public String toString() { - return path; - } - - @Override - public String toRawString() { - return rawPath; - } - - @Override - public HttpRequest.Path absolute() { - return absolutePath == null ? this : absolutePath; - } - - static HttpRequest.Path create(ClientPath contextual, String path, - Map params) { - - return create(contextual, path, path, params); - } - - static HttpRequest.Path create(ClientPath contextual, String path, String rawPath, - Map params) { - - if (contextual == null) { - return new ClientPath(path, rawPath, params, null); - } else { - return contextual.createSubpath(path, rawPath, params); - } - } - - HttpRequest.Path createSubpath(String path, String rawPath, - Map params) { - - if (params == null) { - params = Collections.emptyMap(); - } - if (absolutePath == null) { - HashMap map = - new HashMap<>(this.params.size() + params.size()); - map.putAll(this.params); - map.putAll(params); - return new ClientPath(path, rawPath, params, new ClientPath(this.path, this.rawPath, map, null)); - } else { - int size = this.params.size() + params.size() - + absolutePath.params.size(); - HashMap map = new HashMap<>(size); - map.putAll(absolutePath.params); - map.putAll(this.params); - map.putAll(params); - return new ClientPath(path, rawPath, params, new ClientPath(absolutePath.path, absolutePath.rawPath, map, - /* absolute path */ null)); - } - } - } - private static class ChannelRecord { private final ChannelFuture channelFuture; diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeaders.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeaders.java index 67536e88220..c12089b8611 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeaders.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,23 +20,35 @@ import java.util.Optional; import io.helidon.common.http.Headers; +import io.helidon.common.http.HeadersClientRequest; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; +import io.helidon.common.http.HttpMediaType; /** * Headers that can be modified (until request is sent) for * outbound request. */ -public interface WebClientRequestHeaders extends Headers { +public interface WebClientRequestHeaders extends HeadersClientRequest { /** * Remove a header if set. * * @param name header name * @return updated headers instance + * @deprecated use {@link #unsetHeader(io.helidon.common.http.Http.HeaderName)} */ - WebClientRequestHeaders unsetHeader(String name); + @Deprecated + default WebClientRequestHeaders unsetHeader(String name) { + return unsetHeader(Http.Header.create(name)); + } + + /** + * Remove a header if set. + * + * @param name header name + * @return updated headers instance + */ + WebClientRequestHeaders unsetHeader(Http.HeaderName name); /** * Add a cookie to the request. @@ -55,7 +67,7 @@ public interface WebClientRequestHeaders extends Headers { * @param contentType content type of the request * @return updated headers instance */ - WebClientRequestHeaders contentType(MediaType contentType); + WebClientRequestHeaders contentType(HttpMediaType contentType); /** * Set a content length. This method is optional. @@ -67,13 +79,13 @@ public interface WebClientRequestHeaders extends Headers { WebClientRequestHeaders contentLength(long length); /** - * Add accepted {@link MediaType}. Supports quality factor and wildcards. + * Add accepted {@link io.helidon.common.http.HttpMediaType}. Supports quality factor and wildcards. * Ordered by invocation order. * * @param mediaType media type to accept, with optional quality factor * @return updated headers instance */ - WebClientRequestHeaders addAccept(MediaType mediaType); + WebClientRequestHeaders addAccept(HttpMediaType mediaType); /** * Sets {@link Http.Header#IF_MODIFIED_SINCE} header to specific time. @@ -124,46 +136,7 @@ public interface WebClientRequestHeaders extends Headers { WebClientRequestHeaders ifRange(String etag); /** - * Returns a list of acceptedTypes ({@value io.helidon.common.http.Http.Header#ACCEPT} header) content types in quality - * factor order. - * Never {@code null}. - * - * @return A list of acceptedTypes media types. - */ - List acceptedTypes(); - - /** - * Returns content type of the request. - * - * If there is no explicit content set, then {@link MediaType#WILDCARD} is returned. - * - * @return content type of the request - */ - MediaType contentType(); - - /** - * Returns content length if known. - * - * @return content length - */ - Optional contentLength(); - - /** - * Returns value of header {@value Http.Header#IF_MODIFIED_SINCE}. - * - * @return IF_MODIFIED_SINCE header value. - */ - Optional ifModifiedSince(); - - /** - * Returns value of header {@value Http.Header#IF_UNMODIFIED_SINCE}. - * - * @return IF_UNMODIFIED_SINCE header value. - */ - Optional ifUnmodifiedSince(); - - /** - * Returns value of header {@value Http.Header#IF_NONE_MATCH}. + * Returns value of header {@link Http.Header#IF_NONE_MATCH}. * * Empty {@link List} is returned if this header is not set. * @@ -172,7 +145,7 @@ public interface WebClientRequestHeaders extends Headers { List ifNoneMatch(); /** - * Returns value of header {@value Http.Header#IF_MATCH}. + * Returns value of header {@link Http.Header#IF_MATCH}. * * Empty {@link List} is returned if this header is not set. * @@ -181,14 +154,14 @@ public interface WebClientRequestHeaders extends Headers { List ifMatch(); /** - * Returns value of header {@value Http.Header#IF_RANGE} as a {@link ZonedDateTime}. + * Returns value of header {@link Http.Header#IF_RANGE} as a {@link ZonedDateTime}. * * @return formatted header IF_RANGE as ZonedDateTime */ Optional ifRangeDate(); /** - * Returns value of header {@value Http.Header#IF_RANGE} as a {@link String}. + * Returns value of header {@link Http.Header#IF_RANGE} as a {@link String}. * * @return formatted header IF_RANGE as String */ @@ -199,15 +172,42 @@ public interface WebClientRequestHeaders extends Headers { */ void clear(); - @Override - WebClientRequestHeaders putAll(Parameters parameters); + /** + * Add each header, replacing values if header already exists. + * + * @param parameters headers + * @return updated headers + */ + WebClientRequestHeaders putAll(Headers parameters); - @Override + /** + * Add header values. + * + * @param key header name + * @param values header values + * @return updated headers + * @deprecated use {@link #add(io.helidon.common.http.Http.HeaderName, String...)} + */ + @Deprecated WebClientRequestHeaders add(String key, String... values); - @Override - WebClientRequestHeaders add(String key, Iterable values); + /** + * Add header values. + * + * @param key header name + * @param values header values + * @return updated headers + */ + default WebClientRequestHeaders add(Http.HeaderName key, String... values) { + add(Http.HeaderValue.create(key, values)); + return this; + } - @Override - WebClientRequestHeaders addAll(Parameters parameters); + /** + * Add each header, combining values if header already exists. + * + * @param parameters headers + * @return updated headers + */ + WebClientRequestHeaders addAll(Headers parameters); } diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeadersImpl.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeadersImpl.java index 9265ef56bea..6aad324dd93 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeadersImpl.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeadersImpl.java @@ -19,22 +19,23 @@ import java.time.ZonedDateTime; import java.time.format.DateTimeFormatter; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; -import java.util.Map; import java.util.Optional; +import java.util.OptionalLong; import java.util.Set; -import java.util.concurrent.ConcurrentSkipListMap; -import java.util.function.Function; +import java.util.function.Consumer; +import java.util.function.Supplier; import java.util.stream.Collectors; -import java.util.stream.StreamSupport; +import io.helidon.common.http.Headers; +import io.helidon.common.http.HeadersClientRequest; +import io.helidon.common.http.HeadersWritable; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; +import io.helidon.common.http.Http.HeaderValue; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaType; import io.netty.handler.codec.http.cookie.ClientCookieEncoder; import io.netty.handler.codec.http.cookie.DefaultCookie; @@ -46,113 +47,118 @@ class WebClientRequestHeadersImpl implements WebClientRequestHeaders { private static final DateTimeFormatter FORMATTER = Http.DateTime.RFC_1123_DATE_TIME.withZone(ZoneId.of("GMT")); - private final Map> headers = new ConcurrentSkipListMap<>(String.CASE_INSENSITIVE_ORDER); + private final HeadersClientRequest headers = HeadersClientRequest.create(HeadersWritable.create()); WebClientRequestHeadersImpl() { } WebClientRequestHeadersImpl(WebClientRequestHeaders headers) { - headers.toMap().forEach((key, values) -> this.headers.computeIfAbsent(key, k -> new ArrayList<>()).addAll(values)); + for (HeaderValue header : headers) { + this.headers.add(header); + } } @Override - public WebClientRequestHeaders unsetHeader(String name) { + public WebClientRequestHeaders unsetHeader(Http.HeaderName name) { headers.remove(name); return this; } @Override public WebClientRequestHeaders addCookie(String name, String value) { - add(Http.Header.COOKIE, ClientCookieEncoder.STRICT.encode(new DefaultCookie(name, value))); + headers.add(HeaderValue.create(Http.Header.COOKIE, ClientCookieEncoder.STRICT.encode(new DefaultCookie(name, value)))); return this; } @Override public WebClientRequestHeaders contentType(MediaType contentType) { - put(Http.Header.CONTENT_TYPE, contentType.toString()); + headers.contentType(contentType); return this; } @Override public WebClientRequestHeaders contentLength(long length) { - put(Http.Header.CONTENT_LENGTH, Long.toString(length)); + headers.contentLength(length); return this; } @Override - public WebClientRequestHeaders addAccept(MediaType mediaType) { - add(Http.Header.ACCEPT, mediaType.toString()); + public WebClientRequestHeaders addAccept(HttpMediaType mediaType) { + headers.add(HeaderValue.create(Http.Header.ACCEPT, mediaType.text())); return this; } @Override public WebClientRequestHeaders ifModifiedSince(ZonedDateTime time) { - put(Http.Header.IF_MODIFIED_SINCE, time.format(FORMATTER)); + headers.set(HeaderValue.create(Http.Header.IF_MODIFIED_SINCE, + true, + false, + time.format(FORMATTER))); return this; } @Override public WebClientRequestHeaders ifUnmodifiedSince(ZonedDateTime time) { - put(Http.Header.IF_UNMODIFIED_SINCE, time.format(FORMATTER)); + headers.set(HeaderValue.create(Http.Header.IF_UNMODIFIED_SINCE, + true, + false, + time.format(FORMATTER))); return this; } @Override public WebClientRequestHeaders ifNoneMatch(String... etags) { - put(Http.Header.IF_NONE_MATCH, processEtags(etags)); + headers.set(HeaderValue.create(Http.Header.IF_NONE_MATCH, processEtags(etags))); return this; } @Override public WebClientRequestHeaders ifMatch(String... etags) { - put(Http.Header.IF_MATCH, processEtags(etags)); + headers.set(HeaderValue.create(Http.Header.IF_MATCH, processEtags(etags))); return this; } @Override public WebClientRequestHeaders ifRange(ZonedDateTime time) { - put(Http.Header.IF_RANGE, time.format(FORMATTER)); + headers.set(HeaderValue.create(Http.Header.IF_RANGE, time.format(FORMATTER))); return this; } @Override public WebClientRequestHeaders ifRange(String etag) { - put(Http.Header.IF_RANGE, processEtags(etag)); + headers.set(HeaderValue.create(Http.Header.IF_RANGE, processEtags(etag))); return this; } @Override - public List acceptedTypes() { - List mediaTypes = new ArrayList<>(); - headers.computeIfAbsent(Http.Header.ACCEPT, k -> new ArrayList<>()) - .forEach(s -> mediaTypes.add(MediaType.parse(s))); - return Collections.unmodifiableList(mediaTypes); + public List acceptedTypes() { + return headers.acceptedTypes(); } @Override - public MediaType contentType() { - List contentType = headers.computeIfAbsent(Http.Header.CONTENT_TYPE, k -> new ArrayList<>()); - return contentType.size() == 0 ? MediaType.WILDCARD : MediaType.parse(contentType.get(0)); + public Optional contentType() { + return headers.contentType(); } @Override - public Optional contentLength() { - return Optional.ofNullable(headers.get(Http.Header.CONTENT_LENGTH)).map(list -> Long.parseLong(list.get(0))); + public OptionalLong contentLength() { + return headers.contentLength(); } @Override public Optional ifModifiedSince() { - return parseToDate(Http.Header.IF_MODIFIED_SINCE); + return headers.ifModifiedSince(); } @Override public Optional ifUnmodifiedSince() { - return parseToDate(Http.Header.IF_UNMODIFIED_SINCE); + return headers.ifUnmodifiedSince(); } + @Override public List ifNoneMatch() { - return all(Http.Header.IF_NONE_MATCH) + return all(Http.Header.IF_NONE_MATCH, List::of) .stream() .map(this::unquoteETag) .collect(Collectors.toList()); @@ -160,7 +166,7 @@ public List ifNoneMatch() { @Override public List ifMatch() { - return all(Http.Header.IF_MATCH) + return all(Http.Header.IF_MATCH, List::of) .stream() .map(this::unquoteETag) .collect(Collectors.toList()); @@ -182,103 +188,105 @@ public void clear() { } @Override - public Optional first(String name) { - return Optional.ofNullable(headers.get(name)).map(list -> list.get(0)); + public List all(Http.HeaderName name, Supplier> defaultSupplier) { + return headers.all(name, defaultSupplier); } @Override - public List all(String headerName) { - return Collections.unmodifiableList(headers.getOrDefault(headerName, new ArrayList<>())); + public boolean contains(Http.HeaderName name) { + return headers.contains(name); } @Override - public List put(String key, String... values) { - List list = headers.put(key, Arrays.asList(values)); - return Collections.unmodifiableList(list == null ? new ArrayList<>() : list); + public boolean contains(HeaderValue value) { + return headers.contains(value); } @Override - public List put(String key, Iterable values) { - List list = headers.put(key, iterableToList(values)); - return Collections.unmodifiableList(list == null ? new ArrayList<>() : list); + public HeaderValue get(Http.HeaderName name) { + return headers.get(name); } @Override - public List putIfAbsent(String key, String... values) { - List list = headers.putIfAbsent(key, Arrays.asList(values)); - return Collections.unmodifiableList(list == null ? new ArrayList<>() : list); + public int size() { + return headers.size(); } @Override - public List putIfAbsent(String key, Iterable values) { - List list = headers.putIfAbsent(key, iterableToList(values)); - return Collections.unmodifiableList(list == null ? new ArrayList<>() : list); + public WebClientRequestHeaders setIfAbsent(HeaderValue header) { + headers.setIfAbsent(header); + return this; } @Override - public List computeIfAbsent(String key, Function> values) { - List associatedHeaders = headers.get(key); - if (associatedHeaders == null) { - return put(key, values.apply(key)); - } - return Collections.unmodifiableList(associatedHeaders); + public WebClientRequestHeaders add(HeaderValue header) { + headers.add(header); + return this; } @Override - public List computeSingleIfAbsent(String key, Function value) { - List associatedHeaders = headers.get(key); - if (associatedHeaders == null) { - return put(key, value.apply(key)); - } - return Collections.unmodifiableList(associatedHeaders); + public WebClientRequestHeaders remove(Http.HeaderName name) { + headers.remove(name); + return this; } @Override - public WebClientRequestHeaders putAll(Parameters parameters) { - headers.putAll(parameters.toMap()); + public WebClientRequestHeaders remove(Http.HeaderName name, Consumer removedConsumer) { + headers.remove(name, removedConsumer); return this; } @Override - public WebClientRequestHeaders add(String key, String... values) { - headers.computeIfAbsent(key, k -> new ArrayList<>()).addAll(Arrays.asList(values)); + public WebClientRequestHeaders set(HeaderValue header) { + headers.set(header); return this; } @Override - public WebClientRequestHeaders add(String key, Iterable values) { - headers.computeIfAbsent(key, k -> new ArrayList<>()).addAll(iterableToList(values)); + public WebClientRequestHeaders contentType(HttpMediaType contentType) { + headers.contentType(contentType); return this; } @Override - public WebClientRequestHeaders addAll(Parameters parameters) { - parameters.toMap().forEach(this::add); + public WebClientRequestHeaders putAll(Headers headers) { + for (HeaderValue header : headers) { + this.headers.set(header); + } return this; } @Override - public List remove(String key) { - List value = headers.remove(key); - return value == null ? new ArrayList<>() : value; + public Iterator iterator() { + return headers.iterator(); + } + + public Optional first(Http.HeaderName name) { + if (headers.contains(name)) { + return Optional.of(headers.get(name).value()); + } + return Optional.empty(); } @Override - public Map> toMap() { - return Collections.unmodifiableMap(new HashMap<>(headers)); + public WebClientRequestHeaders add(String key, String... values) { + headers.add(HeaderValue.create(Http.Header.create(key), values)); + return this; } - private List iterableToList(Iterable iterable) { - return StreamSupport - .stream(iterable.spliterator(), false) - .collect(Collectors.toList()); + @Override + public WebClientRequestHeaders addAll(Headers headers) { + for (HeaderValue header : headers) { + this.headers.add(header); + } + return this; } - private Optional parseToDate(String header) { + private Optional parseToDate(Http.HeaderName header) { return first(header).map(Http.DateTime::parse); } - private Iterable processEtags(String... etags) { + private List processEtags(String... etags) { Set set = new HashSet<>(); if (etags.length > 0) { if (etags.length == 1 && etags[0].equals("*")) { @@ -289,7 +297,7 @@ private Iterable processEtags(String... etags) { } } } - return set; + return new ArrayList<>(set); } private String unquoteETag(String etag) { diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestImpl.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestImpl.java index e6e7a105b3a..e73c9f19459 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestImpl.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ import java.util.Map; import io.helidon.common.http.Http; -import io.helidon.common.http.Parameters; -import io.helidon.common.http.ReadOnlyParameters; +import io.helidon.common.uri.UriPath; +import io.helidon.common.uri.UriQuery; /** * Implementation of client request. @@ -30,10 +30,10 @@ class WebClientRequestImpl implements WebClientRequestBuilder.ClientRequest { private final URI uri; private final RequestConfiguration requestConfiguration; private final WebClientRequestHeaders clientRequestHeaders; - private final Http.RequestMethod requestMethod; + private final Http.Method requestMethod; private final Http.Version httpVersion; - private final Parameters queryParams; - private final Path path; + private final UriQuery queryParams; + private final UriPath path; private final Proxy proxy; private final String query; private final String fragment; @@ -45,7 +45,7 @@ class WebClientRequestImpl implements WebClientRequestBuilder.ClientRequest { requestMethod = builder.method(); httpVersion = builder.httpVersion(); uri = builder.uri(); - queryParams = new ReadOnlyParameters(builder.queryParams()); + queryParams = builder.queryParams(); query = builder.query(); fragment = builder.fragment(); path = builder.path(); @@ -86,7 +86,7 @@ public int redirectionCount() { } @Override - public Http.RequestMethod method() { + public Http.Method method() { return requestMethod; } @@ -106,12 +106,12 @@ public String query() { } @Override - public Parameters queryParams() { + public UriQuery queryParams() { return queryParams; } @Override - public Path path() { + public UriPath path() { return path; } diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponse.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponse.java index 984ea984ed9..a20149a87e7 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponse.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,7 +33,7 @@ public interface WebClientResponse { * * @return HTTP status */ - Http.ResponseStatus status(); + Http.Status status(); /** * Content to access entity. diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseHeaders.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseHeaders.java index 6a0e28d12f5..901ffda7afc 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseHeaders.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,16 +19,17 @@ import java.time.ZonedDateTime; import java.util.List; import java.util.Optional; +import java.util.OptionalLong; -import io.helidon.common.http.Headers; +import io.helidon.common.http.HeadersClientResponse; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.http.SetCookie; /** * Headers that may be available on response from server. */ -public interface WebClientResponseHeaders extends Headers { +public interface WebClientResponseHeaders extends HeadersClientResponse { /** * Returns {@link SetCookie} header of the response. @@ -38,28 +39,28 @@ public interface WebClientResponseHeaders extends Headers { List setCookies(); /** - * Returns {@link URI} representation of {@value Http.Header#LOCATION} header from the response. + * Returns {@link URI} representation of {@link Http.Header#LOCATION} header from the response. * * @return location header uri */ Optional location(); /** - * Returns value of header {@value Http.Header#LAST_MODIFIED} of the response. + * Returns value of header {@link Http.Header#LAST_MODIFIED} of the response. * * @return LAST_MODIFIED header value. */ Optional lastModified(); /** - * Returns value of header {@value Http.Header#EXPIRES} of the response. + * Returns value of header {@link Http.Header#EXPIRES} of the response. * * @return EXPIRES header value. */ Optional expires(); /** - * Returns value of header {@value Http.Header#DATE} of the response. + * Returns value of header {@link Http.Header#DATE} of the response. * * @return DATE header value. */ @@ -70,10 +71,10 @@ public interface WebClientResponseHeaders extends Headers { * * @return content type of the response */ - Optional contentType(); + Optional contentType(); /** - * Returns value of header {@value Http.Header#ETAG} of the response. + * Returns value of header {@link Http.Header#ETAG} of the response. * * @return ETAG header value. */ @@ -84,7 +85,7 @@ public interface WebClientResponseHeaders extends Headers { * * @return content length */ - Optional contentLength(); + OptionalLong contentLength(); /** * Transfer encoding of the response. diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseHeadersImpl.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseHeadersImpl.java index a0b5ef1a15f..2f4f6e851dc 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseHeadersImpl.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseHeadersImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,34 +17,27 @@ import java.net.URI; import java.time.ZonedDateTime; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.OptionalLong; +import java.util.function.Supplier; import java.util.stream.Collectors; +import io.helidon.common.http.Headers; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; -import io.helidon.common.http.ReadOnlyParameters; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.http.SetCookie; /** * Implementation of {@link WebClientResponseHeaders}. */ -class WebClientResponseHeadersImpl extends ReadOnlyParameters implements WebClientResponseHeaders { +class WebClientResponseHeadersImpl implements WebClientResponseHeaders { + private final Headers headers; - private WebClientResponseHeadersImpl(Map> headers) { - super(headers); - } - - /** - * Creates {@link WebClientResponseHeaders} instance which contains data from {@link Parameters} instance. - * - * @param parameters headers in parameters instance - * @return response headers instance - */ - static WebClientResponseHeaders create(Parameters parameters) { - return create(parameters.toMap()); + private WebClientResponseHeadersImpl(Headers headers) { + this.headers = headers; } /** @@ -53,13 +46,13 @@ static WebClientResponseHeaders create(Parameters parameters) { * @param headers response headers in map * @return response headers instance */ - static WebClientResponseHeadersImpl create(Map> headers) { + static WebClientResponseHeadersImpl create(Headers headers) { return new WebClientResponseHeadersImpl(headers); } @Override public List setCookies() { - return all(Http.Header.SET_COOKIE) + return all(Http.Header.SET_COOKIE, List::of) .stream() .map(SetCookie::parse) .collect(Collectors.toList()); @@ -86,8 +79,8 @@ public Optional date() { } @Override - public Optional contentType() { - return first(Http.Header.CONTENT_TYPE).map(MediaType::parse); + public Optional contentType() { + return first(Http.Header.CONTENT_TYPE).map(HttpMediaType::create); } @Override @@ -96,15 +89,50 @@ public Optional etag() { } @Override - public Optional contentLength() { - return first(Http.Header.CONTENT_LENGTH) - .map(Long::parseLong) - .or(Optional::empty); + public OptionalLong contentLength() { + return headers.contentLength(); } @Override public List transferEncoding() { - return all(Http.Header.TRANSFER_ENCODING); + return all(Http.Header.TRANSFER_ENCODING, List::of); + } + + @Override + public List all(Http.HeaderName name, Supplier> defaultSupplier) { + return headers.all(name, defaultSupplier); + } + + @Override + public boolean contains(Http.HeaderName name) { + return headers.contains(name); + } + + @Override + public boolean contains(Http.HeaderValue value) { + return headers.contains(value); + } + + @Override + public Http.HeaderValue get(Http.HeaderName name) { + return headers.get(name); + } + + @Override + public int size() { + return headers.size(); + } + + @Override + public Iterator iterator() { + return headers.iterator(); + } + + private Optional first(Http.HeaderName headerName) { + if (headers.contains(headerName)) { + return Optional.of(headers.get(headerName).value()); + } + return Optional.empty(); } private String unquoteETag(String etag) { diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseImpl.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseImpl.java index 7a3db937f28..b593bfefc30 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseImpl.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,16 +16,14 @@ package io.helidon.webclient; import java.net.URI; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.concurrent.Flow; -import java.util.logging.Logger; import io.helidon.common.http.DataChunk; +import io.helidon.common.http.HeadersWritable; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyReadableContent; import io.helidon.media.common.MessageBodyReaderContext; @@ -35,11 +33,9 @@ */ final class WebClientResponseImpl implements WebClientResponse { - private static final Logger LOGGER = Logger.getLogger(NettyClientHandler.class.getName()); - private final WebClientResponseHeadersImpl headers; private final Flow.Publisher publisher; - private final Http.ResponseStatus status; + private final Http.Status status; private final Http.Version version; private final MessageBodyReaderContext readerContext; private final NettyClientHandler.ResponseCloser responseCloser; @@ -65,7 +61,7 @@ public static Builder builder() { } @Override - public Http.ResponseStatus status() { + public Http.Status status() { return status; } @@ -86,7 +82,7 @@ public Single close() { @Override public MessageBodyReadableContent content() { - Optional mediaType = headers.contentType(); + Optional mediaType = headers.contentType(); MessageBodyReaderContext readerContext = MessageBodyReaderContext.create(this.readerContext, null, headers, mediaType); return MessageBodyReadableContent.create(publisher, readerContext); } @@ -101,10 +97,10 @@ public WebClientResponseHeaders headers() { */ static class Builder implements io.helidon.common.Builder { - private final Map> headers = new HashMap<>(); + private final HeadersWritable headers = HeadersWritable.create(); private Flow.Publisher publisher; - private Http.ResponseStatus status = Http.Status.INTERNAL_SERVER_ERROR_500; + private Http.Status status = Http.Status.INTERNAL_SERVER_ERROR_500; private Http.Version version = Http.Version.V1_1; private NettyClientHandler.ResponseCloser responseCloser; private MessageBodyReaderContext readerContext; @@ -143,7 +139,7 @@ Builder readerContext(MessageBodyReaderContext readerContext) { * @param status response status code * @return updated builder instance */ - Builder status(Http.ResponseStatus status) { + Builder status(Http.Status status) { this.status = status; return this; } @@ -167,7 +163,7 @@ Builder httpVersion(Http.Version version) { * @return updated builder instance */ Builder addHeader(String name, List values) { - this.headers.put(name, values); + this.headers.set(Http.HeaderValue.create(Http.Header.create(name), values)); return this; } diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequest.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequest.java index e9d5db1f4f0..e40781568ff 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequest.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,82 @@ */ package io.helidon.webclient; +import java.net.URI; import java.util.Map; import io.helidon.common.context.Context; -import io.helidon.common.http.HttpRequest; +import io.helidon.common.http.Http; import io.helidon.common.reactive.Single; +import io.helidon.common.uri.UriPath; +import io.helidon.common.uri.UriQuery; import io.helidon.webclient.spi.WebClientService; /** * Request to SPI {@link WebClientService} that supports modification of the outgoing request. */ -public interface WebClientServiceRequest extends HttpRequest { +public interface WebClientServiceRequest { + /** + * Returns an HTTP request method. See also {@link io.helidon.common.http.Http.Method HTTP standard methods} utility class. + * + * @return an HTTP method + * @see io.helidon.common.http.Http.Method + */ + Http.Method method(); + + /** + * Returns an HTTP version from the request line. + *

+ * See {@link Http.Version HTTP Version} enumeration for supported versions. + *

+ * If communication starts as a {@code HTTP/1.1} with {@code h2c} upgrade, then it will be automatically + * upgraded and this method returns {@code HTTP/2.0}. + * + * @return an HTTP version + */ + Http.Version version(); + + /** + * Returns a Request-URI (or alternatively path) as defined in request line. + * + * @return a request URI + */ + URI uri(); + + /** + * Returns an encoded query string without leading '?' character. + * + * @return an encoded query string + */ + String query(); + + /** + * Returns query parameters. + * + * @return an parameters representing query parameters + */ + UriQuery queryParams(); + + /** + * Returns a path which was accepted by matcher in actual routing. It is path without a context root + * of the routing. + *

+ * Use {@link io.helidon.common.uri.UriPath#absolute()} method to obtain absolute request URI path representation. + *

+ * Returned {@link io.helidon.common.uri.UriPath} also provides access to path template parameters. + * An absolute path then provides access to + * all (including) context parameters if any. In case of conflict between parameter names, most recent value is returned. + * + * @return a path + */ + UriPath path(); + + /** + * Returns a decoded request URI fragment without leading hash '#' character. + * + * @return a decoded URI fragment + */ + String fragment(); + /** * Configured request headers. * diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequestImpl.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequestImpl.java index 316ea24967a..9e325342f56 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequestImpl.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequestImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,9 @@ import io.helidon.common.context.Context; import io.helidon.common.http.Http; -import io.helidon.common.http.Parameters; import io.helidon.common.reactive.Single; +import io.helidon.common.uri.UriPath; +import io.helidon.common.uri.UriQuery; /** * Implementation of the {@link WebClientServiceRequest} interface. @@ -30,7 +31,7 @@ class WebClientServiceRequestImpl implements WebClientServiceRequest { private final WebClientRequestHeaders headers; - private final Http.RequestMethod method; + private final Http.Method method; private final Http.Version version; private final Map parameters; private final CompletableFuture sent; @@ -125,7 +126,7 @@ public void fragment(String fragment) { } @Override - public Http.RequestMethod method() { + public Http.Method method() { return method; } @@ -145,12 +146,12 @@ public String query() { } @Override - public Parameters queryParams() { + public UriQuery queryParams() { return requestBuilder.queryParams(); } @Override - public Path path() { + public UriPath path() { return requestBuilder.path(); } diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceResponse.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceResponse.java index 3a9b7929f74..aadf2d3b31d 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceResponse.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,6 +42,6 @@ public interface WebClientServiceResponse { * * @return response status */ - Http.ResponseStatus status(); + Http.Status status(); } diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceResponseImpl.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceResponseImpl.java index 08887273ebb..06d0949df85 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceResponseImpl.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceResponseImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,9 +25,9 @@ class WebClientServiceResponseImpl implements WebClientServiceResponse { private final Context context; private final WebClientResponseHeaders headers; - private final Http.ResponseStatus status; + private final Http.Status status; - WebClientServiceResponseImpl(Context context, WebClientResponseHeaders headers, Http.ResponseStatus status) { + WebClientServiceResponseImpl(Context context, WebClientResponseHeaders headers, Http.Status status) { this.context = context; this.headers = headers; this.status = status; @@ -44,7 +44,7 @@ public Context context() { } @Override - public Http.ResponseStatus status() { + public Http.Status status() { return status; } diff --git a/webclient/webclient/src/main/java/module-info.java b/webclient/webclient/src/main/java/module-info.java index c435328323d..bd1f48d3bf3 100644 --- a/webclient/webclient/src/main/java/module-info.java +++ b/webclient/webclient/src/main/java/module-info.java @@ -27,6 +27,9 @@ requires transitive io.helidon.common.reactive; requires transitive io.helidon.config; requires transitive io.helidon.media.common; + requires transitive io.helidon.common.reactive; + requires transitive io.helidon.common.parameters; + requires io.helidon.common.pki; requires io.netty.buffer; diff --git a/webclient/webclient/src/test/java/io/helidon/webclient/ClientRequestHeadersImplTest.java b/webclient/webclient/src/test/java/io/helidon/webclient/ClientRequestHeadersImplTest.java index 947cfeda41c..28f908064a5 100644 --- a/webclient/webclient/src/test/java/io/helidon/webclient/ClientRequestHeadersImplTest.java +++ b/webclient/webclient/src/test/java/io/helidon/webclient/ClientRequestHeadersImplTest.java @@ -21,59 +21,76 @@ import java.util.Collections; import java.util.List; import java.util.Optional; +import java.util.OptionalLong; -import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.Http.Header; +import io.helidon.common.http.Http.HeaderValue; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static io.helidon.common.http.Http.Header.CONTENT_TYPE; +import static io.helidon.common.http.Http.Header.IF_MATCH; +import static io.helidon.common.http.Http.Header.IF_MODIFIED_SINCE; +import static io.helidon.common.http.Http.Header.IF_NONE_MATCH; +import static io.helidon.common.http.Http.Header.IF_UNMODIFIED_SINCE; +import static io.helidon.common.testing.OptionalMatcher.optionalEmpty; +import static io.helidon.common.testing.OptionalMatcher.optionalValue; +import static io.helidon.common.testing.http.HttpHeaderMatcher.hasHeader; +import static io.helidon.common.testing.http.HttpHeaderMatcher.noHeader; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; -public class ClientRequestHeadersImplTest { +class ClientRequestHeadersImplTest { + private static final HttpMediaType YAML = HttpMediaType.create(MediaTypes.APPLICATION_YAML); + private static final HttpMediaType XML = HttpMediaType.create(MediaTypes.APPLICATION_XML); + private static final HttpMediaType JSON = HttpMediaType.create(MediaTypes.APPLICATION_JSON); + private static final HttpMediaType TEXT = HttpMediaType.create(MediaTypes.TEXT_PLAIN); private WebClientRequestHeaders clientRequestHeaders; @BeforeEach - public void beforeEach() { + void beforeEach() { clientRequestHeaders = new WebClientRequestHeadersImpl(); } @Test - public void testAcceptedTypes() { - List expectedTypes = List.of(MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON); + void testAcceptedTypes() { + List expectedTypes = List.of(HttpMediaType.PLAINTEXT_UTF_8, HttpMediaType.JSON_UTF_8); - clientRequestHeaders.addAccept(MediaType.TEXT_PLAIN); - clientRequestHeaders.add(Http.Header.ACCEPT, MediaType.APPLICATION_JSON.toString()); + clientRequestHeaders.addAccept(HttpMediaType.PLAINTEXT_UTF_8); + clientRequestHeaders.add(HeaderValue.create(Header.ACCEPT, HttpMediaType.JSON_UTF_8.text())); assertThat(clientRequestHeaders.acceptedTypes(), is(expectedTypes)); } @Test - public void testContentType() { - assertThat(clientRequestHeaders.contentType(), is(MediaType.WILDCARD)); + void testContentType() { + assertThat(clientRequestHeaders.contentType(), optionalEmpty()); - clientRequestHeaders.add(Http.Header.CONTENT_TYPE, MediaType.APPLICATION_XML.toString()); - assertThat(clientRequestHeaders.contentType(), is(MediaType.APPLICATION_XML)); + clientRequestHeaders.add(CONTENT_TYPE, HttpMediaType.create(MediaTypes.APPLICATION_XML).text()); + assertThat(clientRequestHeaders.contentType(), optionalValue(is(XML))); - clientRequestHeaders.contentType(MediaType.APPLICATION_JSON); - assertThat(clientRequestHeaders.contentType(), is(MediaType.APPLICATION_JSON)); + clientRequestHeaders.contentType(MediaTypes.APPLICATION_JSON); + assertThat(clientRequestHeaders.contentType(), optionalValue(is(JSON))); } @Test - public void testContentLength() { + void testContentLength() { long contentLengthTemplate = 123; - assertThat(clientRequestHeaders.contentLength(), is(Optional.empty())); + assertThat(clientRequestHeaders, noHeader(Header.CONTENT_LENGTH)); + assertThat(clientRequestHeaders.contentLength(), is(OptionalLong.empty())); clientRequestHeaders.contentLength(contentLengthTemplate); - assertThat(clientRequestHeaders.contentLength(), is(Optional.of(contentLengthTemplate))); + assertThat(clientRequestHeaders.contentLength(), is(OptionalLong.of(contentLengthTemplate))); } @Test - public void testIfModifiedSince() { + void testIfModifiedSince() { String template = "Mon, 30 Nov 2015 22:45:59 GMT"; ZonedDateTime zonedDateTemplate = ZonedDateTime.of(2015, 11, 30, 22, 45, 59, 0, ZoneId.of("Z")); @@ -84,12 +101,12 @@ public void testIfModifiedSince() { clientRequestHeaders.ifModifiedSince(ifModifiedSince); - assertThat(clientRequestHeaders.first(Http.Header.IF_MODIFIED_SINCE), is(Optional.of(template))); + assertThat(clientRequestHeaders, hasHeader(HeaderValue.create(IF_MODIFIED_SINCE, template))); assertThat(clientRequestHeaders.ifModifiedSince(), is(Optional.of(zonedDateTemplate))); } @Test - public void testIfUnmodifiedSince() { + void testIfUnmodifiedSince() { String template = "Mon, 30 Nov 2015 22:45:59 GMT"; ZonedDateTime zonedDateTemplate = ZonedDateTime.of(2015, 11, 30, 22, 45, 59, 0, ZoneId.of("Z")); @@ -100,12 +117,12 @@ public void testIfUnmodifiedSince() { clientRequestHeaders.ifUnmodifiedSince(ifUnmodifiedSince); - assertThat(clientRequestHeaders.first(Http.Header.IF_UNMODIFIED_SINCE), is(Optional.of(template))); + assertThat(clientRequestHeaders, hasHeader(HeaderValue.create(IF_UNMODIFIED_SINCE, template))); assertThat(clientRequestHeaders.ifUnmodifiedSince(), is(Optional.of(zonedDateTemplate))); } @Test - public void testIfNoneMatch() { + void testIfNoneMatch() { List unquotedTemplate = List.of("test", "test2"); List quotedTemplate = List.of("\"test\"", "\"test2\""); List star = List.of("*"); @@ -114,15 +131,15 @@ public void testIfNoneMatch() { clientRequestHeaders.ifNoneMatch(unquotedTemplate.toArray(new String[0])); assertThat(clientRequestHeaders.ifNoneMatch(), is(unquotedTemplate)); - assertThat(clientRequestHeaders.all(Http.Header.IF_NONE_MATCH), is(quotedTemplate)); + assertThat(clientRequestHeaders, hasHeader(HeaderValue.create(IF_NONE_MATCH, quotedTemplate))); clientRequestHeaders.ifNoneMatch(star.toArray(new String[0])); assertThat(clientRequestHeaders.ifNoneMatch(), is(star)); - assertThat(clientRequestHeaders.all(Http.Header.IF_NONE_MATCH), is(star)); + assertThat(clientRequestHeaders, hasHeader(HeaderValue.create(IF_NONE_MATCH, star))); } @Test - public void testIfMatch() { + void testIfMatch() { List unquotedTemplate = List.of("test", "test2"); List quotedTemplate = List.of("\"test\"", "\"test2\""); List star = List.of("*"); @@ -131,15 +148,15 @@ public void testIfMatch() { clientRequestHeaders.ifMatch(unquotedTemplate.toArray(new String[0])); assertThat(clientRequestHeaders.ifMatch(), is(unquotedTemplate)); - assertThat(clientRequestHeaders.all(Http.Header.IF_MATCH), is(quotedTemplate)); + assertThat(clientRequestHeaders, hasHeader(HeaderValue.create(IF_MATCH, quotedTemplate))); clientRequestHeaders.ifMatch(star.toArray(new String[0])); assertThat(clientRequestHeaders.ifMatch(), is(star)); - assertThat(clientRequestHeaders.all(Http.Header.IF_MATCH), is(star)); + assertThat(clientRequestHeaders, hasHeader(HeaderValue.create(IF_MATCH, star))); } @Test - public void testIfRange() { + void testIfRange() { String template = "Mon, 30 Nov 2015 22:45:59 GMT"; String templateString = "testString"; ZonedDateTime zonedDateTemplate = @@ -159,22 +176,22 @@ public void testIfRange() { } @Test - public void testCaseInsensitivity() { - clientRequestHeaders.contentType(MediaType.APPLICATION_XML); - assertThat(clientRequestHeaders.contentType(), is(MediaType.APPLICATION_XML)); - clientRequestHeaders.put(Http.Header.CONTENT_TYPE.toLowerCase(), MediaType.APPLICATION_JSON.toString()); - assertThat(clientRequestHeaders.contentType(), is(MediaType.APPLICATION_JSON)); - clientRequestHeaders.put("CoNtEnT-TyPe", MediaType.APPLICATION_YAML.toString()); - assertThat(clientRequestHeaders.contentType(), is(MediaType.APPLICATION_YAML)); + void testCaseInsensitivity() { + clientRequestHeaders.contentType(MediaTypes.APPLICATION_XML); + assertThat(clientRequestHeaders.contentType(), optionalValue(is(XML))); + clientRequestHeaders.set(Header.create(CONTENT_TYPE.lowerCase()), MediaTypes.APPLICATION_JSON.text()); + assertThat(clientRequestHeaders.contentType(), optionalValue(is(JSON))); + clientRequestHeaders.set(Header.create("CoNtEnT-TyPe"), MediaTypes.APPLICATION_YAML.text()); + assertThat(clientRequestHeaders.contentType(), optionalValue(is(YAML))); } @Test - public void testCopyHeaders() { - clientRequestHeaders.contentType(MediaType.APPLICATION_XML); - clientRequestHeaders.put(Http.Header.CONTENT_TYPE.toLowerCase(), MediaType.APPLICATION_JSON.toString()); - clientRequestHeaders.put("CoNtEnT-TyPe", MediaType.APPLICATION_YAML.toString()); + void testCopyHeaders() { + clientRequestHeaders.contentType(MediaTypes.APPLICATION_XML); + clientRequestHeaders.set(Header.create(CONTENT_TYPE.lowerCase()), MediaTypes.APPLICATION_JSON.text()); + clientRequestHeaders.set(Header.create("CoNtEnT-TyPe"), MediaTypes.APPLICATION_YAML.text()); WebClientRequestHeaders copy = new WebClientRequestHeadersImpl(clientRequestHeaders); - assertThat(copy.contentType(), is(MediaType.APPLICATION_YAML)); + assertThat(copy.contentType(), optionalValue(is(YAML))); } } diff --git a/webclient/webclient/src/test/java/io/helidon/webclient/ConfigurationTest.java b/webclient/webclient/src/test/java/io/helidon/webclient/ConfigurationTest.java index bd8e73ea66e..0866c6ee1b1 100644 --- a/webclient/webclient/src/test/java/io/helidon/webclient/ConfigurationTest.java +++ b/webclient/webclient/src/test/java/io/helidon/webclient/ConfigurationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,11 +21,14 @@ import java.util.List; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import org.junit.jupiter.api.Test; +import static io.helidon.common.media.type.MediaTypes.APPLICATION_JSON; +import static io.helidon.common.media.type.MediaTypes.TEXT_PLAIN; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; import static org.hamcrest.Matchers.is; @@ -33,10 +36,10 @@ /** * Unit test for {@link WebClientConfiguration}. */ -public class ConfigurationTest { +class ConfigurationTest { @Test - public void testWebClientConfigurationFromConfig() { + void testWebClientConfigurationFromConfig() { Config config = Config.create(); WebClientConfiguration wcc = WebClientConfiguration.builder() .config(config.get("test-client")) @@ -45,7 +48,7 @@ public void testWebClientConfigurationFromConfig() { } @Test - public void testWebClientConfigurationFromBuilder() { + void testWebClientConfigurationFromBuilder() { WebClientConfiguration wcc = WebClientConfiguration.builder() .uri(URI.create("http://some.address:80")) .connectTimeout(Duration.of(4000, ChronoUnit.MILLIS)) @@ -53,7 +56,7 @@ public void testWebClientConfigurationFromBuilder() { .followRedirects(true) .maxRedirects(10) .userAgent("HelidonTest") - .defaultHeader(Http.Header.ACCEPT, List.of("application/json", "text/plain")) + .defaultHeader(Http.HeaderValue.create(Http.Header.ACCEPT, List.of("application/json", "text/plain"))) .build(); validateConfiguration(wcc); } @@ -65,7 +68,8 @@ private void validateConfiguration(WebClientConfiguration wcc) { assertThat(wcc.followRedirects(), is(true)); assertThat(wcc.maxRedirects(), is(10)); assertThat(wcc.userAgent(), is("HelidonTest")); - assertThat(wcc.headers().acceptedTypes(), containsInAnyOrder(MediaType.APPLICATION_JSON, MediaType.TEXT_PLAIN)); + assertThat(wcc.headers().acceptedTypes(), containsInAnyOrder(HttpMediaType.create(APPLICATION_JSON), + HttpMediaType.create(TEXT_PLAIN))); } } diff --git a/webclient/webclient/src/test/java/io/helidon/webclient/OrderOfWritesTest.java b/webclient/webclient/src/test/java/io/helidon/webclient/OrderOfWritesTest.java index 36f8253c557..ece7036eaf9 100644 --- a/webclient/webclient/src/test/java/io/helidon/webclient/OrderOfWritesTest.java +++ b/webclient/webclient/src/test/java/io/helidon/webclient/OrderOfWritesTest.java @@ -37,7 +37,7 @@ import com.sun.net.httpserver.HttpServer; -public class OrderOfWritesTest { +class OrderOfWritesTest { private static final Duration TIME_OUT = Duration.ofSeconds(5); @Test diff --git a/webclient/webclient/src/test/java/io/helidon/webclient/WebClientTlsTest.java b/webclient/webclient/src/test/java/io/helidon/webclient/WebClientTlsTest.java index f4b667ebb93..4b0a259324d 100644 --- a/webclient/webclient/src/test/java/io/helidon/webclient/WebClientTlsTest.java +++ b/webclient/webclient/src/test/java/io/helidon/webclient/WebClientTlsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,13 +26,10 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -/** - * TODO Javadoc - */ -public class WebClientTlsTest { +class WebClientTlsTest { @Test - public void sslDefaults() { + void sslDefaults() { WebClientTls webClientTls = WebClientTls.builder().build(); assertThat(webClientTls.disableHostnameVerification(), is(false)); @@ -45,7 +42,7 @@ public void sslDefaults() { } //@Test - public void sslFromConfig() { + void sslFromConfig() { Config config = Config.builder() .disableSystemPropertiesSource() .disableEnvironmentVariablesSource() From e0a1e18b6e09bf1b5b9e9ffc0d3f1fed0dc14c56 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:49:49 +0200 Subject: [PATCH 16/54] WebServer refactored to new common. --- .../accesslog/AccessLogSupportTest.java | 15 +- .../webserver/cors/CorsSupportBase.java | 13 +- .../webserver/cors/CorsSupportHelper.java | 26 +- .../io/helidon/webserver/cors/LogHelper.java | 16 +- .../webserver/cors/RequestAdapterSe.java | 17 +- .../webserver/cors/ResponseAdapterSe.java | 8 +- .../webserver/cors/AbstractCorsTest.java | 63 ++-- .../io/helidon/webserver/cors/CorsTest.java | 13 +- .../cors/TestDefaultCorsSupport.java | 23 +- .../cors/TestHandlerRegistration.java | 16 +- .../webserver/cors/TestTwoCorsConfigs.java | 4 +- webserver/jersey/pom.xml | 9 + .../webserver/jersey/JerseySupport.java | 5 +- .../webserver/jersey/ResponseWriter.java | 6 +- .../webserver/jersey/JerseySupportTest.java | 8 +- .../test/resources/logging-test.properties | 2 +- .../ClassPathContentHandler.java | 8 +- .../FileBasedContentHandler.java | 16 +- .../FileSystemContentHandler.java | 6 +- .../staticcontent/StaticContentHandler.java | 14 +- .../staticcontent/StaticContentSupport.java | 7 +- .../StaticContentHandlerTest.java | 16 +- .../webserver/testsupport/MediaPublisher.java | 16 +- .../testsupport/RequestHeadersMap.java | 128 +++++++ .../webserver/testsupport/TestClient.java | 20 +- .../webserver/testsupport/TestRequest.java | 10 +- .../webserver/testsupport/TestResponse.java | 24 +- .../webserver/testsupport/TestClientTest.java | 10 +- .../helidon/webserver/tyrus/TyrusSupport.java | 318 ++++++++++++++++++ webserver/webserver/pom.xml | 174 +++++----- .../webserver/AlreadyCompletedException.java | 55 +++ .../io/helidon/webserver/BareRequest.java | 8 +- .../io/helidon/webserver/BareRequestImpl.java | 18 +- .../io/helidon/webserver/BareResponse.java | 6 +- .../helidon/webserver/BareResponseImpl.java | 5 +- .../io/helidon/webserver/DirectHandler.java | 24 +- .../io/helidon/webserver/DirectHandlers.java | 4 +- .../helidon/webserver/ForwardingHandler.java | 6 +- .../io/helidon/webserver/HandlerRoute.java | 24 +- .../helidon/webserver/HashRequestHeaders.java | 263 --------------- .../webserver/HashResponseHeaders.java | 276 +++++---------- .../io/helidon/webserver/HttpException.java | 10 +- .../io/helidon/webserver/JsonService.java | 27 +- .../webserver/NettyRequestHeaders.java | 77 +++++ .../io/helidon/webserver/NettyWebServer.java | 4 +- .../java/io/helidon/webserver/Request.java | 19 +- .../io/helidon/webserver/RequestHeaders.java | 119 +------ .../helidon/webserver/RequestPredicate.java | 25 +- .../io/helidon/webserver/RequestRouting.java | 16 +- .../java/io/helidon/webserver/Response.java | 14 +- .../io/helidon/webserver/ResponseHeaders.java | 205 +---------- .../main/java/io/helidon/webserver/Route.java | 68 +--- .../java/io/helidon/webserver/RouteList.java | 14 +- .../webserver/RouteListRoutingRules.java | 12 +- .../java/io/helidon/webserver/Routing.java | 12 +- .../helidon/webserver/ServerBasicConfig.java | 43 --- .../webserver/ServerConfiguration.java | 4 +- .../io/helidon/webserver/ServerRequest.java | 113 ++++++- .../io/helidon/webserver/ServerResponse.java | 148 ++------ .../webserver/SocketConfiguration.java | 38 --- .../java/io/helidon/webserver/WebServer.java | 6 +- .../helidon/webserver/WebTracingConfig.java | 6 +- .../webserver/src/main/java/module-info.java | 1 + .../io/helidon/webserver/BytesReuseTest.java | 6 +- .../webserver/ContextLifeCycleTest.java | 8 +- .../helidon/webserver/CookieParserTest.java | 73 ---- .../io/helidon/webserver/FactoryTest.java | 4 +- .../java/io/helidon/webserver/FilterTest.java | 2 +- .../webserver/FormParamsSupportTest.java | 14 +- .../io/helidon/webserver/Gh1893V2ApiTest.java | 6 +- .../helidon/webserver/HandlerRouteTest.java | 20 +- .../helidon/webserver/HashParametersTest.java | 299 ---------------- .../webserver/HashRequestHeadersTest.java | 193 ----------- .../webserver/HashResponseHeadersTest.java | 107 +++--- .../io/helidon/webserver/KeepAliveTest.java | 14 +- .../helidon/webserver/MaxPayloadSizeTest.java | 14 +- .../io/helidon/webserver/MultiPortTest.java | 10 +- .../webserver/NettyRequestHeadersTest.java | 192 +++++++++++ .../helidon/webserver/OrderOfWritesTest.java | 2 +- .../java/io/helidon/webserver/PlainTest.java | 9 +- .../helidon/webserver/ReasonPhraseTest.java | 10 +- .../helidon/webserver/RequestContentTest.java | 250 +++++++++----- .../webserver/RequestPredicateTest.java | 157 +++++---- .../io/helidon/webserver/RequestTest.java | 4 +- .../io/helidon/webserver/RequestTestStub.java | 2 +- .../webserver/ResponseOrderingTest.java | 4 +- .../io/helidon/webserver/ResponseTest.java | 34 +- .../io/helidon/webserver/RouteListTest.java | 20 +- .../webserver/ServerConfigurationTest.java | 16 +- .../webserver/ServerRequestReaderTest.java | 72 ---- .../io/helidon/webserver/StatusTypeTest.java | 14 +- .../webserver/TestHttpParsingDefaults.java | 14 +- .../webserver/TestNettyRejectRequest.java | 4 +- .../webserver/TransferEncodingTest.java | 9 +- .../webserver/utils/SocketHttpClient.java | 16 +- .../test/resources/logging-test.properties | 4 +- .../src/test/resources/logging.properties | 6 +- 97 files changed, 1848 insertions(+), 2442 deletions(-) create mode 100644 webserver/test-support/src/main/java/io/helidon/webserver/testsupport/RequestHeadersMap.java create mode 100644 webserver/tyrus/src/main/java/io/helidon/webserver/tyrus/TyrusSupport.java create mode 100644 webserver/webserver/src/main/java/io/helidon/webserver/AlreadyCompletedException.java delete mode 100644 webserver/webserver/src/main/java/io/helidon/webserver/HashRequestHeaders.java create mode 100644 webserver/webserver/src/main/java/io/helidon/webserver/NettyRequestHeaders.java delete mode 100644 webserver/webserver/src/test/java/io/helidon/webserver/CookieParserTest.java delete mode 100644 webserver/webserver/src/test/java/io/helidon/webserver/HashParametersTest.java delete mode 100644 webserver/webserver/src/test/java/io/helidon/webserver/HashRequestHeadersTest.java create mode 100644 webserver/webserver/src/test/java/io/helidon/webserver/NettyRequestHeadersTest.java delete mode 100644 webserver/webserver/src/test/java/io/helidon/webserver/ServerRequestReaderTest.java diff --git a/webserver/access-log/src/test/java/io/helidon/webserver/accesslog/AccessLogSupportTest.java b/webserver/access-log/src/test/java/io/helidon/webserver/accesslog/AccessLogSupportTest.java index 259afc41718..ef2087f2add 100644 --- a/webserver/access-log/src/test/java/io/helidon/webserver/accesslog/AccessLogSupportTest.java +++ b/webserver/access-log/src/test/java/io/helidon/webserver/accesslog/AccessLogSupportTest.java @@ -22,7 +22,6 @@ import io.helidon.common.context.Context; import io.helidon.common.http.Http; -import io.helidon.common.http.HttpRequest; import io.helidon.webserver.PathMatcher; import io.helidon.webserver.RequestHeaders; import io.helidon.webserver.ServerRequest; @@ -47,7 +46,7 @@ class AccessLogSupportTest { private static final String PATH = "/greet/World"; private static final String HTTP_VERSION = "HTTP/1.1"; - private static final int STATUS_CODE = Http.Status.I_AM_A_TEAPOT.code(); + private static final int STATUS_CODE = Http.Status.I_AM_A_TEAPOT_418.code(); private static final String CONTENT_LENGTH = "-"; private static final long TIME_TAKEN_MICROS = 1140000; @@ -60,13 +59,13 @@ void testHelidonFormat() { when(request.remoteAddress()).thenReturn(REMOTE_IP); when(request.context()).thenReturn(context); when(request.method()).thenReturn(Http.Method.PUT); - HttpRequest.Path path = mock(HttpRequest.Path.class); + ServerRequest.Path path = mock(ServerRequest.Path.class); when(path.toRawString()).thenReturn(PATH); when(request.path()).thenReturn(path); when(request.version()).thenReturn(Http.Version.V1_1); ServerResponse response = mock(ServerResponse.class); - when(response.status()).thenReturn(Http.Status.I_AM_A_TEAPOT); + when(response.status()).thenReturn(Http.Status.I_AM_A_TEAPOT_418); AccessLogContext accessLogContext = mock(AccessLogContext.class); when(accessLogContext.requestDateTime()).thenReturn(BEGIN_TIME); @@ -98,13 +97,13 @@ void testCommonFormat() { when(request.remoteAddress()).thenReturn(REMOTE_IP); when(request.context()).thenReturn(context); when(request.method()).thenReturn(Http.Method.PUT); - HttpRequest.Path path = mock(HttpRequest.Path.class); + ServerRequest.Path path = mock(ServerRequest.Path.class); when(path.toRawString()).thenReturn(PATH); when(request.path()).thenReturn(path); when(request.version()).thenReturn(Http.Version.V1_1); ServerResponse response = mock(ServerResponse.class); - when(response.status()).thenReturn(Http.Status.I_AM_A_TEAPOT); + when(response.status()).thenReturn(Http.Status.I_AM_A_TEAPOT_418); AccessLogContext accessLogContext = mock(AccessLogContext.class); when(accessLogContext.requestDateTime()).thenReturn(BEGIN_TIME); @@ -137,7 +136,7 @@ void testCustomFormat() { when(request.remoteAddress()).thenReturn(REMOTE_IP); when(request.context()).thenReturn(context); when(request.method()).thenReturn(Http.Method.PUT); - HttpRequest.Path path = mock(HttpRequest.Path.class); + ServerRequest.Path path = mock(ServerRequest.Path.class); when(path.toRawString()).thenReturn(PATH); when(request.path()).thenReturn(path); when(request.version()).thenReturn(Http.Version.V1_1); @@ -146,7 +145,7 @@ void testCustomFormat() { when(request.headers()).thenReturn(headers); ServerResponse response = mock(ServerResponse.class); - when(response.status()).thenReturn(Http.Status.I_AM_A_TEAPOT); + when(response.status()).thenReturn(Http.Status.I_AM_A_TEAPOT_418); String logRecord = accessLog.createLogRecord(request, response, diff --git a/webserver/cors/src/main/java/io/helidon/webserver/cors/CorsSupportBase.java b/webserver/cors/src/main/java/io/helidon/webserver/cors/CorsSupportBase.java index 961b77b0932..08bedaab8e9 100644 --- a/webserver/cors/src/main/java/io/helidon/webserver/cors/CorsSupportBase.java +++ b/webserver/cors/src/main/java/io/helidon/webserver/cors/CorsSupportBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import java.util.logging.Level; import java.util.logging.Logger; +import io.helidon.common.http.Http; import io.helidon.config.Config; /** @@ -283,7 +284,7 @@ protected interface RequestAdapter { * @param key header name to retrieve * @return the first header value for the key */ - Optional firstHeader(String key); + Optional firstHeader(Http.HeaderName key); /** * Reports whether the specified header exists. @@ -291,7 +292,7 @@ protected interface RequestAdapter { * @param key header name to check for * @return whether the header exists among the request's headers */ - boolean headerContainsKey(String key); + boolean headerContainsKey(Http.HeaderName key); /** * Retrieves all header values for a given key as Strings. @@ -299,7 +300,7 @@ protected interface RequestAdapter { * @param key header name to retrieve * @return header values for the header; empty list if none */ - List allHeaders(String key); + List allHeaders(Http.HeaderName key); /** * Reports the method name for the request. @@ -343,7 +344,7 @@ protected interface ResponseAdapter { * @param value header value to add * @return the adapter */ - ResponseAdapter header(String key, String value); + ResponseAdapter header(Http.HeaderName key, String value); /** * Arranges to add the specified header and value to the eventual response. @@ -352,7 +353,7 @@ protected interface ResponseAdapter { * @param value header value to add * @return the adapter */ - ResponseAdapter header(String key, Object value); + ResponseAdapter header(Http.HeaderName key, Object value); /** * Returns a response with the forbidden status and the specified error message, without any headers assigned diff --git a/webserver/cors/src/main/java/io/helidon/webserver/cors/CorsSupportHelper.java b/webserver/cors/src/main/java/io/helidon/webserver/cors/CorsSupportHelper.java index c919ba9a993..81b4512cc4c 100644 --- a/webserver/cors/src/main/java/io/helidon/webserver/cors/CorsSupportHelper.java +++ b/webserver/cors/src/main/java/io/helidon/webserver/cors/CorsSupportHelper.java @@ -34,16 +34,16 @@ import io.helidon.webserver.cors.CorsSupportBase.ResponseAdapter; import io.helidon.webserver.cors.LogHelper.Headers; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_CREDENTIALS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_HEADERS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_METHODS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_EXPOSE_HEADERS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_MAX_AGE; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_REQUEST_HEADERS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_REQUEST_METHOD; import static io.helidon.common.http.Http.Header.HOST; import static io.helidon.common.http.Http.Header.ORIGIN; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_CREDENTIALS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_HEADERS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_EXPOSE_HEADERS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_MAX_AGE; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_REQUEST_HEADERS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_REQUEST_METHOD; import static io.helidon.webserver.cors.LogHelper.DECISION_LEVEL; import static java.lang.Character.isDigit; @@ -396,7 +396,7 @@ private boolean isRequestTypeNormal(RequestAdapter requestAdapter, boolean si private RequestType inferCORSRequestType(RequestAdapter requestAdapter, boolean silent) { String methodName = requestAdapter.method(); - boolean isMethodOPTION = methodName.equalsIgnoreCase(Http.Method.OPTIONS.name()); + boolean isMethodOPTION = methodName.equalsIgnoreCase(Http.Method.OPTIONS.text()); boolean requestContainsAccessControlRequestMethodHeader = requestAdapter.headerContainsKey(ACCESS_CONTROL_REQUEST_METHOD); RequestType result = isMethodOPTION && requestContainsAccessControlRequestMethodHeader @@ -500,7 +500,7 @@ R processCorsPreFlightRequest(RequestAdapter requestAdapter, ResponseAdapter< } // Access-Control-Request-Method had to be present in order for this to be assessed as a preflight request. - String requestedMethod = requestAdapter.firstHeader(ACCESS_CONTROL_REQUEST_METHOD).get(); + String requestedMethod = requestAdapter.firstHeader(Http.Header.ACCESS_CONTROL_REQUEST_METHOD).get(); // Lookup the CrossOriginConfig using the requested method, not the current method (which we know is OPTIONS). Optional crossOriginOpt = aggregator.lookupCrossOrigin( @@ -757,12 +757,12 @@ static boolean contains(Collection left, Collection right) { return true; } - private static Supplier noRequiredHeaderExcFactory(String header) { + private static Supplier noRequiredHeaderExcFactory(Http.HeaderName header) { return () -> new IllegalArgumentException(noRequiredHeader(header)); } - private static String noRequiredHeader(String header) { - return "CORS request does not have required header " + header; + private static String noRequiredHeader(Http.HeaderName header) { + return "CORS request does not have required header " + header.defaultCase(); } private R forbid(RequestAdapter requestAdapter, ResponseAdapter responseAdapter, diff --git a/webserver/cors/src/main/java/io/helidon/webserver/cors/LogHelper.java b/webserver/cors/src/main/java/io/helidon/webserver/cors/LogHelper.java index 90c9c6f1302..7dfe9ac0ee6 100644 --- a/webserver/cors/src/main/java/io/helidon/webserver/cors/LogHelper.java +++ b/webserver/cors/src/main/java/io/helidon/webserver/cors/LogHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,10 +32,10 @@ import io.helidon.webserver.cors.CorsSupportBase.RequestAdapter; import io.helidon.webserver.cors.CorsSupportHelper.RequestType; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_REQUEST_METHOD; import static io.helidon.common.http.Http.Header.HOST; import static io.helidon.common.http.Http.Header.ORIGIN; import static io.helidon.webserver.cors.CorsSupportHelper.LOGGER; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_REQUEST_METHOD; class LogHelper { @@ -49,15 +49,15 @@ private LogHelper() { * Collects headers for assignment to a request or response and logging during assignment. */ static class Headers { - private final List> headers = new ArrayList<>(); + private final List> headers = new ArrayList<>(); private final List notes = LOGGER.isLoggable(DECISION_LEVEL) ? new ArrayList<>() : null; - Headers add(String key, Object value) { + Headers add(Http.HeaderName key, Object value) { headers.add(new AbstractMap.SimpleEntry<>(key, value)); return this; } - Headers add(String key, Object value, String note) { + Headers add(Http.HeaderName key, Object value, String note) { add(key, value); if (notes != null) { notes.add(note); @@ -65,7 +65,7 @@ Headers add(String key, Object value, String note) { return this; } - void setAndLog(BiConsumer consumer, String note) { + void setAndLog(BiConsumer consumer, String note) { headers.forEach(entry -> consumer.accept(entry.getKey(), entry.getValue())); LOGGER.log(DECISION_LEVEL, () -> note + ": " + headers + (notes == null ? "" : notes)); } @@ -129,9 +129,9 @@ static void logInferRequestType(RequestType result, boolean silent, RequestA } if (!requestContainsAccessControlRequestMethodHeader) { - reasonsWhyCORS.add(String.format("header %s is absent", ACCESS_CONTROL_REQUEST_METHOD)); + reasonsWhyCORS.add(String.format("header %s is absent", ACCESS_CONTROL_REQUEST_METHOD.defaultCase())); } else { - factorsWhyPreflight.add(String.format("header %s is present(%s)", ACCESS_CONTROL_REQUEST_METHOD, + factorsWhyPreflight.add(String.format("header %s is present(%s)", ACCESS_CONTROL_REQUEST_METHOD.defaultCase(), requestAdapter.firstHeader(ACCESS_CONTROL_REQUEST_METHOD))); } diff --git a/webserver/cors/src/main/java/io/helidon/webserver/cors/RequestAdapterSe.java b/webserver/cors/src/main/java/io/helidon/webserver/cors/RequestAdapterSe.java index 0732accf5e4..ca3f871a006 100644 --- a/webserver/cors/src/main/java/io/helidon/webserver/cors/RequestAdapterSe.java +++ b/webserver/cors/src/main/java/io/helidon/webserver/cors/RequestAdapterSe.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.util.List; import java.util.Optional; +import io.helidon.common.http.Http; import io.helidon.webserver.ServerRequest; /** @@ -37,18 +38,18 @@ public String path() { } @Override - public Optional firstHeader(String key) { - return request.headers().first(key); + public Optional firstHeader(Http.HeaderName key) { + return request.headers().value(key); } @Override - public boolean headerContainsKey(String key) { - return firstHeader(key).isPresent(); + public boolean headerContainsKey(Http.HeaderName key) { + return request.headers().contains(key); } @Override - public List allHeaders(String key) { - return request.headers().all(key); + public List allHeaders(Http.HeaderName key) { + return request.headers().all(key, List::of); } @Override @@ -68,6 +69,6 @@ public ServerRequest request() { @Override public String toString() { - return String.format("RequestAdapterSe{path=%s, method=%s, headers=%s}", path(), method(), request.headers().toMap()); + return String.format("RequestAdapterSe{path=%s, method=%s, headers=%s}", path(), method(), request.headers()); } } diff --git a/webserver/cors/src/main/java/io/helidon/webserver/cors/ResponseAdapterSe.java b/webserver/cors/src/main/java/io/helidon/webserver/cors/ResponseAdapterSe.java index 04d073856a7..fdcaa939fc6 100644 --- a/webserver/cors/src/main/java/io/helidon/webserver/cors/ResponseAdapterSe.java +++ b/webserver/cors/src/main/java/io/helidon/webserver/cors/ResponseAdapterSe.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,20 +30,20 @@ class ResponseAdapterSe implements CorsSupportBase.ResponseAdapter header(String key, String value) { + public CorsSupportBase.ResponseAdapter header(Http.HeaderName key, String value) { serverResponse.headers().add(key, value); return this; } @Override - public CorsSupportBase.ResponseAdapter header(String key, Object value) { + public CorsSupportBase.ResponseAdapter header(Http.HeaderName key, Object value) { serverResponse.headers().add(key, value.toString()); return this; } @Override public ServerResponse forbidden(String message) { - serverResponse.status(Http.ResponseStatus.create(Http.Status.FORBIDDEN_403.code(), message)); + serverResponse.status(Http.Status.create(Http.Status.FORBIDDEN_403.code(), message)); return serverResponse; } diff --git a/webserver/cors/src/test/java/io/helidon/webserver/cors/AbstractCorsTest.java b/webserver/cors/src/test/java/io/helidon/webserver/cors/AbstractCorsTest.java index eb942149229..e85cd90f558 100644 --- a/webserver/cors/src/test/java/io/helidon/webserver/cors/AbstractCorsTest.java +++ b/webserver/cors/src/test/java/io/helidon/webserver/cors/AbstractCorsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,25 +18,26 @@ import java.util.Optional; import java.util.concurrent.ExecutionException; -import io.helidon.common.http.Headers; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; +import io.helidon.webclient.WebClientRequestHeaders; import io.helidon.webclient.WebClientResponse; import org.junit.jupiter.api.Test; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_CREDENTIALS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_HEADERS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_METHODS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_MAX_AGE; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_REQUEST_HEADERS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_REQUEST_METHOD; import static io.helidon.common.http.Http.Header.ORIGIN; import static io.helidon.webserver.cors.CorsTestServices.SERVICE_1; import static io.helidon.webserver.cors.CorsTestServices.SERVICE_2; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_CREDENTIALS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_HEADERS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_MAX_AGE; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_REQUEST_HEADERS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_REQUEST_METHOD; import static io.helidon.webserver.cors.CustomMatchers.notPresent; import static io.helidon.webserver.cors.CustomMatchers.present; import static io.helidon.webserver.cors.TestUtil.path; @@ -60,12 +61,12 @@ public void testSimple() throws Exception { WebClientResponse response = client().get() .path(contextRoot()) - .accept(MediaType.TEXT_PLAIN) + .accept(HttpMediaType.TEXT_PLAIN) .request() .toCompletableFuture() .get(); - Http.ResponseStatus result = response.status(); + Http.Status result = response.status(); assertThat(result.code(), is(Http.Status.OK_200.code())); } @@ -75,7 +76,7 @@ void test1PreFlightAllowedHeaders1() throws ExecutionException, InterruptedExcep .options() .path(path(SERVICE_1)); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, "http://foo.bar"); headers.add(ACCESS_CONTROL_REQUEST_METHOD, "PUT"); headers.add(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo"); @@ -98,7 +99,7 @@ void test1PreFlightAllowedHeaders2() throws ExecutionException, InterruptedExcep .options() .path(path(SERVICE_1)); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, "http://foo.bar"); headers.add(ACCESS_CONTROL_REQUEST_METHOD, "PUT"); headers.add(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo, X-bar"); @@ -122,7 +123,7 @@ void test2PreFlightForbiddenOrigin() throws ExecutionException, InterruptedExcep .options() .path(path(SERVICE_2)); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, "http://not.allowed"); headers.add(ACCESS_CONTROL_REQUEST_METHOD, "PUT"); @@ -131,7 +132,7 @@ void test2PreFlightForbiddenOrigin() throws ExecutionException, InterruptedExcep .toCompletableFuture() .get(); - Http.ResponseStatus status = res.status(); + Http.Status status = res.status(); assertThat(status.code(), is(Http.Status.FORBIDDEN_403.code())); assertThat(status.reasonPhrase(), is("CORS origin is not in allowed list")); } @@ -142,7 +143,7 @@ void test2PreFlightAllowedOrigin() throws ExecutionException, InterruptedExcepti .options() .path(path(SERVICE_2)); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, "http://foo.bar"); headers.add(ACCESS_CONTROL_REQUEST_METHOD, "PUT"); @@ -166,7 +167,7 @@ void test2PreFlightForbiddenMethod() throws ExecutionException, InterruptedExcep .options() .path(path(SERVICE_2)); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, "http://foo.bar"); headers.add(ACCESS_CONTROL_REQUEST_METHOD, "POST"); @@ -175,7 +176,7 @@ void test2PreFlightForbiddenMethod() throws ExecutionException, InterruptedExcep .toCompletableFuture() .get(); - Http.ResponseStatus status = res.status(); + Http.Status status = res.status(); assertThat(status.code(), is(Http.Status.FORBIDDEN_403.code())); assertThat(status.reasonPhrase(), is("CORS origin is denied")); } @@ -186,7 +187,7 @@ void test2PreFlightForbiddenHeader() throws ExecutionException, InterruptedExcep .options() .path(path(SERVICE_2)); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, "http://foo.bar"); headers.add(ACCESS_CONTROL_REQUEST_METHOD, "PUT"); headers.add(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo, X-bar, X-oops"); @@ -196,7 +197,7 @@ void test2PreFlightForbiddenHeader() throws ExecutionException, InterruptedExcep .toCompletableFuture() .get(); - Http.ResponseStatus status = res.status(); + Http.Status status = res.status(); assertThat(status.code(), is(Http.Status.FORBIDDEN_403.code())); assertThat(status.reasonPhrase(), is("CORS headers not in allowed list")); } @@ -207,7 +208,7 @@ void test2PreFlightAllowedHeaders1() throws ExecutionException, InterruptedExcep .options() .path(path(contextRoot(), SERVICE_2)); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, fooOrigin()); headers.add(ACCESS_CONTROL_REQUEST_METHOD, "PUT"); headers.add(ACCESS_CONTROL_REQUEST_HEADERS, fooHeader()); @@ -236,7 +237,7 @@ void test2PreFlightAllowedHeaders2() throws ExecutionException, InterruptedExcep .options() .path(path(SERVICE_2)); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, "http://foo.bar"); headers.add(ACCESS_CONTROL_REQUEST_METHOD, "PUT"); headers.add(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo, X-bar"); @@ -261,7 +262,7 @@ void test2PreFlightAllowedHeaders3() throws ExecutionException, InterruptedExcep .options() .path(path(SERVICE_2)); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, "http://foo.bar"); headers.add(ACCESS_CONTROL_REQUEST_METHOD, "PUT"); headers.add(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo, X-bar"); @@ -286,9 +287,9 @@ void test1ActualAllowedOrigin() throws ExecutionException, InterruptedException WebClientRequestBuilder reqBuilder = client() .put() .path(path(SERVICE_1)) - .contentType(MediaType.TEXT_PLAIN); + .contentType(MediaTypes.TEXT_PLAIN); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, "http://foo.bar"); headers.add(ACCESS_CONTROL_REQUEST_METHOD, "PUT"); @@ -306,9 +307,9 @@ void test2ActualAllowedOrigin() throws ExecutionException, InterruptedException WebClientRequestBuilder reqBuilder = client() .put() .path(path(SERVICE_2)) - .contentType(MediaType.TEXT_PLAIN); + .contentType(MediaTypes.TEXT_PLAIN); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, "http://foo.bar"); WebClientResponse res = reqBuilder @@ -327,9 +328,9 @@ void test2ErrorResponse() throws ExecutionException, InterruptedException { WebClientRequestBuilder reqBuilder = client() .get() .path(path(SERVICE_2) + "/notfound") - .contentType(MediaType.TEXT_PLAIN); + .contentType(MediaTypes.TEXT_PLAIN); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, "http://foo.bar"); WebClientResponse res = reqBuilder @@ -347,7 +348,7 @@ WebClientResponse runTest1PreFlightAllowedOrigin() throws ExecutionException, .options() .path(path(contextRoot(), SERVICE_1)); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, fooOrigin()); headers.add(ACCESS_CONTROL_REQUEST_METHOD, "PUT"); diff --git a/webserver/cors/src/test/java/io/helidon/webserver/cors/CorsTest.java b/webserver/cors/src/test/java/io/helidon/webserver/cors/CorsTest.java index a7bcd95f184..90097d206af 100644 --- a/webserver/cors/src/test/java/io/helidon/webserver/cors/CorsTest.java +++ b/webserver/cors/src/test/java/io/helidon/webserver/cors/CorsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package io.helidon.webserver.cors; +import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; @@ -28,10 +29,10 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_HEADERS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_MAX_AGE; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_HEADERS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_METHODS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_MAX_AGE; import static io.helidon.webserver.cors.CustomMatchers.notPresent; import static io.helidon.webserver.cors.CustomMatchers.present; import static org.hamcrest.MatcherAssert.assertThat; @@ -89,6 +90,6 @@ void test1PreFlightAllowedOrigin() throws ExecutionException, InterruptedExcepti assertThat(res.headers().first(ACCESS_CONTROL_ALLOW_METHODS), present(is("PUT"))); assertThat(res.headers().first(ACCESS_CONTROL_ALLOW_HEADERS), notPresent()); assertThat(res.headers().first(ACCESS_CONTROL_MAX_AGE), present(is("3600"))); - assertThat(res.headers().all(ACCESS_CONTROL_ALLOW_ORIGIN).size(), is(1)); + assertThat(res.headers().all(ACCESS_CONTROL_ALLOW_ORIGIN, List::of).size(), is(1)); } } diff --git a/webserver/cors/src/test/java/io/helidon/webserver/cors/TestDefaultCorsSupport.java b/webserver/cors/src/test/java/io/helidon/webserver/cors/TestDefaultCorsSupport.java index 9e84a9bc668..9e3d60e6543 100644 --- a/webserver/cors/src/test/java/io/helidon/webserver/cors/TestDefaultCorsSupport.java +++ b/webserver/cors/src/test/java/io/helidon/webserver/cors/TestDefaultCorsSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,9 +18,9 @@ import java.util.List; import java.util.concurrent.ExecutionException; -import io.helidon.common.http.Headers; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; +import io.helidon.webclient.WebClientRequestHeaders; import io.helidon.webclient.WebClientResponse; import io.helidon.webclient.WebClientResponseHeaders; import io.helidon.webserver.Routing; @@ -28,6 +28,9 @@ import org.junit.jupiter.api.Test; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN; +import static io.helidon.common.http.Http.Header.HOST; +import static io.helidon.common.http.Http.Header.ORIGIN; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; @@ -75,16 +78,16 @@ void testOptionsWithCors() throws ExecutionException, InterruptedException { WebClientRequestBuilder reqBuilder = client.options() .path("/greet"); - Headers h = reqBuilder.headers(); - h.add("Origin", "http://foo.com"); - h.add("Host", "bar.com"); + WebClientRequestHeaders h = reqBuilder.headers(); + h.add(ORIGIN, "http://foo.com"); + h.add(HOST, "bar.com"); WebClientResponse response = reqBuilder .submit() .toCompletableFuture() .get(); WebClientResponseHeaders headers = response.headers(); - List allowOrigins = headers.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + List allowOrigins = headers.values(ACCESS_CONTROL_ALLOW_ORIGIN); assertThat(allowOrigins, contains("*")); } finally { if (server != null) { @@ -106,16 +109,16 @@ void testOptionsWithoutCors() throws ExecutionException, InterruptedException { WebClientRequestBuilder reqBuilder = client.options() .path("/greet"); - Headers h = reqBuilder.headers(); - h.add("Origin", "http://foo.com"); - h.add("Host", "bar.com"); + WebClientRequestHeaders h = reqBuilder.headers(); + h.add(ORIGIN, "http://foo.com"); + h.add(HOST, "bar.com"); WebClientResponse response = reqBuilder .submit() .toCompletableFuture() .get(); WebClientResponseHeaders headers = response.headers(); - List allowOrigins = headers.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + List allowOrigins = headers.values(ACCESS_CONTROL_ALLOW_ORIGIN); assertThat(allowOrigins.size(), is(0)); } finally { if (server != null) { diff --git a/webserver/cors/src/test/java/io/helidon/webserver/cors/TestHandlerRegistration.java b/webserver/cors/src/test/java/io/helidon/webserver/cors/TestHandlerRegistration.java index f30f4c4d597..76742389cb7 100644 --- a/webserver/cors/src/test/java/io/helidon/webserver/cors/TestHandlerRegistration.java +++ b/webserver/cors/src/test/java/io/helidon/webserver/cors/TestHandlerRegistration.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,10 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; -import io.helidon.common.http.Headers; import io.helidon.common.http.Http; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; +import io.helidon.webclient.WebClientRequestHeaders; import io.helidon.webclient.WebClientResponse; import io.helidon.webserver.WebServer; @@ -30,12 +30,12 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_HEADERS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_METHODS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_REQUEST_HEADERS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_REQUEST_METHOD; import static io.helidon.common.http.Http.Header.ORIGIN; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_HEADERS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_REQUEST_HEADERS; -import static io.helidon.webserver.cors.CrossOriginConfig.ACCESS_CONTROL_REQUEST_METHOD; import static io.helidon.webserver.cors.CustomMatchers.present; import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.MatcherAssert.assertThat; @@ -70,7 +70,7 @@ void test4PreFlightAllowedHeaders2() throws ExecutionException, InterruptedExcep .options() .path(CORS4_CONTEXT_ROOT); - Headers headers = reqBuilder.headers(); + WebClientRequestHeaders headers = reqBuilder.headers(); headers.add(ORIGIN, "http://foo.bar"); headers.add(ACCESS_CONTROL_REQUEST_METHOD, "PUT"); headers.add(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo, X-bar"); diff --git a/webserver/cors/src/test/java/io/helidon/webserver/cors/TestTwoCorsConfigs.java b/webserver/cors/src/test/java/io/helidon/webserver/cors/TestTwoCorsConfigs.java index 49d8e6b4736..08cfa4b9a74 100644 --- a/webserver/cors/src/test/java/io/helidon/webserver/cors/TestTwoCorsConfigs.java +++ b/webserver/cors/src/test/java/io/helidon/webserver/cors/TestTwoCorsConfigs.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,7 +50,7 @@ public void startupClient() { void test1PreFlightAllowedOriginOtherGreeting() throws ExecutionException, InterruptedException { WebClientResponse res = runTest1PreFlightAllowedOrigin(); - Http.ResponseStatus status = res.status(); + Http.Status status = res.status(); assertThat(status.code(), is(Http.Status.FORBIDDEN_403.code())); assertThat(status.reasonPhrase(), is("CORS origin is denied")); } diff --git a/webserver/jersey/pom.xml b/webserver/jersey/pom.xml index 7cd4771ed30..3a20acc1d82 100644 --- a/webserver/jersey/pom.xml +++ b/webserver/jersey/pom.xml @@ -110,6 +110,15 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + diff --git a/webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java b/webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java index 0c769c0fb67..3fcffc908e6 100644 --- a/webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java +++ b/webserver/jersey/src/main/java/io/helidon/webserver/jersey/JerseySupport.java @@ -37,7 +37,6 @@ import io.helidon.common.context.Context; import io.helidon.common.context.Contexts; import io.helidon.common.http.Http; -import io.helidon.common.http.HttpRequest; import io.helidon.config.Config; import io.helidon.config.ConfigValue; import io.helidon.tracing.SpanContext; @@ -90,7 +89,7 @@ */ public class JerseySupport implements Service { - private static final String SEC_WEBSOCKET_KEY = "Sec-WebSocket-Key"; + private static final Http.HeaderName SEC_WEBSOCKET_KEY = Http.Header.create("Sec-WebSocket-Key"); /** * The request scoped span context qualifier that can be injected into a Jersey resource. @@ -201,7 +200,7 @@ private static URI baseUri(ServerRequest req) { } } - static String basePath(HttpRequest.Path path) { + static String basePath(ServerRequest.Path path) { String reqPath = path.toString(); String absPath = path.absolute().toString(); String basePath = absPath.substring(0, absPath.length() - reqPath.length() + 1); diff --git a/webserver/jersey/src/main/java/io/helidon/webserver/jersey/ResponseWriter.java b/webserver/jersey/src/main/java/io/helidon/webserver/jersey/ResponseWriter.java index ae17f4822ff..9c9c368663e 100644 --- a/webserver/jersey/src/main/java/io/helidon/webserver/jersey/ResponseWriter.java +++ b/webserver/jersey/src/main/java/io/helidon/webserver/jersey/ResponseWriter.java @@ -72,7 +72,7 @@ public OutputStream writeResponseStatusAndHeaders(long contentLength, ContainerR throws ContainerException { // Even in 404 responses with empty content, the CORS component might have added headers. Always copy any headers. for (Map.Entry> entry : context.getStringHeaders().entrySet()) { - res.headers().put(entry.getKey(), entry.getValue()); + res.headers().set(Http.Header.create(entry.getKey()), entry.getValue()); } if (context.getStatus() == 404 && contentLength == 0 @@ -90,10 +90,10 @@ public void write(int b) { }; } - res.status(Http.ResponseStatus.create(context.getStatus(), context.getStatusInfo().getReasonPhrase())); + res.status(Http.Status.create(context.getStatus(), context.getStatusInfo().getReasonPhrase())); if (contentLength >= 0) { - res.headers().put(Http.Header.CONTENT_LENGTH, String.valueOf(contentLength)); + res.headers().set(Http.Header.CONTENT_LENGTH, String.valueOf(contentLength)); } // diff --git a/webserver/jersey/src/test/java/io/helidon/webserver/jersey/JerseySupportTest.java b/webserver/jersey/src/test/java/io/helidon/webserver/jersey/JerseySupportTest.java index 4a784f0564e..ca131b02dcb 100644 --- a/webserver/jersey/src/test/java/io/helidon/webserver/jersey/JerseySupportTest.java +++ b/webserver/jersey/src/test/java/io/helidon/webserver/jersey/JerseySupportTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import java.util.Collections; import java.util.List; -import io.helidon.common.http.HttpRequest; +import io.helidon.webserver.ServerRequest; import jakarta.ws.rs.ProcessingException; import jakarta.ws.rs.client.Entity; @@ -338,7 +338,7 @@ public void testJerseyProperties() { assertThat(System.getProperty(IGNORE_EXCEPTION_RESPONSE), is("true")); } - static class PathMockup implements HttpRequest.Path { + static class PathMockup implements ServerRequest.Path { private final String absolutePath; private final String path; @@ -363,7 +363,7 @@ public String toRawString() { } @Override - public HttpRequest.Path absolute() { + public ServerRequest.Path absolute() { return absolutePath == null ? this : new PathMockup(null, absolutePath); } diff --git a/webserver/jersey/src/test/resources/logging-test.properties b/webserver/jersey/src/test/resources/logging-test.properties index 6403096747c..97783f0c6a9 100644 --- a/webserver/jersey/src/test/resources/logging-test.properties +++ b/webserver/jersey/src/test/resources/logging-test.properties @@ -16,7 +16,7 @@ #All attributes details -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n .level=INFO diff --git a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/ClassPathContentHandler.java b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/ClassPathContentHandler.java index 859880f044a..c9633436fa1 100644 --- a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/ClassPathContentHandler.java +++ b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/ClassPathContentHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,7 +85,7 @@ class ClassPathContentHandler extends FileBasedContentHandler { @SuppressWarnings("checkstyle:RegexpSinglelineJava") @Override - boolean doHandle(Http.RequestMethod method, String requestedPath, ServerRequest request, ServerResponse response) + boolean doHandle(Http.Method method, String requestedPath, ServerRequest request, ServerResponse response) throws IOException, URISyntaxException { String resource = requestedPath.isEmpty() ? root : (rootWithTrailingSlash + requestedPath); @@ -146,7 +146,7 @@ boolean doHandle(Http.RequestMethod method, String requestedPath, ServerRequest return true; } - boolean sendJar(Http.RequestMethod method, + boolean sendJar(Http.Method method, String requestedResource, URL url, ServerRequest request, @@ -192,7 +192,7 @@ private ExtractedJarEntry existOrCreate(URL url, ExtractedJarEntry entry) { return entry; } - private void sendUrlStream(Http.RequestMethod method, URL url, ServerRequest request, ServerResponse response) + private void sendUrlStream(Http.Method method, URL url, ServerRequest request, ServerResponse response) throws IOException { LOGGER.finest(() -> "Sending static content using stream from classpath: " + url); diff --git a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/FileBasedContentHandler.java b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/FileBasedContentHandler.java index 25409171eb8..4781544aaa8 100644 --- a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/FileBasedContentHandler.java +++ b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/FileBasedContentHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,8 @@ import java.util.logging.Logger; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaType; import io.helidon.common.media.type.MediaTypes; import io.helidon.media.common.DefaultMediaSupport; import io.helidon.media.common.MessageBodyWriter; @@ -96,8 +97,7 @@ private MediaType detectType(String fileName, RequestHeaders requestHeaders) { // then find if we have a detected type // then check the type is accepted by the request return findCustomMediaType(fileName) - .or(() -> MediaTypes.detectType(fileName) - .map(MediaType::parse)) + .or(() -> MediaTypes.detectType(fileName)) .map(it -> { if (requestHeaders.isAccepted(it)) { return it; @@ -106,11 +106,11 @@ private MediaType detectType(String fileName, RequestHeaders requestHeaders) { Http.Status.UNSUPPORTED_MEDIA_TYPE_415); }) .orElseGet(() -> { - List acceptedTypes = requestHeaders.acceptedTypes(); + List acceptedTypes = requestHeaders.acceptedTypes(); if (acceptedTypes.isEmpty()) { - return MediaType.APPLICATION_OCTET_STREAM; + return MediaTypes.APPLICATION_OCTET_STREAM; } else { - return acceptedTypes.iterator().next(); + return acceptedTypes.iterator().next().mediaType(); } }); } @@ -127,7 +127,7 @@ Optional findCustomMediaType(String fileName) { return Optional.ofNullable(customMediaTypes.get(fileSuffix)); } - void sendFile(Http.RequestMethod method, + void sendFile(Http.Method method, Path pathParam, ServerRequest request, ServerResponse response, diff --git a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/FileSystemContentHandler.java b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/FileSystemContentHandler.java index 8b79da9963c..418459a631b 100644 --- a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/FileSystemContentHandler.java +++ b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/FileSystemContentHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ class FileSystemContentHandler extends FileBasedContentHandler { } @Override - boolean doHandle(Http.RequestMethod method, String requestedPath, ServerRequest request, ServerResponse response) + boolean doHandle(Http.Method method, String requestedPath, ServerRequest request, ServerResponse response) throws IOException { Path resolved; if (requestedPath.isEmpty()) { @@ -56,7 +56,7 @@ boolean doHandle(Http.RequestMethod method, String requestedPath, ServerRequest return doHandle(method, resolved, request, response); } - boolean doHandle(Http.RequestMethod method, Path path, ServerRequest request, ServerResponse response) throws IOException { + boolean doHandle(Http.Method method, Path path, ServerRequest request, ServerResponse response) throws IOException { // Check existence if (!Files.exists(path)) { return false; diff --git a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentHandler.java b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentHandler.java index ab45b5f88b7..a392ba7a69f 100644 --- a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentHandler.java +++ b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -85,7 +85,7 @@ void releaseCache() { * @param request an HTTP request * @param response an HTTP response */ - void handle(Http.RequestMethod method, ServerRequest request, ServerResponse response) { + void handle(Http.Method method, ServerRequest request, ServerResponse response) { // Check method if ((method != Http.Method.GET) && (method != Http.Method.HEAD)) { request.next(); @@ -127,7 +127,7 @@ void handle(Http.RequestMethod method, ServerRequest request, ServerResponse res * @throws java.io.IOException if resource is not acceptable * @throws io.helidon.webserver.HttpException if some known WEB error */ - abstract boolean doHandle(Http.RequestMethod method, String requestedPath, ServerRequest request, ServerResponse response) + abstract boolean doHandle(Http.Method method, String requestedPath, ServerRequest request, ServerResponse response) throws IOException, URISyntaxException; /** @@ -145,9 +145,9 @@ static void processEtag(String etag, RequestHeaders requestHeaders, ResponseHead } etag = unquoteETag(etag); // Put ETag into the response - responseHeaders.put(Http.Header.ETAG, '"' + etag + '"'); + responseHeaders.set(Http.Header.ETAG, '"' + etag + '"'); // Process If-None-Match header - List ifNoneMatches = requestHeaders.values(Http.Header.IF_NONE_MATCH); + List ifNoneMatches = requestHeaders.all(Http.Header.IF_NONE_MATCH, List::of); for (String ifNoneMatch : ifNoneMatches) { ifNoneMatch = unquoteETag(ifNoneMatch); if ("*".equals(ifNoneMatch) || ifNoneMatch.equals(etag)) { @@ -155,7 +155,7 @@ static void processEtag(String etag, RequestHeaders requestHeaders, ResponseHead } } // Process If-Match header - List ifMatches = requestHeaders.values(Http.Header.IF_MATCH); + List ifMatches = requestHeaders.all(Http.Header.IF_MATCH, List::of); if (!ifMatches.isEmpty()) { boolean ifMatchChecked = false; for (String ifMatch : ifMatches) { @@ -244,7 +244,7 @@ static void redirect(ServerRequest request, ServerResponse response, String loca } response.status(Http.Status.MOVED_PERMANENTLY_301); - response.headers().put(Http.Header.LOCATION, locationWithQuery); + response.headers().set(Http.Header.LOCATION, locationWithQuery); response.send(); } diff --git a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentSupport.java b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentSupport.java index 8e863311225..78a8daa1264 100644 --- a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentSupport.java +++ b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,8 @@ import java.util.TreeMap; import java.util.function.Function; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaType; import io.helidon.webserver.Service; /** @@ -203,7 +204,7 @@ abstract class FileBasedBuilder> extends StaticCon * @throws NullPointerException if any parameter is {@code null} * @throws IllegalArgumentException if {@code filenameExtension} is empty */ - public T contentType(String filenameExtension, MediaType contentType) { + public T contentType(String filenameExtension, HttpMediaType contentType) { Objects.requireNonNull(filenameExtension, "Parameter 'filenameExtension' is null!"); Objects.requireNonNull(contentType, "Parameter 'contentType' is null!"); filenameExtension = filenameExtension.trim(); diff --git a/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/StaticContentHandlerTest.java b/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/StaticContentHandlerTest.java index 0496e41dc85..f0a7d03d352 100644 --- a/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/StaticContentHandlerTest.java +++ b/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/StaticContentHandlerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -67,7 +67,7 @@ void etag_InNonMatch_NotAccept() { when(req.values(Http.Header.IF_MATCH)).thenReturn(Collections.emptyList()); ResponseHeaders res = mock(ResponseHeaders.class); StaticContentHandler.processEtag("aaa", req, res); - verify(res).put(Http.Header.ETAG, "\"aaa\""); + verify(res).set(Http.Header.ETAG, "\"aaa\""); } @Test @@ -77,7 +77,7 @@ void etag_InNonMatch_Accept() { when(req.values(Http.Header.IF_MATCH)).thenReturn(Collections.emptyList()); ResponseHeaders res = mock(ResponseHeaders.class); assertHttpException(() -> StaticContentHandler.processEtag("aaa", req, res), Http.Status.NOT_MODIFIED_304); - verify(res).put(Http.Header.ETAG, "\"aaa\""); + verify(res).set(Http.Header.ETAG, "\"aaa\""); } @Test @@ -87,7 +87,7 @@ void etag_InMatch_NotAccept() { when(req.values(Http.Header.IF_NONE_MATCH)).thenReturn(Collections.emptyList()); ResponseHeaders res = mock(ResponseHeaders.class); assertHttpException(() -> StaticContentHandler.processEtag("aaa", req, res), Http.Status.PRECONDITION_FAILED_412); - verify(res).put(Http.Header.ETAG, "\"aaa\""); + verify(res).set(Http.Header.ETAG, "\"aaa\""); } @Test @@ -97,7 +97,7 @@ void etag_InMatch_Accept() { when(req.values(Http.Header.IF_NONE_MATCH)).thenReturn(Collections.emptyList()); ResponseHeaders res = mock(ResponseHeaders.class); StaticContentHandler.processEtag("aaa", req, res); - verify(res).put(Http.Header.ETAG, "\"aaa\""); + verify(res).set(Http.Header.ETAG, "\"aaa\""); } @Test @@ -150,7 +150,7 @@ void redirect() { Mockito.doReturn(resh).when(res).headers(); StaticContentHandler.redirect(req, res, "/foo/"); verify(res).status(Http.Status.MOVED_PERMANENTLY_301); - verify(resh).put(Http.Header.LOCATION, "/foo/"); + verify(resh).set(Http.Header.LOCATION, "/foo/"); verify(res).send(); } @@ -239,7 +239,7 @@ static TestContentHandler create(boolean returnValue) { } @Override - boolean doHandle(Http.RequestMethod method, Path path, ServerRequest request, ServerResponse response) { + boolean doHandle(Http.Method method, Path path, ServerRequest request, ServerResponse response) { this.counter.incrementAndGet(); this.path = path; return returnValue; @@ -261,7 +261,7 @@ static TestClassPathContentHandler create() { } @Override - boolean doHandle(Http.RequestMethod method, String path, ServerRequest request, ServerResponse response) + boolean doHandle(Http.Method method, String path, ServerRequest request, ServerResponse response) throws IOException, URISyntaxException { super.doHandle(method, path, request, response); this.counter.incrementAndGet(); diff --git a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/MediaPublisher.java b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/MediaPublisher.java index 017888c3666..896d31aea81 100644 --- a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/MediaPublisher.java +++ b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/MediaPublisher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import java.util.concurrent.Flow; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Multi; /** @@ -36,7 +36,7 @@ public interface MediaPublisher extends Flow.Publisher { * * @return a published media type or {@code null} for undefined. */ - MediaType mediaType(); + HttpMediaType mediaType(); /** * Creates new instance. @@ -45,10 +45,10 @@ public interface MediaPublisher extends Flow.Publisher { * @param publisher a publisher. * @return new instance. */ - static MediaPublisher create(MediaType publishedType, Flow.Publisher publisher) { + static MediaPublisher create(HttpMediaType publishedType, Flow.Publisher publisher) { return new MediaPublisher() { @Override - public MediaType mediaType() { + public HttpMediaType mediaType() { return publishedType; } @@ -67,16 +67,16 @@ public void subscribe(Flow.Subscriber subscriber) { * @param charSequence A sequence to publish. * @return new publisher. */ - static MediaPublisher create(MediaType publishedType, CharSequence charSequence) { + static MediaPublisher create(HttpMediaType publishedType, CharSequence charSequence) { ByteBuffer data = Optional.ofNullable(publishedType) - .flatMap(MediaType::charset) + .flatMap(HttpMediaType::charset) .map(Charset::forName) .orElse(StandardCharsets.UTF_8) .encode(charSequence.toString()); Flow.Publisher publisher = Multi.singleton(DataChunk.create(data)); return new MediaPublisher() { @Override - public MediaType mediaType() { + public HttpMediaType mediaType() { return publishedType; } diff --git a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/RequestHeadersMap.java b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/RequestHeadersMap.java new file mode 100644 index 00000000000..25d7017d276 --- /dev/null +++ b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/RequestHeadersMap.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.webserver.testsupport; + +import java.util.Iterator; +import java.util.List; +import java.util.Map; +import java.util.Optional; +import java.util.OptionalLong; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Supplier; + +import io.helidon.common.http.HeadersWritable; +import io.helidon.common.http.Http; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaType; +import io.helidon.webserver.RequestHeaders; + +class RequestHeadersMap implements RequestHeaders { + private final HeadersWritable delegate; + + RequestHeadersMap(Map> headers) { + this.delegate = HeadersWritable.create(); + headers.forEach((key, value) -> delegate.set(Http.Header.create(key), value)); + } + + @Override + @Deprecated(forRemoval = true) + public List all(String headerName) { + return delegate.all(headerName); + } + + @Override + public List all(Http.HeaderName name, Supplier> defaultSupplier) { + return delegate.all(name, defaultSupplier); + } + + @Override + public boolean contains(Http.HeaderName name) { + return delegate.contains(name); + } + + @Override + public boolean contains(Http.HeaderValue value) { + return delegate.contains(value); + } + + @Override + public Http.HeaderValue get(Http.HeaderName name) { + return delegate.get(name); + } + + @Override + public Optional value(Http.HeaderName headerName) { + return delegate.value(headerName); + } + + @Override + public Optional first(Http.HeaderName headerName) { + return delegate.first(headerName); + } + + @Override + public List values(Http.HeaderName headerName) { + return delegate.values(headerName); + } + + @Override + public OptionalLong contentLength() { + return delegate.contentLength(); + } + + @Override + public Optional contentType() { + return delegate.contentType(); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public List acceptedTypes() { + return delegate.acceptedTypes(); + } + + @Override + public boolean isAccepted(MediaType mediaType) { + return delegate.isAccepted(mediaType); + } + + @Override + @Deprecated(forRemoval = true) + public Map> toMap() { + return delegate.toMap(); + } + + @Override + public Iterator iterator() { + return delegate.iterator(); + } + + @Override + public void forEach(Consumer action) { + delegate.forEach(action); + } + + @Override + public Spliterator spliterator() { + return delegate.spliterator(); + } +} diff --git a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestClient.java b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestClient.java index f8be2109fec..b13d305f40b 100644 --- a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestClient.java +++ b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestClient.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,12 +35,12 @@ import io.helidon.common.context.Contexts; import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; -import io.helidon.common.http.ReadOnlyParameters; import io.helidon.common.reactive.Single; import io.helidon.media.common.MediaContext; import io.helidon.media.common.MediaSupport; import io.helidon.webserver.BareRequest; import io.helidon.webserver.BareResponse; +import io.helidon.webserver.RequestHeaders; import io.helidon.webserver.Routing; /** @@ -140,7 +140,7 @@ public TestRequest path(String path) { * @throws RuntimeException if response is not composed but throws exception * @throws TimeoutException if request timeouts */ - TestResponse call(Http.RequestMethod method, + TestResponse call(Http.Method method, Http.Version version, URI path, Map> headers, @@ -162,14 +162,14 @@ TestResponse call(Http.RequestMethod method, private static class TestBareRequest implements BareRequest { - private final Http.RequestMethod method; + private final Http.Method method; private final Http.Version version; private final URI path; - private final Map> headers; + private final RequestHeaders headers; private final Flow.Publisher publisher; private final TestWebServer webServer; - TestBareRequest(Http.RequestMethod method, + TestBareRequest(Http.Method method, Http.Version version, URI path, Map> headers, @@ -180,7 +180,7 @@ private static class TestBareRequest implements BareRequest { this.method = Objects.requireNonNull(method, "Parameter 'method' is null!"); this.version = Objects.requireNonNull(version, "Parameter 'version' is null!"); this.path = Objects.requireNonNull(path, "Parameter 'path' is null!"); - this.headers = new ReadOnlyParameters(headers).toMap(); + this.headers = new RequestHeadersMap(headers); if (publisher == null) { this.publisher = Single.empty(); } else { @@ -194,7 +194,7 @@ public TestWebServer webServer() { } @Override - public Http.RequestMethod method() { + public Http.Method method() { return method; } @@ -234,7 +234,7 @@ public boolean isSecure() { } @Override - public Map> headers() { + public RequestHeaders headers() { return headers; } @@ -279,7 +279,7 @@ byte[] asBytes() { } @Override - public void writeStatusAndHeaders(Http.ResponseStatus status, Map> headers) { + public void writeStatusAndHeaders(Http.Status status, Map> headers) { headersCompletionStage.complete(this); responseFuture.complete(new TestResponse(status, headers, this)); } diff --git a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestRequest.java b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestRequest.java index 6b17157a436..6517e50b897 100644 --- a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestRequest.java +++ b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -92,10 +92,10 @@ public TestRequest queryParameter(String name, String value) { * @return Updated instance. * @throws NullPointerException If {@code name} or {@code value} parameter is {@code null}. */ - public TestRequest header(String name, String value) { + public TestRequest header(Http.HeaderName name, String value) { Objects.requireNonNull(name, "Parameter 'name' is null!"); Objects.requireNonNull(name, "Parameter 'value' is null!"); - headers.computeIfAbsent(name, k -> new ArrayList<>()).add(value); + headers.computeIfAbsent(name.defaultCase(), k -> new ArrayList<>()).add(value); return this; } @@ -288,7 +288,7 @@ public TestResponse trace() throws InterruptedException, TimeoutException { * @throws InterruptedException if thread is interrupted. * @throws TimeoutException if request timeout is reached. */ - public TestResponse call(Http.RequestMethod method, MediaPublisher mediaPublisher) + public TestResponse call(Http.Method method, MediaPublisher mediaPublisher) throws InterruptedException, TimeoutException { if (mediaPublisher != null && !headers.containsKey(Http.Header.CONTENT_TYPE) && mediaPublisher.mediaType() != null) { header(Http.Header.CONTENT_TYPE, mediaPublisher.mediaType().toString()); @@ -304,7 +304,7 @@ public TestResponse call(Http.RequestMethod method, MediaPublisher mediaPublishe * @throws InterruptedException if thread is interrupted. * @throws TimeoutException if request timeout is reached. */ - public TestResponse call(Http.RequestMethod method) throws InterruptedException, TimeoutException { + public TestResponse call(Http.Method method) throws InterruptedException, TimeoutException { return testClient.call(method, version, uri(), headers, null); } diff --git a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestResponse.java b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestResponse.java index 8532e48443e..5cc6637f916 100644 --- a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestResponse.java +++ b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,10 +22,10 @@ import java.util.Map; import java.util.concurrent.CompletableFuture; +import io.helidon.common.http.Headers; +import io.helidon.common.http.HeadersWritable; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; -import io.helidon.common.http.ReadOnlyParameters; +import io.helidon.common.http.HttpMediaType; import io.helidon.webserver.WebServer; /** @@ -33,8 +33,8 @@ */ public class TestResponse { - private final Http.ResponseStatus status; - private final Parameters headers; + private final Http.Status status; + private final HeadersWritable headers; // todo Needs much better solution. private final TestClient.TestBareResponse bareResponse; @@ -45,11 +45,11 @@ public class TestResponse { * @param headers HTTP headers * @param bareResponse the test bare response */ - TestResponse(Http.ResponseStatus status, + TestResponse(Http.Status status, Map> headers, TestClient.TestBareResponse bareResponse) { this.status = status; - this.headers = new ReadOnlyParameters(headers); + this.headers = HeadersWritable.create(); this.bareResponse = bareResponse; } @@ -58,7 +58,7 @@ public class TestResponse { * * @return an HTTP status code. */ - public Http.ResponseStatus status() { + public Http.Status status() { return status; } @@ -67,7 +67,7 @@ public Http.ResponseStatus status() { * * @return response headers. */ - public Parameters headers() { + public Headers headers() { return headers; } @@ -90,8 +90,8 @@ public CompletableFuture asBytes() { */ public CompletableFuture asString() { Charset charset = headers.first(Http.Header.CONTENT_TYPE) - .map(MediaType::parse) - .flatMap(MediaType::charset) + .map(HttpMediaType::create) + .flatMap(HttpMediaType::charset) .map(s -> { try { return Charset.forName(s); diff --git a/webserver/test-support/src/test/java/io/helidon/webserver/testsupport/TestClientTest.java b/webserver/test-support/src/test/java/io/helidon/webserver/testsupport/TestClientTest.java index 47663c4cd85..7c3bc460c6e 100644 --- a/webserver/test-support/src/test/java/io/helidon/webserver/testsupport/TestClientTest.java +++ b/webserver/test-support/src/test/java/io/helidon/webserver/testsupport/TestClientTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -288,7 +288,7 @@ public void implicitNotFound() throws Exception { @Test public void advancingToDefaultErrorHandler() throws Exception { StringBuffer sb = new StringBuffer(); - HttpException exception = new HttpException("test-exception", Http.ResponseStatus.create(777)); + HttpException exception = new HttpException("test-exception", Http.Status.create(777)); Routing routing = Routing.builder() .any((req, res) -> { @@ -325,12 +325,12 @@ public void advancingToDefaultErrorHandler() throws Exception { @Test public void throwingExceptionInErrorHandler() throws Exception { StringBuffer sb = new StringBuffer(); - Http.ResponseStatus expected = Http.ResponseStatus.create(888); + Http.Status expected = Http.Status.create(888); Routing routing = Routing.builder() .any((req, res) -> { sb.append("any-"); - throw new HttpException("original-exception", Http.ResponseStatus.create(777)); + throw new HttpException("original-exception", Http.Status.create(777)); }) .error(HttpException.class, (req, res, ex) -> { sb.append("httpExceptionHandler-"); @@ -365,7 +365,7 @@ public void nextInErrorWhenHeadersSent() throws Exception { } catch (Exception e) { fail("Should not have gotten an exception."); } - throw new HttpException("test-exception", Http.ResponseStatus.create(400)); + throw new HttpException("test-exception", Http.Status.create(400)); }) .error(Throwable.class, (req, res, ex) -> { sb.append("throwableHandler"); diff --git a/webserver/tyrus/src/main/java/io/helidon/webserver/tyrus/TyrusSupport.java b/webserver/tyrus/src/main/java/io/helidon/webserver/tyrus/TyrusSupport.java new file mode 100644 index 00000000000..9479c47b400 --- /dev/null +++ b/webserver/tyrus/src/main/java/io/helidon/webserver/tyrus/TyrusSupport.java @@ -0,0 +1,318 @@ +/* + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.webserver.tyrus; + +import java.net.URI; +import java.nio.ByteBuffer; +import java.util.Collections; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutorService; +import java.util.logging.Logger; + +import io.helidon.common.http.Http; +import io.helidon.webserver.Handler; +import io.helidon.webserver.Routing; +import io.helidon.webserver.ServerRequest; +import io.helidon.webserver.ServerResponse; +import io.helidon.webserver.Service; + +import jakarta.websocket.DeploymentException; +import jakarta.websocket.Extension; +import jakarta.websocket.server.HandshakeRequest; +import jakarta.websocket.server.ServerEndpointConfig; +import org.glassfish.tyrus.core.RequestContext; +import org.glassfish.tyrus.core.TyrusUpgradeResponse; +import org.glassfish.tyrus.core.TyrusWebSocketEngine; +import org.glassfish.tyrus.server.TyrusServerContainer; +import org.glassfish.tyrus.spi.Connection; +import org.glassfish.tyrus.spi.WebSocketEngine; + +import static java.nio.charset.StandardCharsets.UTF_8; + +/** + * Class TyrusSupport implemented as a Helidon service. + */ +public class TyrusSupport implements Service { + private static final Logger LOGGER = Logger.getLogger(TyrusSupport.class.getName()); + + /** + * A zero-length buffer indicates a connection flush to Helidon. + */ + private static final ByteBuffer FLUSH_BUFFER = ByteBuffer.allocateDirect(0); + + private final WebSocketEngine engine; + private final TyrusHandler handler = new TyrusHandler(); + private Set> endpointClasses; + private Set endpointConfigs; + private Set extensions; + + /** + * Create from another instance. + * + * @param other The other instance. + */ + protected TyrusSupport(TyrusSupport other) { + this.engine = other.engine; + this.endpointClasses = other.endpointClasses; + this.endpointConfigs = other.endpointConfigs; + this.extensions = other.extensions; + } + + TyrusSupport( + WebSocketEngine engine, + Set> endpointClasses, + Set endpointConfigs, + Set extensions) { + this.engine = engine; + this.endpointClasses = endpointClasses; + this.endpointConfigs = endpointConfigs; + this.extensions = extensions; + } + + /** + * Register our WebSocket handler for all routes. Once a request is received, + * it will be forwarded to the next handler if not a protocol upgrade request. + * + * @param routingRules Routing rules to update. + */ + @Override + public void update(Routing.Rules routingRules) { + LOGGER.info("Updating TyrusSupport routing routes"); + routingRules.any(handler); + } + + /** + * Access to endpoint classes. + * + * @return Immutable set of end endpoint classes. + */ + public Set> endpointClasses() { + return Collections.unmodifiableSet(endpointClasses); + } + + /** + * Access to endpoint configs. + * + * @return Immutable set of end endpoint configs. + */ + public Set endpointConfigs() { + return Collections.unmodifiableSet(endpointConfigs); + } + + /** + * Access to extensions. + * + * @return Immutable set of extensions. + */ + public Set extensions() { + return Collections.unmodifiableSet(extensions); + } + + /** + * Returns executor service, can be overridden. + * + * @return Executor service or {@code null}. + */ + protected ExecutorService executorService() { + return null; + } + + /** + * Creates a builder for this class. + * + * @return A builder for this class. + */ + public static Builder builder() { + return new Builder(); + } + + /** + * Builder for convenient way to create {@link TyrusSupport}. + */ + public static class Builder implements io.helidon.common.Builder { + + private Set> endpointClasses = new HashSet<>(); + private Set endpointConfigs = new HashSet<>(); + private Set extensions = new HashSet<>(); + + private Builder() { + } + + /** + * Register an endpoint class. + * + * @param endpointClass The class. + * @return The builder. + */ + public Builder register(Class endpointClass) { + endpointClasses.add(endpointClass); + return this; + } + + /** + * Register an endpoint config. + * + * @param endpointConfig The endpoint config. + * @return The builder. + */ + public Builder register(ServerEndpointConfig endpointConfig) { + endpointConfigs.add(endpointConfig); + return this; + } + + /** + * Register an extension. + * + * @param extension The extension. + * @return The builder. + */ + public Builder register(Extension extension) { + extensions.add(extension); + return this; + } + + @Override + public TyrusSupport build() { + // a purposefully mutable extensions + Set installedExtensions = new HashSet<>(extensions); + // Create container and WebSocket engine + TyrusServerContainer serverContainer = new TyrusServerContainer(endpointClasses) { + private final WebSocketEngine engine = + TyrusWebSocketEngine.builder(this).build(); + + @Override + public void register(Class endpointClass) { + throw new UnsupportedOperationException("Use TyrusWebSocketEngine for registration"); + } + + @Override + public void register(ServerEndpointConfig serverEndpointConfig) { + throw new UnsupportedOperationException("Use TyrusWebSocketEngine for registration"); + } + + @Override + public Set getInstalledExtensions() { + return installedExtensions; + } + + @Override + public WebSocketEngine getWebSocketEngine() { + return engine; + } + }; + + // Register classes with context path "/" + WebSocketEngine engine = serverContainer.getWebSocketEngine(); + endpointClasses.forEach(c -> { + try { + // Context path handled by Helidon based on app's routes + engine.register(c, "/"); + } catch (DeploymentException e) { + throw new RuntimeException(e); + } + }); + endpointConfigs.forEach(c -> { + try { + // Context path handled by Helidon based on app's routes + engine.register(c, "/"); + } catch (DeploymentException e) { + throw new RuntimeException(e); + } + }); + + // Create TyrusSupport using WebSocket engine + return new TyrusSupport(serverContainer.getWebSocketEngine(), endpointClasses, endpointConfigs, extensions); + } + } + + /** + * A Helidon handler that integrates with Tyrus and can process WebSocket + * upgrade requests. + */ + private class TyrusHandler implements Handler { + private static final Http.HeaderName SEC_WEBSOCKET_KEY = Http.Header.create(HandshakeRequest.SEC_WEBSOCKET_KEY); + + /** + * Process a server request/response. + * + * @param req an HTTP server request. + * @param res an HTTP server response. + */ + @Override + public void accept(ServerRequest req, ServerResponse res) { + // Skip this handler if not an upgrade request + Optional secWebSocketKey = req.headers().value(SEC_WEBSOCKET_KEY); + if (secWebSocketKey.isEmpty()) { + req.next(); + return; + } + + LOGGER.fine("Initiating WebSocket handshake ..."); + + // Create Tyrus request context and copy request headers + RequestContext requestContext = RequestContext.Builder.create() + .requestURI(URI.create(req.path().toString())) // excludes context path + .build(); + req.headers().toMap().forEach((key, value) -> requestContext.getHeaders().put(key, value)); + + // Use Tyrus to process a WebSocket upgrade request + final TyrusUpgradeResponse upgradeResponse = new TyrusUpgradeResponse(); + final WebSocketEngine.UpgradeInfo upgradeInfo = engine.upgrade(requestContext, upgradeResponse); + + // Respond to upgrade request using response from Tyrus + res.status(upgradeResponse.getStatus()); + upgradeResponse.getHeaders().forEach((key, value) -> res.headers().set(Http.Header.create(key), value)); + TyrusWriterPublisher publisherWriter = new TyrusWriterPublisher(); + res.send(publisherWriter); + + // Write reason for failure if not successful + if (upgradeInfo.getStatus() != WebSocketEngine.UpgradeStatus.SUCCESS) { + String reason = upgradeResponse.getReasonPhrase(); + if (reason != null) { + publisherWriter.write(ByteBuffer.wrap(reason.getBytes(UTF_8)), null); + } + } + + // Flush upgrade response + publisherWriter.write(FLUSH_BUFFER, null); + + // Setup the WebSocket connection and subscriber, calls @onOpen + ExecutorService executorService = executorService(); + if (executorService != null) { + CompletableFuture future = + CompletableFuture.supplyAsync( + () -> upgradeInfo.createConnection(publisherWriter, + closeReason -> LOGGER.fine(() -> "Connection closed: " + closeReason)), + executorService); + future.thenAccept(c -> { + TyrusReaderSubscriber subscriber = new TyrusReaderSubscriber(c, executorService); + req.content().subscribe(subscriber); + }); + } else { + Connection connection = upgradeInfo.createConnection(publisherWriter, + closeReason -> LOGGER.fine(() -> "Connection closed: " + closeReason)); + if (connection != null) { + TyrusReaderSubscriber subscriber = new TyrusReaderSubscriber(connection); + req.content().subscribe(subscriber); + } + } + } + } +} diff --git a/webserver/webserver/pom.xml b/webserver/webserver/pom.xml index 377a727798c..867101de8f4 100644 --- a/webserver/webserver/pom.xml +++ b/webserver/webserver/pom.xml @@ -34,85 +34,6 @@ etc/spotbugs/exclude.xml - - - - org.apache.maven.plugins - maven-compiler-plugin - - true - - - - org.apache.maven.plugins - maven-surefire-plugin - - - ${project.build.testOutputDirectory}/logging-test.properties - - - - - - default-test - - test - - test - - - **/DataChunkReleaseTest.java - - - - - - datachunk-release-test - - test - - test - - - **/DataChunkReleaseTest.java - - - - - - - org.apache.maven.plugins - maven-failsafe-plugin - - - - integration-test - verify - - - - - - **/*TckTest.java - - - - - org.apache.maven.surefire - surefire-testng - ${version.lib.surefire.testng} - - - - - - io.helidon.common @@ -122,6 +43,10 @@ io.helidon.common helidon-common + + io.helidon.common + helidon-common-features + io.helidon.common helidon-common-http @@ -230,5 +155,96 @@ reactive-streams-tck-flow test + + io.helidon.common.testing + helidon-common-testing-http + test + + + io.helidon.logging + helidon-logging-jul + test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + true + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${project.build.testOutputDirectory}/logging-test.properties + + + + + + default-test + + test + + test + + + **/DataChunkReleaseTest.java + + + + + + datachunk-release-test + + test + + test + + + **/DataChunkReleaseTest.java + + + + + + + org.apache.maven.plugins + maven-failsafe-plugin + + + + integration-test + verify + + + + + + **/*TckTest.java + + + + + org.apache.maven.surefire + surefire-testng + ${version.lib.surefire.testng} + + + + + + + diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/AlreadyCompletedException.java b/webserver/webserver/src/main/java/io/helidon/webserver/AlreadyCompletedException.java new file mode 100644 index 00000000000..22f04f5ae90 --- /dev/null +++ b/webserver/webserver/src/main/java/io/helidon/webserver/AlreadyCompletedException.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.webserver; + +/** + * Signals that a mutation method has been invoked on a resource that is already completed. + * + * It is no longer possible to mute state of these objects. + */ +public class AlreadyCompletedException extends IllegalStateException { + + /** + * Constructs an {@link AlreadyCompletedException} with the specified detail message. + * + * @param s the String that contains a detailed message. + */ + public AlreadyCompletedException(String s) { + super(s); + } + + /** + * Constructs an {@link AlreadyCompletedException} with the specified detail message and cause. + * + * @param message the detail message (which is saved for later retrieval by the {@link Throwable#getMessage()} method). + * @param cause the cause (which is saved for later retrieval by the {@link Throwable#getCause()} method). + * (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + public AlreadyCompletedException(String message, Throwable cause) { + super(message, cause); + } + + /** + * Constructs an {@link AlreadyCompletedException} with the specified cause. + * + * @param cause the cause (which is saved for later retrieval by the {@link Throwable#getCause()} method). + * (A {@code null} value is permitted, and indicates that the cause is nonexistent or unknown.) + */ + public AlreadyCompletedException(Throwable cause) { + super(cause); + } +} diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/BareRequest.java b/webserver/webserver/src/main/java/io/helidon/webserver/BareRequest.java index 3c86e3b8510..25bf8632189 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/BareRequest.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/BareRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,6 @@ package io.helidon.webserver; import java.net.URI; -import java.util.List; -import java.util.Map; import java.util.concurrent.Flow; import io.helidon.common.http.DataChunk; @@ -43,7 +41,7 @@ public interface BareRequest { * * @return an HTTP method. */ - Http.RequestMethod method(); + Http.Method method(); /** * Gets an HTTP version from the request line such as {@code HTTP/1.1}. @@ -99,7 +97,7 @@ public interface BareRequest { * * @return representing http headers. */ - Map> headers(); + RequestHeaders headers(); /** * Gets the Flow Publisher that allows a subscription for request body chunks. diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/BareRequestImpl.java b/webserver/webserver/src/main/java/io/helidon/webserver/BareRequestImpl.java index 8a10048e34f..d870a30f560 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/BareRequestImpl.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/BareRequestImpl.java @@ -19,10 +19,6 @@ import java.net.InetSocketAddress; import java.net.SocketAddress; import java.net.URI; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.concurrent.CompletableFuture; import java.util.concurrent.Flow; @@ -70,8 +66,8 @@ public WebServer webServer() { } @Override - public Http.RequestMethod method() { - return Http.RequestMethod.create(nettyRequest.method().name()); + public Http.Method method() { + return Http.Method.create(nettyRequest.method().name()); } @Override @@ -121,14 +117,8 @@ public boolean isSecure() { } @Override - public Map> headers() { - HashMap> map = new HashMap<>(); - - for (Map.Entry entry : nettyRequest.headers().entries()) { - map.computeIfAbsent(entry.getKey(), s -> new ArrayList<>()).add(entry.getValue()); - } - - return map; + public RequestHeaders headers() { + return new NettyRequestHeaders(nettyRequest.headers()); } @Override diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/BareResponse.java b/webserver/webserver/src/main/java/io/helidon/webserver/BareResponse.java index 3f9a0a38230..e84f0b03151 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/BareResponse.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/BareResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ public interface BareResponse extends Flow.Subscriber { * @throws SocketClosedException if headers were already send or response is closed * @throws NullPointerException if {@code status} is {@code null} */ - void writeStatusAndHeaders(Http.ResponseStatus status, Map> headers) + void writeStatusAndHeaders(Http.Status status, Map> headers) throws SocketClosedException, NullPointerException; /** @@ -59,7 +59,7 @@ void writeStatusAndHeaders(Http.ResponseStatus status, Map> Single whenCompleted(); /** - * Each response is subscribed up to a single publisher and AFTER {@link #writeStatusAndHeaders(Http.ResponseStatus, Map)} + * Each response is subscribed up to a single publisher and AFTER {@link #writeStatusAndHeaders(Http.Status, Map)} * method is called and returned. * * @param subscription a subscription. diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/BareResponseImpl.java b/webserver/webserver/src/main/java/io/helidon/webserver/BareResponseImpl.java index 65123fb6e77..b3898103a79 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/BareResponseImpl.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/BareResponseImpl.java @@ -151,14 +151,15 @@ private void channelClosed(Future future) { } @Override - public void writeStatusAndHeaders(Http.ResponseStatus status, Map> headers) { + public void writeStatusAndHeaders(Http.Status status, Map> headers) { Objects.requireNonNull(status, "Parameter 'statusCode' was null!"); if (!statusHeadersSent.compareAndSet(false, true)) { throw new IllegalStateException("Status and headers were already sent"); } HttpResponseStatus nettyStatus; - if (status instanceof Http.Status || status.reasonPhrase() == null) { + + if (status.reasonPhrase() == null) { // default reason phrase nettyStatus = valueOf(status.code()); } else { diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/DirectHandler.java b/webserver/webserver/src/main/java/io/helidon/webserver/DirectHandler.java index f8efcd6f45e..665302ce42d 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/DirectHandler.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/DirectHandler.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import java.util.Optional; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; /** * A handler that is invoked when a response is sent outside of routing. @@ -47,7 +47,7 @@ public interface DirectHandler { */ default TransportResponse handle(TransportRequest request, EventType eventType, - Http.ResponseStatus defaultStatus, + Http.Status defaultStatus, Throwable t) { return handle(request, eventType, defaultStatus, t.getMessage()); } @@ -66,7 +66,7 @@ default TransportResponse handle(TransportRequest request, */ TransportResponse handle(TransportRequest request, EventType eventType, - Http.ResponseStatus defaultStatus, + Http.Status defaultStatus, String message); /** @@ -126,7 +126,7 @@ enum EventType { * Response to correctly reply to the original client. */ class TransportResponse { - private final Http.ResponseStatus status; + private final Http.Status status; private final Map> headers; private final byte[] entity; @@ -155,7 +155,7 @@ public static TransportResponse create(String message) { return builder().entity(message).build(); } - Http.ResponseStatus status() { + Http.Status status() { return status; } @@ -173,7 +173,7 @@ Optional entity() { public static class Builder implements io.helidon.common.Builder { private final Map> headers = new HashMap<>(); - private Http.ResponseStatus status = Http.Status.BAD_REQUEST_400; + private Http.Status status = Http.Status.BAD_REQUEST_400; private byte[] entity; private Builder() { @@ -190,7 +190,7 @@ public TransportResponse build() { * @param status status to use, default is bad request * @return updated builder */ - public Builder status(Http.ResponseStatus status) { + public Builder status(Http.Status status) { this.status = status; return this; } @@ -204,7 +204,7 @@ public Builder status(Http.ResponseStatus status) { * @throws java.lang.IllegalArgumentException if an attempt is made to modify protected headers (such as Connection) */ public Builder header(String name, String... values) { - if (name.equalsIgnoreCase(Http.Header.CONNECTION)) { + if (name.equalsIgnoreCase(Http.Header.CONNECTION.lowerCase())) { throw new IllegalArgumentException( "Connection header cannot be overridden, it is always set to Close fro transport errors"); } @@ -222,7 +222,7 @@ public Builder header(String name, String... values) { * @return updated builder */ public Builder entity(String entity) { - this.headers.putIfAbsent(Http.Header.CONTENT_TYPE, List.of(MediaType.TEXT_PLAIN.toString())); + this.headers.putIfAbsent(Http.Header.CONTENT_TYPE.defaultCase(), List.of(HttpMediaType.PLAINTEXT_UTF_8.text())); return entity(HtmlEncoder.encode(entity).getBytes(StandardCharsets.UTF_8)); } @@ -238,9 +238,9 @@ public Builder entity(String entity) { public Builder entity(byte[] entity) { this.entity = Arrays.copyOf(entity, entity.length); if (this.entity.length == 0) { - this.headers.remove(Http.Header.CONTENT_LENGTH); + this.headers.remove(Http.Header.CONTENT_LENGTH.defaultCase()); } else { - this.header(Http.Header.CONTENT_LENGTH, String.valueOf(entity.length)); + this.header(Http.Header.CONTENT_LENGTH.defaultCase(), String.valueOf(entity.length)); } return this; } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/DirectHandlers.java b/webserver/webserver/src/main/java/io/helidon/webserver/DirectHandlers.java index 6603ba87ad2..99ce3b273d4 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/DirectHandlers.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/DirectHandlers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -62,7 +62,7 @@ private static class DefaultHandler implements DirectHandler { @Override public TransportResponse handle(TransportRequest request, EventType eventType, - Http.ResponseStatus defaultStatus, + Http.Status defaultStatus, String message) { TransportResponse.Builder builder = TransportResponse.builder() diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/ForwardingHandler.java b/webserver/webserver/src/main/java/io/helidon/webserver/ForwardingHandler.java index 3f2aaa96463..01327769906 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/ForwardingHandler.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/ForwardingHandler.java @@ -286,10 +286,10 @@ private boolean channelReadHttpRequest(ChannelHandlerContext ctx, Context reques } // Certificate management - request.headers().remove(Http.Header.X_HELIDON_CN); + request.headers().remove(Http.Header.X_HELIDON_CN).defaultCase(); String cn = ctx.channel().attr(CLIENT_CERTIFICATE_NAME).get(); if (cn != null) { - request.headers().set(Http.Header.X_HELIDON_CN, cn); + request.headers().set(Http.Header.X_HELIDON_CN.defaultCase(), cn); } // If the client x509 certificate is present on the channel, add it to the context scope of the ongoing @@ -547,7 +547,7 @@ private void send413PayloadTooLarge(ChannelHandlerContext ctx, HttpRequest reque private FullHttpResponse toNettyResponse(TransportResponse handlerResponse) { Optional entity = handlerResponse.entity(); - Http.ResponseStatus status = handlerResponse.status(); + Http.Status status = handlerResponse.status(); Map> headers = handlerResponse.headers(); HttpResponseStatus nettyStatus = HttpResponseStatus.valueOf(status.code(), status.reasonPhrase()); diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/HandlerRoute.java b/webserver/webserver/src/main/java/io/helidon/webserver/HandlerRoute.java index 8fdd26dec6f..18b0cc8b377 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/HandlerRoute.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/HandlerRoute.java @@ -38,7 +38,7 @@ class HandlerRoute implements HttpRoute { private final PathMatcher pathMatcher; private final Handler handler; private final List serviceContext; - private final HttpMethodPredicate methodPredicate; + private final Http.MethodPredicate methodPredicate; private final Map diagnosticEvent; @@ -50,22 +50,22 @@ class HandlerRoute implements HttpRoute { * @param handler an effective handler for the matcher * @param methods accepted methods. If empty then all methods are accepted */ - HandlerRoute(List serviceContext, PathMatcher pathMatcher, Handler handler, Iterable methods) { + HandlerRoute(List serviceContext, PathMatcher pathMatcher, Handler handler, Iterable methods) { if (serviceContext == null || serviceContext.isEmpty()) { this.serviceContext = Collections.emptyList(); } else { this.serviceContext = new ArrayList<>(serviceContext); } if (methods == null) { - this.methodPredicate = new HttpMethodPredicate(null); + this.methodPredicate = Http.Method.predicate(); } else if (methods instanceof Collection) { - this.methodPredicate = new HttpMethodPredicate((Collection) methods); + this.methodPredicate = Http.Method.predicate((Collection) methods); } else { - Collection mtds = new ArrayList<>(); - for (Http.RequestMethod method : methods) { + Collection mtds = new ArrayList<>(); + for (Http.Method method : methods) { mtds.add(method); } - this.methodPredicate = new HttpMethodPredicate(mtds); + this.methodPredicate = Http.Method.predicate(mtds); } this.pathMatcher = pathMatcher == null ? EMPTY_PATH_MATCHER : pathMatcher; this.handler = handler; @@ -90,7 +90,7 @@ class HandlerRoute implements HttpRoute { * @param handler an effective handler for the matcher * @param methods accepted methods. If empty then all methods are accepted */ - HandlerRoute(List serviceContext, PathMatcher pathMatcher, Handler handler, Http.RequestMethod... methods) { + HandlerRoute(List serviceContext, PathMatcher pathMatcher, Handler handler, Http.Method... methods) { this(serviceContext, pathMatcher, handler, Arrays.asList(methods)); } @@ -101,7 +101,7 @@ class HandlerRoute implements HttpRoute { * @param handler an effective handler for the matcher. * @param methods accepted methods. If empty then all methods are accepted. */ - HandlerRoute(List serviceContext, Handler handler, Http.RequestMethod... methods) { + HandlerRoute(List serviceContext, Handler handler, Http.Method... methods) { this(serviceContext, EMPTY_PATH_MATCHER, handler, methods); } @@ -112,17 +112,17 @@ class HandlerRoute implements HttpRoute { * @param handler an effective handler for the matcher. * @param methods accepted methods. If empty then all methods are accepted. */ - HandlerRoute(List serviceContext, Handler handler, Iterable methods) { + HandlerRoute(List serviceContext, Handler handler, Iterable methods) { this(serviceContext, EMPTY_PATH_MATCHER, handler, methods); } @Override - public Set acceptedMethods() { + public Set acceptedMethods() { return methodPredicate.acceptedMethods(); } @Override - public boolean accepts(Http.RequestMethod method) { + public boolean accepts(Http.Method method) { return methodPredicate.test(method); } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/HashRequestHeaders.java b/webserver/webserver/src/main/java/io/helidon/webserver/HashRequestHeaders.java deleted file mode 100644 index fe20d53cb57..00000000000 --- a/webserver/webserver/src/main/java/io/helidon/webserver/HashRequestHeaders.java +++ /dev/null @@ -1,263 +0,0 @@ -/* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.webserver; - -import java.net.URI; -import java.time.ZonedDateTime; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Objects; -import java.util.Optional; -import java.util.OptionalLong; -import java.util.stream.Collectors; - -import io.helidon.common.LazyList; -import io.helidon.common.LazyValue; -import io.helidon.common.http.HashParameters; -import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; -import io.helidon.common.http.ReadOnlyParameters; -import io.helidon.common.http.Utils; - -/** - * A {@link RequestHeaders} implementation on top of {@link ReadOnlyParameters}. - */ -class HashRequestHeaders extends ReadOnlyParameters implements RequestHeaders { - - /** - * Header value of the non compliant {@code Accept} header sent by - * {@link java.net.HttpURLConnection} when none is set. - * @see JDK-8163921 - */ - static final String HUC_ACCEPT_DEFAULT = "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2"; - - /** - * Accepted types for {@link #HUC_ACCEPT_DEFAULT}. - */ - private static final List HUC_ACCEPT_DEFAULT_TYPES = List.of( - MediaType.TEXT_HTML, - MediaType.parse("image/gif"), - MediaType.parse("image/jpeg"), - MediaType.parse("*/*; q=.2")); - - private final Object internalLock = new Object(); - private volatile Parameters cookies; - private List acceptedtypesCache; - - /** - * Creates a new instance. - */ - HashRequestHeaders() { - this(null); - } - - /** - * Creates a new instance from provided data. - * Initial data are copied. - * - * @param initialContent initial content. - */ - HashRequestHeaders(Map> initialContent) { - super(initialContent); - } - - @Override - public Optional contentType() { - return first(Http.Header.CONTENT_TYPE).map(MediaType::parse); - } - - @Override - public OptionalLong contentLength() { - Optional v = first(Http.Header.CONTENT_LENGTH); - if (v.isPresent()) { - return OptionalLong.of(Long.parseLong(v.get())); - } else { - return OptionalLong.empty(); - } - } - - @Override - public Parameters cookies() { - Parameters lCookies = this.cookies; - if (lCookies == null) { - synchronized (internalLock) { - lCookies = this.cookies; - if (lCookies == null) { - List list = all(Http.Header.COOKIE).stream() - .map(CookieParser::parse) - .collect(Collectors.toList()); - lCookies = Parameters.toUnmodifiableParameters(HashParameters.concat(list)); - this.cookies = lCookies; - } - } - } - return lCookies; - } - - @Override - public List acceptedTypes() { - List result = this.acceptedtypesCache; - if (result == null) { - List acceptValues = all(Http.Header.ACCEPT); - - if (acceptValues.size() == 1 && HUC_ACCEPT_DEFAULT.equals(acceptValues.get(0))) { - result = HUC_ACCEPT_DEFAULT_TYPES; - - } else { - result = LazyList.create(acceptValues.stream() - .flatMap(h -> Utils.tokenize(',', "\"", false, h).stream()) - .map(String::trim) - .map(s -> LazyValue.create(() -> MediaType.parse(s))) - .collect(Collectors.toList())); - } - - result = Collections.unmodifiableList(result); - this.acceptedtypesCache = result; - } - return result; - } - - @Override - public boolean isAccepted(MediaType mediaType) { - Objects.requireNonNull(mediaType, "Parameter 'mediaType' is null!"); - List acceptedTypes = acceptedTypes(); - return acceptedTypes.isEmpty() || acceptedTypes.stream().anyMatch(mediaType); - } - - @Override - public Optional bestAccepted(MediaType... mediaTypes) { - if (mediaTypes == null || mediaTypes.length == 0) { - return Optional.empty(); - } - List accepts = acceptedTypes(); - if (accepts == null || accepts.isEmpty()) { - return Optional.ofNullable(mediaTypes[0]); - } - - double best = 0; - MediaType result = null; - for (MediaType mt : mediaTypes) { - if (mt != null) { - for (MediaType acc : accepts) { - double q = acc.qualityFactor(); - if (q > best && acc.test(mt)) { - if (q == 1) { - return Optional.of(mt); - } else { - best = q; - result = mt; - } - } - } - } - } - return Optional.ofNullable(result); - } - - @Override - public Optional acceptDatetime() { - return first(Http.Header.ACCEPT_DATETIME).map(Http.DateTime::parse); - } - - @Override - public Optional date() { - return first(Http.Header.DATE).map(Http.DateTime::parse); - } - - @Override - public Optional ifModifiedSince() { - return first(Http.Header.IF_MODIFIED_SINCE).map(Http.DateTime::parse); - } - - @Override - public Optional ifUnmodifiedSince() { - return first(Http.Header.IF_UNMODIFIED_SINCE).map(Http.DateTime::parse); - } - - @Override - public Optional referer() { - return first(Http.Header.REFERER).map(URI::create); - } - - /** - * Parse cookies based on RFC6265 but it can accepts also older formats including RFC2965 but skips parameters. - */ - static class CookieParser { - - private CookieParser() { - } - - private static final String RFC2965_VERSION = "$Version"; - private static final String RFC2965_PATH = "$Path"; - private static final String RFC2965_DOMAIN = "$Domain"; - private static final String RFC2965_PORT = "$Port"; - - /** - * Parse cookies based on RFC6265 but it can accepts also older formats including RFC2965 but skips parameters. - * - *

Multiple cookies can be returned in a single headers and a single cookie-name can have multiple values. - * Note that base on RFC6265 an order of cookie values has no semantics. - * - * @param cookieHeaderValue a value of '{@code Cookie:}' header. - * @return a cookie name and values parsed into a parameter format. - */ - public static Parameters parse(String cookieHeaderValue) { - if (cookieHeaderValue == null) { - return empty(); - } - cookieHeaderValue = cookieHeaderValue.trim(); - if (cookieHeaderValue.isEmpty()) { - return empty(); - } - - // Beware RFC2965 - boolean isRfc2965 = false; - if (cookieHeaderValue.regionMatches(true, 0, RFC2965_VERSION, 0, RFC2965_VERSION.length())) { - isRfc2965 = true; - int ind = cookieHeaderValue.indexOf(';'); - if (ind < 0) { - return empty(); - } else { - cookieHeaderValue = cookieHeaderValue.substring(ind + 1); - } - } - - Parameters result = HashParameters.create(); - for (String baseToken : Utils.tokenize(',', "\"", false, cookieHeaderValue)) { - for (String token : Utils.tokenize(';', "\"", false, baseToken)) { - int eqInd = token.indexOf('='); - if (eqInd > 0) { - String name = token.substring(0, eqInd).trim(); - if (name.isEmpty()) { - continue; // Name MOST NOT be empty; - } - if (isRfc2965 && name.charAt(0) == '$' - && (RFC2965_PATH.equalsIgnoreCase(name) || RFC2965_DOMAIN.equalsIgnoreCase(name) - || RFC2965_PORT.equalsIgnoreCase(name) || RFC2965_VERSION.equalsIgnoreCase(name))) { - continue; // Skip RFC2965 attributes - } - String value = token.substring(eqInd + 1).trim(); - result.add(name, Utils.unwrap(value)); - } - } - } - return result; - } - } -} diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/HashResponseHeaders.java b/webserver/webserver/src/main/java/io/helidon/webserver/HashResponseHeaders.java index 825f80356a4..374b77f9667 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/HashResponseHeaders.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/HashResponseHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,40 +16,31 @@ package io.helidon.webserver; -import java.net.URI; -import java.time.Duration; -import java.time.Instant; import java.time.ZoneId; import java.time.ZonedDateTime; import java.util.ArrayList; -import java.util.Collections; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Objects; -import java.util.Optional; -import java.util.OptionalLong; +import java.util.TreeMap; import java.util.concurrent.CompletableFuture; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; import java.util.function.Consumer; -import java.util.function.Function; import java.util.function.Supplier; -import java.util.stream.Collectors; import io.helidon.common.LazyValue; -import io.helidon.common.http.AlreadyCompletedException; -import io.helidon.common.http.HashParameters; +import io.helidon.common.http.HeadersServerResponse; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.http.SetCookie; -import io.helidon.common.http.Utils; import io.helidon.common.reactive.Single; /** - * A {@link ResponseHeaders} implementation on top of {@link HashParameters}. + * A {@link ResponseHeaders} implementation. */ -class HashResponseHeaders extends HashParameters implements ResponseHeaders { +class HashResponseHeaders implements ResponseHeaders { private static final String COMPLETED_EXCEPTION_MESSAGE = "Response headers are already completed (sent to the client)!"; @@ -57,9 +48,10 @@ class HashResponseHeaders extends HashParameters implements ResponseHeaders { () -> ZonedDateTime.of(1970, 1, 1, 0, 0, 0, 0, ZoneId.of("GMT+0"))); // status is by default null, so we can check if it was explicitly set - private volatile Http.ResponseStatus httpStatus; + private volatile Http.Status httpStatus; private final CompletionSupport completable; private final CompletableFuture completionStage = new CompletableFuture<>(); + private final HeadersServerResponse headers = HeadersServerResponse.create(); /** * Creates a new instance. @@ -77,153 +69,11 @@ class HashResponseHeaders extends HashParameters implements ResponseHeaders { }); } // Set standard headers - this.put(Http.Header.DATE, ZonedDateTime.now().format(Http.DateTime.RFC_1123_DATE_TIME)); + this.set(Http.Header.DATE, ZonedDateTime.now().format(Http.DateTime.RFC_1123_DATE_TIME)); } - @Override - public List acceptPatches() { - List result = all(Http.Header.ACCEPT_PATCH).stream() - .flatMap(h -> Utils.tokenize(',', "\"", false, h).stream()) - .map(String::trim) - .map(MediaType::parse) - .collect(Collectors.toList()); - return Collections.unmodifiableList(result); - } - - @Override - public void addAcceptPatches(MediaType... acceptableMediaTypes) { - if (acceptableMediaTypes == null) { - return; - } - for (MediaType mt : acceptableMediaTypes) { - add(Http.Header.ACCEPT_PATCH, mt.toString()); - } - } - - @Override - public Optional contentType() { - return first(Http.Header.CONTENT_TYPE).map(MediaType::parse); - } - - @Override - public void contentType(MediaType contentType) { - if (contentType == null) { - remove(Http.Header.CONTENT_TYPE); - } else { - put(Http.Header.CONTENT_TYPE, contentType.toString()); - } - } - - @Override - public OptionalLong contentLength() { - return first(Http.Header.CONTENT_LENGTH).stream() - .mapToLong(Long::parseLong).findFirst(); - } - - @Override - public void contentLength(long contentLength) { - put(Http.Header.CONTENT_LENGTH, String.valueOf(contentLength)); - } - @Override - public Optional expires() { - return first(Http.Header.EXPIRES).map(Http.DateTime::parse); - } - - @Override - public void expires(ZonedDateTime dateTime) { - if (dateTime == null) { - remove(Http.Header.EXPIRES); - } else { - put(Http.Header.EXPIRES, dateTime.format(Http.DateTime.RFC_1123_DATE_TIME)); - } - } - - @Override - public void expires(Instant dateTime) { - if (dateTime == null) { - remove(Http.Header.EXPIRES); - } else { - ZonedDateTime dt = ZonedDateTime.ofInstant(dateTime, ZoneId.systemDefault()); - put(Http.Header.EXPIRES, dt.format(Http.DateTime.RFC_1123_DATE_TIME)); - } - } - @Override - public Optional lastModified() { - return first(Http.Header.LAST_MODIFIED).map(Http.DateTime::parse); - } - - @Override - public void lastModified(ZonedDateTime dateTime) { - if (dateTime == null) { - remove(Http.Header.LAST_MODIFIED); - } else { - put(Http.Header.LAST_MODIFIED, dateTime.format(Http.DateTime.RFC_1123_DATE_TIME)); - } - } - - @Override - public void lastModified(Instant dateTime) { - if (dateTime == null) { - remove(Http.Header.LAST_MODIFIED); - } else { - ZonedDateTime dt = ZonedDateTime.ofInstant(dateTime, ZoneId.systemDefault()); - put(Http.Header.LAST_MODIFIED, dt.format(Http.DateTime.RFC_1123_DATE_TIME)); - } - } - - @Override - public Optional location() { - return first(Http.Header.LOCATION).map(URI::create); - } - - @Override - public void location(URI location) { - if (location == null) { - remove(Http.Header.LOCATION); - } else { - put(Http.Header.LOCATION, location.toASCIIString()); - } - } - - @Override - public void addCookie(String name, String value) { - add(Http.Header.SET_COOKIE, SetCookie.create(name, value).toString()); - } - - @Override - public void addCookie(String name, String value, Duration maxAge) { - add(Http.Header.SET_COOKIE, - SetCookie.builder(name, value) - .maxAge(maxAge) - .build() - .toString()); - } - - @Override - public void addCookie(SetCookie cookie) { - Objects.requireNonNull(cookie, "Parameter 'cookie' is null!"); - add(Http.Header.SET_COOKIE, cookie.toString()); - } - - @Override - public void clearCookie(String name) { - SetCookie expiredCookie = SetCookie.builder(name, "deleted") - .path("/") - .expires(START_OF_YEAR_1970.get()) - .build(); - - List values = remove(Http.Header.SET_COOKIE); - if (values.size() == 0) { - addCookie(expiredCookie); - } else { - List newValues = values.stream().map(v -> - SetCookie.parse(v).name().equals(name) ? expiredCookie.toString() : v) - .collect(Collectors.toList()); - put(Http.Header.SET_COOKIE, newValues); - } - } @Override public boolean equals(Object o) { @@ -259,7 +109,7 @@ public int hashCode() { * * @return an HTTP status code. */ - Http.ResponseStatus httpStatus() { + Http.Status httpStatus() { return httpStatus; } @@ -268,7 +118,7 @@ Http.ResponseStatus httpStatus() { * * @param httpStatusCode an HTTP status code. */ - void httpStatus(Http.ResponseStatus httpStatusCode) { + void httpStatus(Http.Status httpStatusCode) { Objects.requireNonNull(httpStatusCode, "Parameter 'httpStatus' is null!"); completable.runIfNotCompleted(() -> this.httpStatus = httpStatusCode, "Response status code and headers are already completed (sent to the client)!"); @@ -279,62 +129,86 @@ void httpStatus(Http.ResponseStatus httpStatusCode) { // --------------------------------------------------------------------- @Override - public List put(String key, String... values) { - return completable.supplyIfNotCompleted(() -> super.put(key, values), COMPLETED_EXCEPTION_MESSAGE); + public List all(Http.HeaderName name, Supplier> defaultSupplier) { + return headers.all(name, defaultSupplier); } @Override - public List put(String key, Iterable values) { - return completable.supplyIfNotCompleted(() -> super.put(key, values), COMPLETED_EXCEPTION_MESSAGE); + public boolean contains(Http.HeaderName name) { + return headers.contains(name); } @Override - public List putIfAbsent(String key, String... values) { - return completable.supplyIfNotCompleted(() -> super.putIfAbsent(key, values), COMPLETED_EXCEPTION_MESSAGE); + public boolean contains(Http.HeaderValue value) { + return headers.contains(value); } @Override - public List putIfAbsent(String key, Iterable values) { - return completable.supplyIfNotCompleted(() -> super.putIfAbsent(key, values), COMPLETED_EXCEPTION_MESSAGE); + public Http.HeaderValue get(Http.HeaderName name) { + return headers.get(name); } @Override - public List computeIfAbsent(String key, Function> values) { - return completable.supplyIfNotCompleted(() -> super.computeIfAbsent(key, values), COMPLETED_EXCEPTION_MESSAGE); + public int size() { + return headers.size(); } @Override - public List computeSingleIfAbsent(String key, Function value) { - return completable.supplyIfNotCompleted(() -> super.computeSingleIfAbsent(key, value), COMPLETED_EXCEPTION_MESSAGE); + public HeadersServerResponse addCookie(SetCookie cookie) { + completable.runIfNotCompleted(() -> headers.addCookie(cookie), COMPLETED_EXCEPTION_MESSAGE); + return this; + } + + @Override + public HeadersServerResponse clearCookie(String name) { + completable.runIfNotCompleted(() -> headers.clearCookie(name), COMPLETED_EXCEPTION_MESSAGE); + return this; } @Override - public HashResponseHeaders putAll(Parameters parameters) { - completable.runIfNotCompleted(() -> super.putAll(parameters), COMPLETED_EXCEPTION_MESSAGE); + public HeadersServerResponse setIfAbsent(Http.HeaderValue header) { + completable.runIfNotCompleted(() -> headers.setIfAbsent(header), COMPLETED_EXCEPTION_MESSAGE); return this; } @Override - public HashResponseHeaders add(String key, String... values) { - completable.runIfNotCompleted(() -> super.add(key, values), COMPLETED_EXCEPTION_MESSAGE); + public HeadersServerResponse add(Http.HeaderValue header) { + completable.runIfNotCompleted(() -> headers.add(header), COMPLETED_EXCEPTION_MESSAGE); return this; } @Override - public HashResponseHeaders add(String key, Iterable values) { - completable.runIfNotCompleted(() -> super.add(key, values), COMPLETED_EXCEPTION_MESSAGE); + public HeadersServerResponse remove(Http.HeaderName name) { + completable.runIfNotCompleted(() -> headers.remove(name), COMPLETED_EXCEPTION_MESSAGE); return this; } @Override - public HashResponseHeaders addAll(Parameters parameters) { - completable.runIfNotCompleted(() -> super.addAll(parameters), COMPLETED_EXCEPTION_MESSAGE); + public HeadersServerResponse remove(Http.HeaderName name, Consumer removedConsumer) { + completable.runIfNotCompleted(() -> headers.remove(name, removedConsumer), COMPLETED_EXCEPTION_MESSAGE); return this; } @Override - public List remove(String key) { - return completable.supplyIfNotCompleted(() -> super.remove(key), COMPLETED_EXCEPTION_MESSAGE); + public HeadersServerResponse set(Http.HeaderValue header) { + completable.runIfNotCompleted(() -> headers.set(header), COMPLETED_EXCEPTION_MESSAGE); + return this; + } + + @Override + public ResponseHeaders clear() { + completable.runIfNotCompleted(headers::clear, COMPLETED_EXCEPTION_MESSAGE); + return this; + } + + @Override + public Iterator iterator() { + return headers.iterator(); + } + + @Override + public List acceptedTypes() { + return List.of(); } // ---------------------------------- @@ -482,9 +356,9 @@ synchronized boolean doComplete(HashResponseHeaders headers) { rwLock.writeLock().lock(); try { state = State.COMPLETED; - Http.ResponseStatus status = (null == headers.httpStatus) ? Http.Status.OK_200 : headers.httpStatus; + Http.Status status = (null == headers.httpStatus) ? Http.Status.OK_200 : headers.httpStatus; status = (null == status) ? Http.Status.OK_200 : status; - Map> rawHeaders = filterSpecificHeaders(headers.toMap(), status); + Map> rawHeaders = filterSpecificHeaders(headers, status); bareResponse.writeStatusAndHeaders(status, rawHeaders); } finally { rwLock.writeLock().unlock(); @@ -498,23 +372,25 @@ synchronized boolean doComplete(HashResponseHeaders headers) { /** * Specific status codes requires or omits specific headers. * - * @param data mutable headers data - * @param status response status code + * @param headers + * @param status response status code * @return filtered headers */ - private Map> filterSpecificHeaders(Map> data, Http.ResponseStatus status) { - if (data == null) { - return null; - } + private Map> filterSpecificHeaders(HashResponseHeaders headers, Http.Status status) { + Map> data = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + + headers.headers.iterator() + .forEachRemaining(it -> data.put(it.name(), it.allValues())); + if (status.code() == Http.Status.NO_CONTENT_204.code()) { - data.remove(Http.Header.TRANSFER_ENCODING); - data.remove(Http.Header.CONTENT_DISPOSITION); - data.remove(Http.Header.CONTENT_ENCODING); - data.remove(Http.Header.CONTENT_LANGUAGE); - data.remove(Http.Header.CONTENT_LENGTH); - data.remove(Http.Header.CONTENT_LOCATION); - data.remove(Http.Header.CONTENT_RANGE); - data.remove(Http.Header.CONTENT_TYPE); + data.remove(Http.Header.TRANSFER_ENCODING.defaultCase()); + data.remove(Http.Header.CONTENT_DISPOSITION.defaultCase()); + data.remove(Http.Header.CONTENT_ENCODING.defaultCase()); + data.remove(Http.Header.CONTENT_LANGUAGE.defaultCase()); + data.remove(Http.Header.CONTENT_LENGTH.defaultCase()); + data.remove(Http.Header.CONTENT_LOCATION.defaultCase()); + data.remove(Http.Header.CONTENT_RANGE.defaultCase()); + data.remove(Http.Header.CONTENT_TYPE.defaultCase()); } return data; } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/HttpException.java b/webserver/webserver/src/main/java/io/helidon/webserver/HttpException.java index f7769c43560..7ff00d62740 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/HttpException.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/HttpException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ */ public class HttpException extends RuntimeException { - private final Http.ResponseStatus status; + private final Http.Status status; /** * Creates {@link HttpException} associated with {@link Http.Status#INTERNAL_SERVER_ERROR_500}. @@ -54,7 +54,7 @@ public HttpException(String message, Throwable cause) { * @param message the message * @param status the http status */ - public HttpException(String message, Http.ResponseStatus status) { + public HttpException(String message, Http.Status status) { super(message); this.status = status; @@ -67,7 +67,7 @@ public HttpException(String message, Http.ResponseStatus status) { * @param status the http status * @param cause the cause of this exception */ - public HttpException(String message, Http.ResponseStatus status, Throwable cause) { + public HttpException(String message, Http.Status status, Throwable cause) { super(message, cause); this.status = status; @@ -78,7 +78,7 @@ public HttpException(String message, Http.ResponseStatus status, Throwable cause * * @return the http status */ - public final Http.ResponseStatus status() { + public final Http.Status status() { return status; } } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/JsonService.java b/webserver/webserver/src/main/java/io/helidon/webserver/JsonService.java index 3fa90b866be..dc78f0af7be 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/JsonService.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/JsonService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,8 @@ import java.util.List; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; /** * A {@link Service} and abstract {@link Handler} that provides support for JSON content. @@ -49,10 +50,10 @@ public void update(final Routing.Rules routingRules) { * @return {@code true} if JSON is accepted. */ protected boolean acceptsJson(ServerRequest request, ServerResponse response) { - final MediaType responseType = response.headers().contentType().orElse(null); + final HttpMediaType responseType = response.headers().contentType().orElse(null); if (responseType == null) { // No response type set yet. See if one of the accepted types is JSON. - final MediaType jsonResponseType = toJsonResponseType(request.headers().acceptedTypes()); + final HttpMediaType jsonResponseType = toJsonResponseType(request.headers().acceptedTypes()); if (jsonResponseType == null) { // Nope return false; @@ -62,17 +63,17 @@ protected boolean acceptsJson(ServerRequest request, ServerResponse response) { return true; } } else { - return MediaType.JSON_PREDICATE.test(responseType); + return HttpMediaType.JSON_PREDICATE.test(responseType); } } - private MediaType toJsonResponseType(List acceptedTypes) { + private HttpMediaType toJsonResponseType(List acceptedTypes) { if (acceptedTypes == null || acceptedTypes.isEmpty()) { // None provided, so go ahead and return JSON. - return MediaType.APPLICATION_JSON; + return HttpMediaType.JSON_UTF_8; } else { - for (final MediaType type : acceptedTypes) { - final MediaType responseType = toJsonResponseType(type); + for (final HttpMediaType type : acceptedTypes) { + final HttpMediaType responseType = toJsonResponseType(type); if (responseType != null) { return responseType; } @@ -87,11 +88,11 @@ private MediaType toJsonResponseType(List acceptedTypes) { * @param acceptedType The accepted type. * @return The response type or {@code null} if not an accepted JSON type. */ - protected MediaType toJsonResponseType(MediaType acceptedType) { - if (acceptedType.test(MediaType.APPLICATION_JSON)) { - return MediaType.APPLICATION_JSON; + protected HttpMediaType toJsonResponseType(HttpMediaType acceptedType) { + if (acceptedType.test(MediaTypes.APPLICATION_JSON)) { + return HttpMediaType.JSON_UTF_8; } else if (acceptedType.hasSuffix("json")) { - return MediaType.create(acceptedType.type(), acceptedType.subtype()); + return HttpMediaType.create(MediaTypes.create(acceptedType.type(), acceptedType.subtype())); } else { return null; } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/NettyRequestHeaders.java b/webserver/webserver/src/main/java/io/helidon/webserver/NettyRequestHeaders.java new file mode 100644 index 00000000000..1798e8cfc41 --- /dev/null +++ b/webserver/webserver/src/main/java/io/helidon/webserver/NettyRequestHeaders.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.webserver; + +import java.util.Iterator; +import java.util.List; +import java.util.function.Supplier; + +import io.helidon.common.http.HeadersServerRequest; +import io.helidon.common.http.HeadersWritable; +import io.helidon.common.http.Http; +import io.helidon.common.http.HttpMediaType; + +import io.netty.handler.codec.http.HttpHeaders; + +class NettyRequestHeaders implements RequestHeaders { + private final HeadersServerRequest delegate; + + private volatile List cachedAccepted; + + NettyRequestHeaders(HttpHeaders nettyHeaders) { + HeadersWritable hw = HeadersWritable.create(); + for (String name : nettyHeaders.names()) { + hw.set(Http.HeaderValue.create(Http.Header.create(name), nettyHeaders.getAll(name))); + } + this.delegate = HeadersServerRequest.create(hw); + } + + @Override + public List all(Http.HeaderName name, Supplier> defaultSupplier) { + return delegate.all(name, defaultSupplier); + } + + @Override + public boolean contains(Http.HeaderName name) { + return delegate.contains(name); + } + + @Override + public boolean contains(Http.HeaderValue value) { + return delegate.contains(value); + } + + @Override + public Http.HeaderValue get(Http.HeaderName name) { + return delegate.get(name); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public List acceptedTypes() { + return delegate.acceptedTypes(); + } + + @Override + public Iterator iterator() { + return delegate.iterator(); + } +} diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/NettyWebServer.java b/webserver/webserver/src/main/java/io/helidon/webserver/NettyWebServer.java index 22420d0ea69..332bf5c69da 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/NettyWebServer.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/NettyWebServer.java @@ -40,11 +40,11 @@ import javax.net.ssl.SSLContext; -import io.helidon.common.HelidonFeatures; -import io.helidon.common.HelidonFlavor; import io.helidon.common.SerializationConfig; import io.helidon.common.Version; import io.helidon.common.context.Context; +import io.helidon.common.features.HelidonFeatures; +import io.helidon.common.features.HelidonFlavor; import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyReaderContext; import io.helidon.media.common.MessageBodyWriterContext; diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/Request.java b/webserver/webserver/src/main/java/io/helidon/webserver/Request.java index 8fd2adc7a0a..599a244e787 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/Request.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/Request.java @@ -31,10 +31,9 @@ import io.helidon.common.context.Context; import io.helidon.common.context.Contexts; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; -import io.helidon.common.http.UriComponent; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Single; +import io.helidon.common.uri.UriQuery; import io.helidon.media.common.MessageBodyContext; import io.helidon.media.common.MessageBodyReadableContent; import io.helidon.media.common.MessageBodyReaderContext; @@ -60,8 +59,8 @@ abstract class Request implements ServerRequest { private final BareRequest bareRequest; private final WebServer webServer; private final Context context; - private final Parameters queryParams; - private final HashRequestHeaders headers; + private final UriQuery queryParams; + private final RequestHeaders headers; private final MessageBodyReadableContent content; private final MessageBodyEventListener eventListener; @@ -71,12 +70,12 @@ abstract class Request implements ServerRequest { * @param req bare request from HTTP SPI implementation. * @param webServer relevant server. */ - Request(BareRequest req, WebServer webServer, HashRequestHeaders headers) { + Request(BareRequest req, WebServer webServer, RequestHeaders headers) { this.bareRequest = req; this.webServer = webServer; this.headers = headers; this.context = Contexts.context().orElseGet(() -> Context.create(webServer.context())); - this.queryParams = UriComponent.decodeQuery(req.uri().getRawQuery(), true); + this.queryParams = UriQuery.create(req.uri().getRawQuery()); this.eventListener = new MessageBodyEventListener(); MessageBodyReaderContext readerContext = MessageBodyReaderContext .create(webServer.readerContext(), eventListener, headers, headers.contentType()); @@ -107,7 +106,7 @@ abstract class Request implements ServerRequest { static Charset contentCharset(ServerRequest request) { return request.headers() .contentType() - .flatMap(MediaType::charset) + .flatMap(HttpMediaType::charset) .map(Charset::forName) .orElse(DEFAULT_CHARSET); } @@ -123,7 +122,7 @@ public Context context() { } @Override - public Http.RequestMethod method() { + public Http.Method method() { return bareRequest.method(); } @@ -143,7 +142,7 @@ public String query() { } @Override - public Parameters queryParams() { + public UriQuery queryParams() { return queryParams; } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/RequestHeaders.java b/webserver/webserver/src/main/java/io/helidon/webserver/RequestHeaders.java index f4290675ade..df4d64b72ff 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/RequestHeaders.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/RequestHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,124 +16,13 @@ package io.helidon.webserver; -import java.net.URI; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Optional; -import java.util.OptionalLong; - -import io.helidon.common.http.Headers; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; +import io.helidon.common.http.HeadersServerRequest; /** - * Extends {@link Parameters} interface by adding HTTP request headers oriented convenient methods. + * Extends {@link io.helidon.common.http.Headers} interface by adding HTTP request headers oriented convenient methods. * Use constants located in {@link io.helidon.common.http.Http.Header} as standard header names. * * @see io.helidon.common.http.Http.Header */ -public interface RequestHeaders extends Headers { - - /** - * Optionally returns the MIME type of the body of the request. - * - * @return Media type of the content. - */ - Optional contentType(); - - /** - * Optionally returns the length of the request body in octets (8-bit bytes). - * - * @return Length of the body in octets. - */ - OptionalLong contentLength(); - - /** - * Returns cookies (parsed from '{@code Cookie:}' header) based on RFC6265. - * It parse also older formats including RFC2965 but skips parameters. Only cookie {@code name} and {@code value} is returned. - * - *

Multiple cookies can be returned in a single headers and a single cookie-name can have multiple values. - * Note that base on RFC6265 an order of cookie values has no semantics. - * - * @return An unmodifiable cookies represented by {@link Parameters} interface where key is a name of the cookie and - * values are cookie values. - */ - Parameters cookies(); - - /** - * Returns a list of acceptedTypes ({@value io.helidon.common.http.Http.Header#ACCEPT} header) content types in quality - * factor order. - * Never {@code null}. - * - * @return A list of acceptedTypes media types. - */ - List acceptedTypes(); - - /** - * Test if the given media type is acceptable as a response for this request. - * A media type is accepted if the {@code Accept} header is not present in the - * request or if it contains the provided media type. - * - * @param mediaType the media type to test - * @return {@code true} if provided type is acceptable, {@code false} otherwise - * @throws NullPointerException if the provided type is {@code null}. - */ - boolean isAccepted(MediaType mediaType); - - /** - * Optionally returns a single media type from the given media types that is the - * best one accepted by the client. - * Method uses content negotiation {@value io.helidon.common.http.Http.Header#ACCEPT} - * header parameter and returns an empty value in case nothing matches. - * - * @param mediaTypes media type candidates. - * @return an accepted media type. - */ - Optional bestAccepted(MediaType... mediaTypes); - - // TODO Add support for other accept headers. - - /** - * Optionally returns acceptedTypes version in time ({@value io.helidon.common.http.Http.Header#ACCEPT_DATETIME} header). - * - * @return Acceptable version in time. - */ - Optional acceptDatetime(); - - /** - * Optionally returns request date ({@value io.helidon.common.http.Http.Header#ACCEPT_DATETIME} header). - * - * @return Request date. - */ - Optional date(); - - /** - * Optionally returns a value of {@value io.helidon.common.http.Http.Header#IF_MODIFIED_SINCE} header. - *

- * Allows a 304 Not Modified to be returned if content is unchanged. - * - * @return Content of {@value io.helidon.common.http.Http.Header#IF_MODIFIED_SINCE} header. - */ - Optional ifModifiedSince(); - - /** - * Optionally returns a value of {@value io.helidon.common.http.Http.Header#IF_UNMODIFIED_SINCE} header. - *

- * Only send the response if the entity has not been modified since a specific time. - * - * @return Content of {@value io.helidon.common.http.Http.Header#IF_MODIFIED_SINCE} header. - */ - Optional ifUnmodifiedSince(); - - /** - * Optionally returns the address of the previous web page (header {@value io.helidon.common.http.Http.Header#REFERER}) from which a link - * to the currently requested page was followed. - *

- * The word {@code referrer} has been misspelled in the RFC as well as in most implementations to the point that it - * has become standard usage and is considered correct terminology - * - * @return Referrers URI - */ - Optional referer(); - +public interface RequestHeaders extends HeadersServerRequest { } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/RequestPredicate.java b/webserver/webserver/src/main/java/io/helidon/webserver/RequestPredicate.java index 48ef0e107c4..5886c0ed8e4 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/RequestPredicate.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/RequestPredicate.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,8 +21,9 @@ import java.util.function.Predicate; import java.util.stream.Stream; +import io.helidon.common.http.Http; import io.helidon.common.http.Http.Method; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; /** * Fluent API that allows to create chains of request conditions for composing @@ -110,7 +111,7 @@ private RequestPredicate(){ /** * Create a composed predicate with the given condition. * @param first the first predicate in the chain - * @param expr the condition for the new predicate + * @param cond the condition for the new predicate */ private RequestPredicate(final RequestPredicate first, final Condition cond){ @@ -226,7 +227,7 @@ public RequestPredicate isOfMethod(final Method... methods) { * this predicate AND the provided predicate * @throws NullPointerException if the specified name is null */ - public RequestPredicate containsHeader(final String name) { + public RequestPredicate containsHeader(final Http.HeaderName name) { return containsHeader(name, (c) -> true); } @@ -242,7 +243,7 @@ public RequestPredicate containsHeader(final String name) { * this predicate AND the provided predicate * @throws NullPointerException if the specified name or value is null */ - public RequestPredicate containsHeader(final String name, + public RequestPredicate containsHeader(final Http.HeaderName name, final String value) { Objects.requireNonNull(value, "header value"); @@ -262,7 +263,7 @@ public RequestPredicate containsHeader(final String name, * this predicate AND the provided predicate * @throws NullPointerException if the specified name or predicate is null */ - public RequestPredicate containsHeader(final String name, + public RequestPredicate containsHeader(final Http.HeaderName name, final Predicate predicate) { Objects.requireNonNull(name, "header name"); @@ -383,7 +384,7 @@ public RequestPredicate accepts(final String... contentType) { Objects.requireNonNull(contentType, "content types"); return and((req) -> Stream.of(contentType).anyMatch((mt) -> - req.headers().isAccepted(MediaType.parse(mt)))); + req.headers().isAccepted(HttpMediaType.create(mt)))); } /** @@ -394,7 +395,7 @@ public RequestPredicate accepts(final String... contentType) { * this predicate AND the provided predicate * @throws NullPointerException if the specified content type array is null */ - public RequestPredicate accepts(final MediaType... contentType) { + public RequestPredicate accepts(final HttpMediaType... contentType) { Objects.requireNonNull(contentType, "accepted media types"); return and((req) -> Stream.of(contentType).anyMatch((mt) -> @@ -412,11 +413,11 @@ public RequestPredicate accepts(final MediaType... contentType) { public RequestPredicate hasContentType(final String... contentType) { Objects.requireNonNull(contentType, "accepted media types"); return and((req) -> { - Optional actualContentType = req.headers().contentType(); + Optional actualContentType = req.headers().contentType(); return actualContentType.isPresent() && Stream.of(contentType) .anyMatch((mt) -> actualContentType.get() - .equals(MediaType.parse(mt))); + .equals(HttpMediaType.create(mt))); }); } @@ -428,10 +429,10 @@ public RequestPredicate hasContentType(final String... contentType) { * this predicate AND the provided predicate * @throws NullPointerException if the specified content type array is null */ - public RequestPredicate hasContentType(final MediaType... contentType) { + public RequestPredicate hasContentType(final HttpMediaType... contentType) { Objects.requireNonNull(contentType, "content types"); return and((req) -> { - Optional actualContentType = req.headers().contentType(); + Optional actualContentType = req.headers().contentType(); return actualContentType.isPresent() && Stream.of(contentType) .anyMatch((mt) -> actualContentType.get() diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/RequestRouting.java b/webserver/webserver/src/main/java/io/helidon/webserver/RequestRouting.java index 51011eae4e3..fca35f4e903 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/RequestRouting.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/RequestRouting.java @@ -33,9 +33,8 @@ import io.helidon.common.LazyValue; import io.helidon.common.context.Contexts; -import io.helidon.common.http.AlreadyCompletedException; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.tracing.Span; import io.helidon.tracing.SpanContext; import io.helidon.tracing.Tracer; @@ -71,7 +70,8 @@ public void route(BareRequest bareRequest, BareResponse bareResponse) { try { WebServer webServer = bareRequest.webServer(); - HashRequestHeaders requestHeaders = new HashRequestHeaders(bareRequest.headers()); + RequestHeaders requestHeaders = bareRequest.headers(); + RoutedResponse response = new RoutedResponse( webServer, bareResponse, @@ -127,7 +127,7 @@ private static class Crawler { private final Request.Path contextPath; private final String path; private final String rawPath; - private final Http.RequestMethod method; + private final Http.Method method; private final Http.Version version; private volatile int index = -1; @@ -144,7 +144,7 @@ private static class Crawler { * @param version HTTP protocol version */ private Crawler(List routes, Request.Path contextPath, String path, String rawPath, - Http.RequestMethod method, Http.Version version) { + Http.Method method, Http.Version version) { this.routes = routes; this.path = path; this.rawPath = rawPath; @@ -162,7 +162,7 @@ private Crawler(List routes, Request.Path contextPath, String path, Strin * @param method an HTTP method to route. * @param version HTTP protocol version */ - Crawler(List routes, String path, String rawPath, Http.RequestMethod method, Http.Version version) { + Crawler(List routes, String path, String rawPath, Http.Method method, Http.Version version) { this(routes, null, path, rawPath, method, version); } @@ -253,7 +253,7 @@ private static class RoutedRequest extends Request { WebServer webServer, Crawler crawler, List> errorHandlers, - HashRequestHeaders headers) { + RequestHeaders headers) { super(req, webServer, headers); this.crawler = crawler; this.errorHandlers = new LinkedList<>(errorHandlers); @@ -460,7 +460,7 @@ private static class RoutedResponse extends Response { private final AtomicReference request = new AtomicReference<>(); - RoutedResponse(WebServer webServer, BareResponse bareResponse, List acceptedTypes) { + RoutedResponse(WebServer webServer, BareResponse bareResponse, List acceptedTypes) { super(webServer, bareResponse, acceptedTypes); } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/Response.java b/webserver/webserver/src/main/java/io/helidon/webserver/Response.java index 14f96de6192..6d589548d61 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/Response.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/Response.java @@ -27,7 +27,8 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaType; import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyContext; import io.helidon.media.common.MessageBodyFilter; @@ -68,7 +69,7 @@ abstract class Response implements ServerResponse { * @param webServer a web server. * @param bareResponse an implementation of the response SPI. */ - Response(WebServer webServer, BareResponse bareResponse, List acceptedTypes) { + Response(WebServer webServer, BareResponse bareResponse, List acceptedTypes) { this.webServer = webServer; this.bareResponse = bareResponse; this.headers = new HashResponseHeaders(bareResponse); @@ -110,13 +111,13 @@ public WebServer webServer() { } @Override - public Http.ResponseStatus status() { - Http.ResponseStatus status = headers.httpStatus(); + public Http.Status status() { + Http.Status status = headers.httpStatus(); return (null == status) ? Http.Status.OK_200 : status; } @Override - public Response status(Http.ResponseStatus status) { + public Response status(Http.Status status) { Objects.requireNonNull(status, "Parameter 'status' was null!"); headers.httpStatus(status); return this; @@ -308,8 +309,7 @@ private synchronized void sendErrorHeadersIfNeeded() { if (headers != null && !sent) { status(500); //We are not using CombinedHttpHeaders - headers() - .add(HttpHeaderNames.TRAILER.toString(), STREAM_STATUS + "," + STREAM_RESULT); + headers().add(Http.HeaderValue.create(Http.Header.TRAILER, STREAM_STATUS + "," + STREAM_RESULT)); sent = true; headers.send(); } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/ResponseHeaders.java b/webserver/webserver/src/main/java/io/helidon/webserver/ResponseHeaders.java index 1f23b33502d..051165c34e1 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/ResponseHeaders.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/ResponseHeaders.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,24 +16,14 @@ package io.helidon.webserver; -import java.net.URI; -import java.time.Duration; -import java.time.Instant; -import java.time.ZonedDateTime; -import java.util.List; -import java.util.Optional; -import java.util.OptionalLong; import java.util.function.Consumer; -import io.helidon.common.http.AlreadyCompletedException; -import io.helidon.common.http.Headers; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; -import io.helidon.common.http.SetCookie; +import io.helidon.common.http.HeadersServerResponse; import io.helidon.common.reactive.Single; /** - * Extends {@link Parameters} interface by adding HTTP response headers oriented constants and convenient methods. + * Extends {@link io.helidon.common.http.HeadersServerResponse} interface by adding HTTP response headers oriented constants and + * convenient methods. * Use constants located in {@link io.helidon.common.http.Http.Header} as standard header names. * *

Lifecycle

@@ -44,169 +34,7 @@ * * @see io.helidon.common.http.Http.Header */ -public interface ResponseHeaders extends Headers { - - /** - * Gets immutable list of supported patch document formats (header {@value io.helidon.common.http.Http.Header#ACCEPT_PATCH}). - *

- * Method returns a copy of actual values. - * - * @return A list of supported media types for the patch. - */ - List acceptPatches(); - - /** - * Adds one or more acceptedTypes path document formats (header {@value io.helidon.common.http.Http.Header#ACCEPT_PATCH}). - * - * @param acceptableMediaTypes media types to add. - * @throws AlreadyCompletedException if headers were completed (sent to the client). - */ - void addAcceptPatches(MediaType... acceptableMediaTypes) throws AlreadyCompletedException; - - /** - * Optionally gets the MIME type of the response body. - * - * @return Media type of the content. - */ - Optional contentType(); - - /** - * Sets the MIME type of the response body. - * - * @param contentType Media type of the content. - * @throws AlreadyCompletedException if headers were completed (sent to the client). - */ - void contentType(MediaType contentType) throws AlreadyCompletedException; - - /** - * Optionally gets the value of {@value io.helidon.common.http.Http.Header#CONTENT_LENGTH} header. - * - * @return Length of the body in octets. - */ - OptionalLong contentLength(); - - /** - * Sets the value of {@value io.helidon.common.http.Http.Header#CONTENT_LENGTH} header. - * - * @param contentLength Length of the body in octets. - * @throws AlreadyCompletedException if headers were completed (sent to the client). - */ - void contentLength(long contentLength) throws AlreadyCompletedException; - - /** - * Optionally gets the value of {@value io.helidon.common.http.Http.Header#EXPIRES} header. - *

- * Gives the date/time after which the response is considered stale. - * - * @return Expires header value. - */ - Optional expires(); - - /** - * Sets the value of {@value io.helidon.common.http.Http.Header#EXPIRES} header. - *

- * The date/time after which the response is considered stale. - * - * @param dateTime Expires date/time. - * @throws AlreadyCompletedException if headers were completed (sent to the client). - */ - void expires(ZonedDateTime dateTime) throws AlreadyCompletedException; - - /** - * Sets the value of {@value io.helidon.common.http.Http.Header#EXPIRES} header. - *

- * The date/time after which the response is considered stale. - * - * @param dateTime Expires date/time. - * @throws AlreadyCompletedException if headers were completed (sent to the client). - */ - void expires(Instant dateTime) throws AlreadyCompletedException; - - /** - * Optionally gets the value of {@value io.helidon.common.http.Http.Header#LAST_MODIFIED} header. - *

- * The last modified date for the requested object. - * - * @return Expires header value. - */ - Optional lastModified(); - - /** - * Sets the value of {@value io.helidon.common.http.Http.Header#LAST_MODIFIED} header. - *

- * The last modified date for the requested object. - * - * @param dateTime Expires date/time. - * @throws AlreadyCompletedException if headers were completed (sent to the client). - */ - void lastModified(ZonedDateTime dateTime) throws AlreadyCompletedException; - - /** - * Sets the value of {@value io.helidon.common.http.Http.Header#LAST_MODIFIED} header. - *

- * The last modified date for the requested object - * - * @param dateTime Expires date/time. - * @throws AlreadyCompletedException if headers were completed (sent to the client). - */ - void lastModified(Instant dateTime) throws AlreadyCompletedException; - - /** - * Optionally gets the value of {@value io.helidon.common.http.Http.Header#LOCATION} header. - *

- * Used in redirection, or when a new resource has been created. - * - * @return Location header value. - */ - Optional location(); - - /** - * Sets the value of {@value io.helidon.common.http.Http.Header#LOCATION} header. - *

- * Used in redirection, or when a new resource has been created. - * - * @param location Location header value. - * @throws AlreadyCompletedException if headers were completed (sent to the client). - */ - void location(URI location) throws AlreadyCompletedException; - - /** - * Adds {@code Set-Cookie} header based on RFC2616. - * - * @param name a name of the cookie. - * @param value a value of the cookie. - * @throws NullPointerException if {@code name} parameter is {@code null}. - * @throws AlreadyCompletedException if headers were completed (sent to the client). - */ - void addCookie(String name, String value) throws AlreadyCompletedException, NullPointerException; - - /** - * Adds {@code Set-Cookie} header based on RFC6265 with {@code Max-Age} - * parameter. - * - * @param name a name of the cookie. - * @param value a value of the cookie. - * @param maxAge a {@code Max-Age} cookie parameter. - * @throws NullPointerException if {@code name} parameter is {@code null}. - * @throws AlreadyCompletedException if headers were completed (sent to the client). - */ - void addCookie(String name, String value, Duration maxAge) throws AlreadyCompletedException, NullPointerException; - - /** - * Adds {@code Set-Cookie} header specified in RFC6265. - * - * @param cookie a cookie definition - * @throws NullPointerException if {@code cookie} parameter is {@code null} - */ - void addCookie(SetCookie cookie) throws NullPointerException; - - /** - * Clears a cookie by adding a {@code Set-Cookie} header with an expiration date in the past. - * - * @param name name of the cookie. - */ - void clearCookie(String name); - +public interface ResponseHeaders extends HeadersServerResponse { /** * Register a {@link Consumer} which is executed just before headers are send. {@code Consumer} can made 'last minute * changes' in headers. @@ -219,17 +47,6 @@ public interface ResponseHeaders extends Headers { */ void beforeSend(Consumer headersConsumer); - /** - * Returns a completion stage which is completed when all headers are send to the client. - * - * @return a completion stage of the headers. - * @deprecated since 2.0.0, please use {@link #whenSent()} - */ - @Deprecated - default Single whenSend() { - return whenSent(); - } - /** * Returns a {@link io.helidon.common.reactive.Single} which is completed when all headers are sent to the client. * @@ -246,16 +63,4 @@ default Single whenSend() { * @return a completion stage of sending process. */ Single send(); - - @Override - ResponseHeaders putAll(Parameters parameters); - - @Override - ResponseHeaders add(String key, String... values); - - @Override - ResponseHeaders add(String key, Iterable values); - - @Override - ResponseHeaders addAll(Parameters parameters); } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/Route.java b/webserver/webserver/src/main/java/io/helidon/webserver/Route.java index 5b8ac6dc373..1f1a2f7052c 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/Route.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/Route.java @@ -16,14 +16,9 @@ package io.helidon.webserver; -import java.util.ArrayList; -import java.util.Collection; import java.util.Collections; -import java.util.EnumSet; -import java.util.HashSet; import java.util.Map; import java.util.Set; -import java.util.function.Predicate; import io.helidon.common.http.Http; @@ -73,12 +68,12 @@ public String param(String name) { }; /** - * Gets all accepted {@link Http.RequestMethod HTTP methods} or empty set if accepts ANY method or {@code null} + * Gets all accepted {@link Http.Method HTTP methods} or empty set if accepts ANY method or {@code null} * if no method (not a method based route). * * @return accepted methods. */ - default Set acceptedMethods() { + default Set acceptedMethods() { return null; } @@ -88,64 +83,7 @@ default Set acceptedMethods() { * @param method An HTTP method. * @return {@code true} if this record accepts provided method. */ - default boolean accepts(Http.RequestMethod method) { + default boolean accepts(Http.Method method) { return false; } - - /** - * Abstract parent for {@link Route routes} using {@link Http.RequestMethod HTTP method}. - */ - class HttpMethodPredicate implements Predicate { - - private final boolean allMethods; - private final EnumSet standardMethods; - private final Set otherMethods; - - HttpMethodPredicate(Collection methods) { - if (methods == null || methods.isEmpty()) { - this.allMethods = true; - this.standardMethods = null; - this.otherMethods = null; - } else { - this.allMethods = false; - this.otherMethods = new HashSet<>(); - Collection sms = new ArrayList<>(methods.size()); - for (Http.RequestMethod method : methods) { - if (method instanceof Http.Method) { - sms.add((Http.Method) method); - } else { - otherMethods.add(method); - } - } - if (sms.isEmpty()) { - this.standardMethods = EnumSet.noneOf(Http.Method.class); - } else { - this.standardMethods = EnumSet.copyOf(sms); - } - } - } - - @Override - public boolean test(Http.RequestMethod method) { - if (allMethods) { - return true; - } else if (method instanceof Http.Method) { - return standardMethods.contains(method); - } else { - return otherMethods.contains(method); - } - } - - public Set acceptedMethods() { - if (allMethods) { - return Collections.emptySet(); - } else { - HashSet result = new HashSet<>(standardMethods.size() + otherMethods.size()); - result.addAll(standardMethods); - result.addAll(otherMethods); - return result; - } - } - - } } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/RouteList.java b/webserver/webserver/src/main/java/io/helidon/webserver/RouteList.java index 013cacb727d..2bb6f457f14 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/RouteList.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/RouteList.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -34,7 +34,7 @@ class RouteList extends ArrayList implements Route { private static final long serialVersionUID = 1L; // must declare transient, as ArrayList is Serializable (and we are not) private final transient PathMatcher pathContext; - private final transient HttpMethodPredicate methodPredicate; + private final transient Http.MethodPredicate methodPredicate; /** * Creates new instance. @@ -46,10 +46,10 @@ class RouteList extends ArrayList implements Route { super(records); boolean acceptSomeMethods = false; - Set acceptedMethods = new HashSet<>(); + Set acceptedMethods = new HashSet<>(); for (Route record : records) { - Set mtds = record.acceptedMethods(); + Set mtds = record.acceptedMethods(); if (mtds != null) { acceptSomeMethods = true; if (mtds.isEmpty()) { @@ -61,7 +61,7 @@ class RouteList extends ArrayList implements Route { } } if (acceptSomeMethods) { - this.methodPredicate = new HttpMethodPredicate(acceptedMethods); + this.methodPredicate = Http.Method.predicate(acceptedMethods); } else { this.methodPredicate = null; } @@ -82,12 +82,12 @@ PathMatcher pathContext() { } @Override - public Set acceptedMethods() { + public Set acceptedMethods() { return methodPredicate == null ? null : methodPredicate.acceptedMethods(); } @Override - public boolean accepts(Http.RequestMethod method) { + public boolean accepts(Http.Method method) { return methodPredicate != null && methodPredicate.test(method); } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/RouteListRoutingRules.java b/webserver/webserver/src/main/java/io/helidon/webserver/RouteListRoutingRules.java index 696187073b1..80ace1c0462 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/RouteListRoutingRules.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/RouteListRoutingRules.java @@ -188,7 +188,7 @@ public RouteListRoutingRules register(String pathPattern, Supplier methods, Handler... requestHandlers) { + public RouteListRoutingRules anyOf(Iterable methods, Handler... requestHandlers) { return anyOf(methods, (PathMatcher) null, requestHandlers); } @Override - public RouteListRoutingRules anyOf(Iterable methods, String pathPattern, Handler... requestHandlers) { + public RouteListRoutingRules anyOf(Iterable methods, String pathPattern, Handler... requestHandlers) { if (pathPattern == null) { return anyOf(methods, (PathMatcher) null, requestHandlers); } else { @@ -378,7 +378,7 @@ public RouteListRoutingRules anyOf(Iterable methods, String @Override public RouteListRoutingRules anyOf( - Iterable methods, PathMatcher pathMatcher, Handler... requestHandlers) { + Iterable methods, PathMatcher pathMatcher, Handler... requestHandlers) { if (requestHandlers != null) { for (Handler requestHandler : requestHandlers) { if (pathMatcher == null) { diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/Routing.java b/webserver/webserver/src/main/java/io/helidon/webserver/Routing.java index 0836b195d5c..c1faaa0fe0f 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/Routing.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/Routing.java @@ -379,7 +379,7 @@ interface Rules { * @param requestHandlers handlers to process HTTP request * @return an updated routing configuration */ - Rules anyOf(Iterable methods, Handler... requestHandlers); + Rules anyOf(Iterable methods, Handler... requestHandlers); /** * Routes requests with any specified method and corresponding path to provided handler(s). Request handler can call @@ -390,7 +390,7 @@ interface Rules { * @param requestHandlers handlers to process HTTP request * @return an updated routing configuration */ - Rules anyOf(Iterable methods, String pathPattern, Handler... requestHandlers); + Rules anyOf(Iterable methods, String pathPattern, Handler... requestHandlers); /** * Routes requests with any specified method and corresponding path to provided handler(s). Request handler can call @@ -401,7 +401,7 @@ interface Rules { * @param requestHandlers handlers to process HTTP request * @return an updated routing configuration */ - Rules anyOf(Iterable methods, PathMatcher pathMatcher, Handler... requestHandlers); + Rules anyOf(Iterable methods, PathMatcher pathMatcher, Handler... requestHandlers); /** * Registers callback on created new {@link WebServer} instance with this routing. @@ -633,19 +633,19 @@ public Builder any(PathMatcher pathMatcher, Handler... requestHandlers) { } @Override - public Builder anyOf(Iterable methods, Handler... requestHandlers) { + public Builder anyOf(Iterable methods, Handler... requestHandlers) { delegate.anyOf(methods, requestHandlers); return this; } @Override - public Builder anyOf(Iterable methods, String pathPattern, Handler... requestHandlers) { + public Builder anyOf(Iterable methods, String pathPattern, Handler... requestHandlers) { delegate.anyOf(methods, pathPattern, requestHandlers); return this; } @Override - public Builder anyOf(Iterable methods, + public Builder anyOf(Iterable methods, PathMatcher pathMatcher, Handler... requestHandlers) { delegate.anyOf(methods, pathMatcher, requestHandlers); diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/ServerBasicConfig.java b/webserver/webserver/src/main/java/io/helidon/webserver/ServerBasicConfig.java index 8dc6ce41ab6..ca22f0ed75e 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/ServerBasicConfig.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/ServerBasicConfig.java @@ -22,9 +22,6 @@ import java.util.HashMap; import java.util.Map; import java.util.Optional; -import java.util.Set; - -import javax.net.ssl.SSLContext; import io.helidon.common.context.Context; import io.helidon.tracing.Tracer; @@ -63,26 +60,6 @@ class ServerBasicConfig implements ServerConfiguration { this.socketConfigs = Collections.unmodifiableMap(map); } - @Override - public SSLContext ssl() { - return socketConfig.ssl(); - } - - @Override - public Set enabledSslProtocols() { - return socketConfig.enabledSslProtocols(); - } - - @Override - public Set allowedCipherSuite() { - return socketConfig.allowedCipherSuite(); - } - - @Override - public ClientAuthentication clientAuth() { - return socketConfig.clientAuth(); - } - @Override public int workersCount() { return workers; @@ -257,26 +234,6 @@ public Optional tls() { return Optional.ofNullable(webServerTls); } - @Override - public SSLContext ssl() { - return tls().map(WebServerTls::sslContext).orElse(null); - } - - @Override - public Set enabledSslProtocols() { - return tls().map(WebServerTls::enabledTlsProtocols).map(Set::copyOf).orElseGet(Set::of); - } - - @Override - public Set allowedCipherSuite() { - return tls().map(WebServerTls::cipherSuite).orElseGet(Set::of); - } - - @Override - public ClientAuthentication clientAuth() { - return tls().map(WebServerTls::clientAuth).orElse(ClientAuthentication.NONE); - } - @Override public String name() { return name; diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/ServerConfiguration.java b/webserver/webserver/src/main/java/io/helidon/webserver/ServerConfiguration.java index c32e8c7759c..100383e350f 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/ServerConfiguration.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/ServerConfiguration.java @@ -211,7 +211,7 @@ default Optional transport() { } /** - * Whether to print details of {@link io.helidon.common.HelidonFeatures}. + * Whether to print details of {@link io.helidon.common.features.HelidonFeatures}. * * @return whether to print details */ @@ -575,7 +575,7 @@ public Builder transport(Transport transport) { * * @param print whether to print details or not * @return updated builder instance - * @see io.helidon.common.HelidonFeatures + * @see io.helidon.common.features.HelidonFeatures */ public Builder printFeatureDetails(boolean print) { this.printFeatureDetails = print; diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/ServerRequest.java b/webserver/webserver/src/main/java/io/helidon/webserver/ServerRequest.java index c024554ae3f..91d17d7ce85 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/ServerRequest.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/ServerRequest.java @@ -20,12 +20,13 @@ import java.net.URI; import java.net.URISyntaxException; import java.net.URL; +import java.util.List; import java.util.Optional; import io.helidon.common.context.Context; import io.helidon.common.http.Http; -import io.helidon.common.http.HttpRequest; import io.helidon.common.reactive.Single; +import io.helidon.common.uri.UriQuery; import io.helidon.media.common.MessageBodyReadableContent; import io.helidon.tracing.SpanContext; import io.helidon.tracing.Tracer; @@ -33,7 +34,7 @@ /** * Represents HTTP Request and provides WebServer related API. */ -public interface ServerRequest extends HttpRequest { +public interface ServerRequest { /** * Continue request processing on the next registered handler. @@ -180,4 +181,112 @@ default URI absoluteUri() { throw new HttpException("Unable to parse request URL", Http.Status.BAD_REQUEST_400, e); } } + + /** + * Returns an HTTP request method. See also {@link Http.Method HTTP standard methods} utility class. + * + * @return an HTTP method + * @see Http.Method + */ + Http.Method method(); + + /** + * Returns an HTTP version from the request line. + *

+ * See {@link Http.Version HTTP Version} enumeration for supported versions. + *

+ * If communication starts as a {@code HTTP/1.1} with {@code h2c} upgrade, then it will be automatically + * upgraded and this method returns {@code HTTP/2.0}. + * + * @return an HTTP version + */ + Http.Version version(); + + /** + * Returns a Request-URI (or alternatively path) as defined in request line. + * + * @return a request URI + */ + URI uri(); + + /** + * Returns an encoded query string without leading '?' character. + * + * @return an encoded query string + */ + String query(); + + /** + * Returns query parameters. + * + * @return an parameters representing query parameters + */ + UriQuery queryParams(); + + /** + * Returns a path which was accepted by matcher in actual routing. It is path without a context root + * of the routing. + *

+ * Use {@link Path#absolute()} method to obtain absolute request URI path representation. + *

+ * Returned {@link Path} also provide access to path template parameters. An absolute path then provides access to + * all (including) context parameters if any. In case of conflict between parameter names, most recent value is returned. + * + * @return a path + */ + Path path(); + + /** + * Returns a decoded request URI fragment without leading hash '#' character. + * + * @return a decoded URI fragment + */ + String fragment(); + + /** + * Represents requested normalised URI path. + */ + interface Path { + + /** + * Returns value of single parameter resolved from path pattern. + * + * @param name a parameter name + * @return a parameter value or {@code null} if not exist + */ + String param(String name); + + /** + * Returns path as a list of its segments. + * + * @return a list of path segments + */ + List segments(); + + /** + * Returns a path string representation with leading slash. + * + * @return a path + */ + String toString(); + + /** + * Returns a path string representation with leading slash without + * any character decoding. + * + * @return an undecoded path + */ + String toRawString(); + + /** + * If the instance represents a path relative to some context root then returns absolute requested path otherwise + * returns this instance. + *

+ * The absolute path also contains access to path parameters defined in context matchers. If there is + * name conflict then value represents latest matcher result. + * + * @return an absolute requested URI path + */ + Path absolute(); + } } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/ServerResponse.java b/webserver/webserver/src/main/java/io/helidon/webserver/ServerResponse.java index 5e89d7032fb..f28c14af936 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/ServerResponse.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/ServerResponse.java @@ -17,17 +17,14 @@ package io.helidon.webserver; import java.nio.ByteBuffer; +import java.util.List; import java.util.concurrent.Flow.Publisher; import java.util.concurrent.Flow.Subscriber; import java.util.concurrent.Flow.Subscription; import java.util.function.Function; -import java.util.function.Predicate; -import io.helidon.common.http.AlreadyCompletedException; import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyFilter; import io.helidon.media.common.MessageBodyFilters; @@ -92,7 +89,7 @@ private ServerResponse apply(ServerResponse serverResponse) { * * @return an HTTP status code */ - Http.ResponseStatus status(); + Http.Status status(); /** * Sets new HTTP status code. Can be done before headers are completed - see {@link ResponseHeaders} documentation. @@ -102,7 +99,7 @@ private ServerResponse apply(ServerResponse serverResponse) { * @return this instance of {@link ServerResponse} */ default ServerResponse status(int statusCode) throws AlreadyCompletedException { - return status(Http.ResponseStatus.create(statusCode)); + return status(Http.Status.create(statusCode)); } /** @@ -113,7 +110,7 @@ default ServerResponse status(int statusCode) throws AlreadyCompletedException { * @throws AlreadyCompletedException if headers were completed (sent to the client) * @throws NullPointerException if status parameter is {@code null} */ - ServerResponse status(Http.ResponseStatus status) throws AlreadyCompletedException, NullPointerException; + ServerResponse status(Http.Status status) throws AlreadyCompletedException, NullPointerException; /** * Returns response headers. It can be modified before headers are sent to the client. @@ -130,11 +127,28 @@ default ServerResponse status(int statusCode) throws AlreadyCompletedException { * @return this instance of {@link ServerResponse} * @throws NullPointerException if the specified name is null. * @see #headers() - * @see Parameters#add(String, String...) * @see Http.Header header names constants + * @deprecated use {@link #addHeader(io.helidon.common.http.Http.HeaderName, String...)} */ + @Deprecated default ServerResponse addHeader(String name, String... values) { - headers().add(name, values); + return addHeader(Http.Header.create(name), values); + } + + /** + * Adds header values for a specified name. + * + * @param name header name + * @param values header values + * @return this instance of {@link ServerResponse} + * @throws NullPointerException if the specified name is null. + * @see #headers() + * @see Http.Header header names constants + * @deprecated use {@link #addHeader(io.helidon.common.http.Http.HeaderName, String...)} + */ + @Deprecated + default ServerResponse addHeader(String name, List values) { + headers().add(Http.HeaderValue.create(Http.Header.create(name), values)); return this; } @@ -146,25 +160,24 @@ default ServerResponse addHeader(String name, String... values) { * @return this instance of {@link ServerResponse} * @throws NullPointerException if the specified name is null. * @see #headers() - * @see Parameters#add(String, Iterable) * @see Http.Header header names constants */ - default ServerResponse addHeader(String name, Iterable values) { - headers().add(name, values); + default ServerResponse addHeader(Http.HeaderName name, String... values) { + headers().add(Http.HeaderValue.create(name, values)); return this; } /** - * Copies all of the mappings from the specified {@code parameters} to this response headers instance. + * Adds header values for a specified name. * - * @param parameters to copy. + * @param value header value * @return this instance of {@link ServerResponse} - * @throws NullPointerException if the specified {@code parameters} are null. + * @throws NullPointerException if the specified name is null. * @see #headers() - * @see Parameters#addAll(Parameters) + * @see Http.Header header names constants */ - default ServerResponse addHeaders(Parameters parameters){ - headers().addAll(parameters); + default ServerResponse addHeader(Http.HeaderValue value) { + headers().add(value); return this; } @@ -201,7 +214,8 @@ default ServerResponse cachingStrategy(CachingStrategy cachingStrategy) { * Send a message and close the response. * *

Marshalling

- * Data are marshaled using default or {@link #registerWriter(Class, Function) registered} {@code writer} to the format + * Data are marshaled using default or {@link #registerWriter(io.helidon.media.common.MessageBodyWriter) registered} + * {@code writer} to the format * of {@link ByteBuffer} {@link Publisher Publisher}. The last registered compatible writer is used. *

* Default writers supports: @@ -286,102 +300,6 @@ default ServerResponse cachingStrategy(CachingStrategy cachingStrategy) { */ Single send(); - /** - * Registers a content writer for a given type. - *

- * Registered writer is used to marshal response content of given type to the {@link Publisher Publisher} - * of {@link DataChunk response chunks}. - * - * @param type a type of the content. If {@code null} then accepts any type. - * @param function a writer function - * @param a type of the content - * @return this instance of {@link ServerResponse} - * @throws NullPointerException if {@code function} parameter is {@code null} - * @deprecated Since 2.0.0, use {@link #registerWriter(io.helidon.media.common.MessageBodyWriter)} instead - */ - @Deprecated(since = "2.0.0") - ServerResponse registerWriter(Class type, Function> function); - - /** - * Registers a content writer for a given type and media type. - *

- * Registered writer is used to marshal response content of given type to the {@link Publisher Publisher} - * of {@link DataChunk response chunks}. It is used only if {@code Content-Type} header is compatible with a given - * content type or if it is {@code null}. If {@code Content-Type} is {@code null} and it is still possible to modify - * headers (headers were not send yet), the provided content type will be set. - * - * @param type a type of the content. If {@code null} then accepts any type. - * @param contentType a {@code Content-Type} of the entity - * @param function a writer function - * @param a type of the content - * @return this instance of {@link ServerResponse} - * @throws NullPointerException if {@code function} parameter is {@code null} - * @deprecated since 2.0.0, use {@link #registerWriter(io.helidon.media.common.MessageBodyWriter)} instead - */ - @Deprecated(since = "2.0.0") - ServerResponse registerWriter(Class type, - MediaType contentType, - Function> function); - - /** - * Registers a content writer for all accepted contents. - *

- * Registered writer is used to marshal response content of given type to the {@link Publisher Publisher} - * of {@link DataChunk response chunks}. - * - * @param accept a predicate to test if content is marshallable by the writer. If {@code null} then accepts any type. - * @param function a writer function - * @param a type of the content - * @return this instance of {@link ServerResponse} - * @throws NullPointerException if {@code function} parameter is {@code null} - * @deprecated since 2.0.0, use {@link #registerWriter(io.helidon.media.common.MessageBodyWriter)} instead - */ - @Deprecated - ServerResponse registerWriter(Predicate accept, Function> function); - - /** - * Registers a content writer for all accepted contents. - *

- * Registered writer is used to marshal response content of given type to the {@link Publisher Publisher} - * of {@link DataChunk response chunks}. It is used only if {@code Content-Type} header is compatible with a given - * content type or if it is {@code null}. If {@code Content-Type} is {@code null} and it is still possible to modify - * headers (headers were not send yet), the provided content type will be set. - * - * @param accept a predicate to test if content is marshallable by the writer. If {@code null} then accepts any type. - * @param contentType a {@code Content-Type} of the entity - * @param function a writer function - * @param a type of the content - * @return this instance of {@link ServerResponse} - * @throws NullPointerException if {@code function} parameter is {@code null} - * @deprecated since 2.0.0, use {@link #registerWriter(io.helidon.media.common.MessageBodyWriter)} instead - */ - @Deprecated - ServerResponse registerWriter(Predicate accept, - MediaType contentType, - Function> function); - - /** - * Registers a provider of the new response content publisher - typically a filter. - *

- * All response content is always represented by a single {@link Publisher Publisher} - * of {@link DataChunk response chunks}. This method can be used to filter or completely replace original publisher by - * a new one with different contract. For example data coding, logging, filtering, caching, etc. - *

- * New publisher is created at the moment of content write by any {@link #send(Object) send(...)} method including the empty - * one. - *

- * All registered filters are used as a chain from original content {@code Publisher}, first registered to the last - * registered. - * - * @param function a function to map previously registered or original {@code Publisher} to the new one. If returns - * {@code null} then the result will be ignored. - * @return this instance of {@link ServerResponse} - * @throws NullPointerException if parameter {@code function} is {@code null} - * @deprecated since 2.0.0, use {@link #registerFilter(io.helidon.media.common.MessageBodyFilter)} instead - */ - @Deprecated - ServerResponse registerFilter(Function, Publisher> function); - @Override ServerResponse registerFilter(MessageBodyFilter filter); diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/SocketConfiguration.java b/webserver/webserver/src/main/java/io/helidon/webserver/SocketConfiguration.java index 7e3cfe62964..d296fd4db51 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/SocketConfiguration.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/SocketConfiguration.java @@ -21,7 +21,6 @@ import java.util.Arrays; import java.util.List; import java.util.Optional; -import java.util.Set; import java.util.concurrent.TimeUnit; import java.util.function.Supplier; @@ -106,43 +105,6 @@ default String name() { */ Optional tls(); - /** - * Returns a {@link SSLContext} to use with the server socket. If not {@code null} then - * the server enforces an SSL communication. - * - * @deprecated use {@code tls().sslContext()} instead. This method will be removed at 3.0.0 version. - * @return a SSL context to use - */ - @Deprecated(since = "2.3.1", forRemoval = true) - SSLContext ssl(); - - /** - * Returns the SSL protocols to enable, or {@code null} to enable the default - * protocols. - * @deprecated use {@code tls().enabledTlsProtocols()} instead. This method will be removed at 3.0.0 version. - * @return the SSL protocols to enable - */ - @Deprecated(since = "2.3.1", forRemoval = true) - Set enabledSslProtocols(); - - /** - * Return the allowed cipher suite of the TLS. If empty set is returned, the default cipher suite is used. - * - * @deprecated use {@code tls().cipherSuite()} instead. This method will be removed at 3.0.0 version. - * @return the allowed cipher suite - */ - @Deprecated(since = "2.3.1", forRemoval = true) - Set allowedCipherSuite(); - - /** - * Whether to require client authentication or not. - * - * @deprecated use {@code tls().clientAuth()} instead. This method will be removed at 3.0.0 version. - * @return client authentication - */ - @Deprecated(since = "2.3.1", forRemoval = true) - ClientAuthentication clientAuth(); - /** * Whether this socket is enabled (and will be opened on server startup), or disabled * (and ignored on server startup). diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/WebServer.java b/webserver/webserver/src/main/java/io/helidon/webserver/WebServer.java index 67b1adf47ad..98c67182af6 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/WebServer.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/WebServer.java @@ -166,8 +166,7 @@ default boolean hasTls() { * Update the TLS configuration of the default socket {@link WebServer#DEFAULT_SOCKET_NAME}. * * @param tls new TLS configuration - * @throws IllegalStateException if {@link WebServerTls#enabled()} returns {@code false} or - * if {@code SocketConfiguration.tls().sslContext()} returns {@code null} + * @throws IllegalStateException if {@link WebServerTls#enabled()} returns {@code false} */ void updateTls(WebServerTls tls); @@ -176,8 +175,7 @@ default boolean hasTls() { * * @param tls new TLS configuration * @param socketName specific named socket name - * @throws IllegalStateException if {@link WebServerTls#enabled()} returns {@code false} or - * if {@code SocketConfiguration.tls().sslContext()} returns {@code null} + * @throws IllegalStateException if {@link WebServerTls#enabled()} returns {@code false} */ void updateTls(WebServerTls tls, String socketName); diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/WebTracingConfig.java b/webserver/webserver/src/main/java/io/helidon/webserver/WebTracingConfig.java index 0e45bcbe588..a4aeb93c64d 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/WebTracingConfig.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/WebTracingConfig.java @@ -111,9 +111,9 @@ static Tracer tracer(WebServer webServer) { Service service() { return rules -> { pathConfigs().forEach(path -> { - List methods = path.methods() + List methods = path.methods() .stream() - .map(Http.RequestMethod::create) + .map(Http.Method::create) .collect(Collectors.toList()); TracingConfig wrappedPath = path.tracedConfig(); @@ -319,7 +319,7 @@ private void doAccept(ServerRequest req, ServerResponse res) { res.whenSent() .thenRun(() -> { - Http.ResponseStatus httpStatus = res.status(); + Http.Status httpStatus = res.status(); if (httpStatus != null) { int statusCode = httpStatus.code(); span.tag(Tag.HTTP_STATUS.create(statusCode)); diff --git a/webserver/webserver/src/main/java/module-info.java b/webserver/webserver/src/main/java/module-info.java index 50c0620f41a..6ea9509a91a 100644 --- a/webserver/webserver/src/main/java/module-info.java +++ b/webserver/webserver/src/main/java/module-info.java @@ -32,6 +32,7 @@ requires transitive io.helidon.tracing; requires io.helidon.logging.common; requires static io.helidon.config.metadata; + requires io.helidon.common.features; requires java.logging; requires io.netty.handler; diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/BytesReuseTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/BytesReuseTest.java index f8d6b6ecfa2..ad696f454a7 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/BytesReuseTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/BytesReuseTest.java @@ -44,6 +44,7 @@ import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import static io.helidon.common.http.Http.HeaderValues.TRANSFER_ENCODING_CHUNKED; import static io.helidon.webserver.utils.SocketHttpClient.longData; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -85,12 +86,13 @@ private static void startServer(int port) throws Exception { .any((req, res) -> { req.content().registerFilter( (Publisher publisher) -> Multi.create(publisher).map(chunk -> { - if (req.queryParams().first("keep_chunks").map(Boolean::valueOf).orElse(true)) { + if (req.queryParams().first("keep_chunks").map(Boolean::valueOf) + .orElse(true)) { chunkReference.add(chunk); } return chunk; })); - res.headers().add("Transfer-Encoding", "chunked"); + res.headers().add(TRANSFER_ENCODING_CHUNKED); req.next(); }) .post("/subscriber", (req, res) -> { diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/ContextLifeCycleTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/ContextLifeCycleTest.java index 181f50ff9f8..dda834ab08d 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/ContextLifeCycleTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/ContextLifeCycleTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,7 +25,7 @@ import io.helidon.common.context.Context; import io.helidon.common.context.Contexts; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.Http.ResponseStatus; +import io.helidon.common.http.Http; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientResponse; @@ -62,7 +62,7 @@ void testContextMediaSupport() { .start() .await(10, TimeUnit.SECONDS); - ResponseStatus responseStatus = WebClient.builder() + Http.Status responseStatus = WebClient.builder() .baseUri("http://localhost:" + webServer.port()) .build() .post() @@ -114,7 +114,7 @@ public void onComplete() { .start() .await(10, TimeUnit.SECONDS); - ResponseStatus responseStatus = WebClient.builder() + Http.Status responseStatus = WebClient.builder() .baseUri("http://localhost:" + webServer.port()) .build() .post() diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/CookieParserTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/CookieParserTest.java deleted file mode 100644 index f1a6a17ce30..00000000000 --- a/webserver/webserver/src/test/java/io/helidon/webserver/CookieParserTest.java +++ /dev/null @@ -1,73 +0,0 @@ -/* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.webserver; - -import io.helidon.common.http.Parameters; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.collection.IsIterableContainingInOrder.contains; - - -/** - * Tests {@link HashRequestHeaders.CookieParser}. - */ -public class CookieParserTest { - - @Test - public void emptyAndNull() throws Exception { - Parameters p = HashRequestHeaders.CookieParser.parse(null); - assertThat(p, notNullValue()); - assertThat(p.toMap().isEmpty(), is(true)); - p = HashRequestHeaders.CookieParser.parse(""); - assertThat(p, notNullValue()); - assertThat(p.toMap().isEmpty(), is(true)); - } - - @Test - public void basicMultiValue() throws Exception { - Parameters p = HashRequestHeaders.CookieParser.parse("foo=bar; aaa=bbb; c=what_the_hell; aaa=ccc"); - assertThat(p, notNullValue()); - assertThat(p.all("foo"), contains("bar")); - assertThat(p.all("aaa"), contains("bbb", "ccc")); - assertThat(p.all("c"), contains("what_the_hell")); - } - - @Test - public void rfc2965() throws Exception { - String header = "$version=1; foo=bar; $Domain=google.com, aaa=bbb, c=cool; $Domain=google.com; $Path=\"/foo\""; - Parameters p = HashRequestHeaders.CookieParser.parse(header); - assertThat(p, notNullValue()); - assertThat(p.all("foo"), contains("bar")); - assertThat(p.all("aaa"), contains("bbb")); - assertThat(p.all("c"), contains("cool")); - assertThat(p.first("$Domain").isPresent(), is(false)); - assertThat(p.first("$Path").isPresent(), is(false)); - assertThat(p.first("$Version").isPresent(), is(false)); - } - - @Test - public void unquote() throws Exception { - Parameters p = HashRequestHeaders.CookieParser.parse("foo=\"bar\"; aaa=bbb; c=\"what_the_hell\"; aaa=\"ccc\""); - assertThat(p, notNullValue()); - assertThat(p.all("foo"), contains("bar")); - assertThat(p.all("aaa"), contains("bbb", "ccc")); - } -} diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/FactoryTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/FactoryTest.java index e7f57b648bb..d910bbd4e13 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/FactoryTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/FactoryTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ public class FactoryTest { @Test - public void testSpiLoad() throws Exception { + public void testSpiLoad() { assertThat(WebServer.create(Routing.builder()), IsInstanceOf.instanceOf(NettyWebServer.class)); } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/FilterTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/FilterTest.java index 0f8117b708f..0f445a20dd4 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/FilterTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/FilterTest.java @@ -61,7 +61,7 @@ private static void startServer(int port) { ) .routing(r -> r .any((req, res) -> { - res.headers().add(Http.Header.TRANSFER_ENCODING, "chunked"); + res.headers().set(Http.HeaderValues.TRANSFER_ENCODING_CHUNKED); req.next(); }) .get("/dataChunkPublisher", (req, res) -> { diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/FormParamsSupportTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/FormParamsSupportTest.java index 014781091a6..f0177018301 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/FormParamsSupportTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/FormParamsSupportTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,8 +19,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -import io.helidon.common.http.FormParams; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; +import io.helidon.common.parameters.Parameters; import io.helidon.webclient.WebClient; import org.junit.jupiter.api.AfterAll; @@ -42,8 +42,8 @@ public class FormParamsSupportTest { public static void startup() throws InterruptedException, ExecutionException, TimeoutException { testServer = WebServer.create(Routing.builder() .put("/params", (req, resp) -> { - req.content().as(FormParams.class).thenAccept(fp -> - resp.send(fp.toMap().toString())); + req.content().as(Parameters.class).thenAccept(fp -> + resp.send(fp.toString())); }) .build()) .start() @@ -64,7 +64,7 @@ public static void shutdown() { public void urlEncodedTest() throws Exception { webClient.put() .path("/params") - .contentType(MediaType.APPLICATION_FORM_URLENCODED) + .contentType(MediaTypes.APPLICATION_FORM_URLENCODED) .submit("key1=val+1&key2=val2_1&key2=val2_2", String.class) .thenAccept(it -> { assertThat(it, containsString("key1=[val 1]")); @@ -78,7 +78,7 @@ public void urlEncodedTest() throws Exception { public void plainTextTest() throws Exception{ webClient.put() .path("/params") - .contentType(MediaType.TEXT_PLAIN) + .contentType(MediaTypes.TEXT_PLAIN) .submit("key1=val 1\nkey2=val2_1\nkey2=val2_2", String.class) .thenAccept(it -> { assertThat(it, containsString("key1=[val 1]")); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/Gh1893V2ApiTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/Gh1893V2ApiTest.java index 5999243ec70..a48d9995b38 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/Gh1893V2ApiTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/Gh1893V2ApiTest.java @@ -82,16 +82,16 @@ static void startServer() { private static TransportResponse badRequestHandler(DirectHandler.TransportRequest request, DirectHandler.EventType eventType, - Http.ResponseStatus defaultStatus, + Http.Status defaultStatus, String message) { if (request.uri().equals("/redirect")) { return TransportResponse.builder() .status(Http.Status.TEMPORARY_REDIRECT_307) - .header(Http.Header.LOCATION, "/errorPage") + .header(Http.Header.LOCATION.defaultCase(), "/errorPage") .build(); } return TransportResponse.builder() - .status(Http.ResponseStatus.create(Http.Status.BAD_REQUEST_400.code(), + .status(Http.Status.create(Http.Status.BAD_REQUEST_400.code(), CUSTOM_REASON_PHRASE)) .entity(CUSTOM_ENTITY) .build(); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/HandlerRouteTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/HandlerRouteTest.java index 616fdd62d6e..ee6d0f2c425 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/HandlerRouteTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/HandlerRouteTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,17 +37,17 @@ public void standardMethodRouting() throws Exception { assertThat(rr.accepts(Http.Method.PUT), is(true)); assertThat(rr.accepts(Http.Method.GET), is(false)); assertThat(rr.accepts(Http.Method.DELETE), is(false)); - assertThat(rr.accepts(Http.RequestMethod.create("FOO")), is(false)); + assertThat(rr.accepts(Http.Method.create("FOO")), is(false)); assertThat(rr.acceptedMethods().size(), is(2)); } @Test public void specialMethodRouting() throws Exception { - HandlerRoute rr = new HandlerRoute(null, VOID_HANDLER, Http.RequestMethod.create("FOO")); + HandlerRoute rr = new HandlerRoute(null, VOID_HANDLER, Http.Method.create("FOO")); assertThat(rr.accepts(Http.Method.GET), is(false)); assertThat(rr.accepts(Http.Method.POST), is(false)); - assertThat(rr.accepts(Http.RequestMethod.create("FOO")), is(true)); - assertThat(rr.accepts(Http.RequestMethod.create("BAR")), is(false)); + assertThat(rr.accepts(Http.Method.create("FOO")), is(true)); + assertThat(rr.accepts(Http.Method.create("BAR")), is(false)); assertThat(rr.acceptedMethods().size(), is(1)); } @@ -56,14 +56,14 @@ public void combinedMethodRouting() throws Exception { HandlerRoute rr = new HandlerRoute(null, VOID_HANDLER, Http.Method.POST, - Http.RequestMethod.create("FOO"), + Http.Method.create("FOO"), Http.Method.PUT); assertThat(rr.accepts(Http.Method.POST), is(true)); assertThat(rr.accepts(Http.Method.PUT), is(true)); assertThat(rr.accepts(Http.Method.GET), is(false)); assertThat(rr.accepts(Http.Method.DELETE), is(false)); - assertThat(rr.accepts(Http.RequestMethod.create("FOO")), is(true)); - assertThat(rr.accepts(Http.RequestMethod.create("BAR")), is(false)); + assertThat(rr.accepts(Http.Method.create("FOO")), is(true)); + assertThat(rr.accepts(Http.Method.create("BAR")), is(false)); assertThat(rr.acceptedMethods().size(), is(3)); } @@ -72,8 +72,8 @@ public void anyMethodRouting() throws Exception { HandlerRoute rr = new HandlerRoute(null, VOID_HANDLER); assertThat(rr.accepts(Http.Method.POST), is(true)); assertThat(rr.accepts(Http.Method.GET), is(true)); - assertThat(rr.accepts(Http.RequestMethod.create("FOO")), is(true)); - assertThat(rr.accepts(Http.RequestMethod.create("BAR")), is(true)); + assertThat(rr.accepts(Http.Method.create("FOO")), is(true)); + assertThat(rr.accepts(Http.Method.create("BAR")), is(true)); assertThat(rr.acceptedMethods().size(), is(0)); } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/HashParametersTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/HashParametersTest.java deleted file mode 100644 index acf4b3eda63..00000000000 --- a/webserver/webserver/src/test/java/io/helidon/webserver/HashParametersTest.java +++ /dev/null @@ -1,299 +0,0 @@ -/* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.webserver; - -import java.util.ArrayList; -import java.util.Arrays; -import java.util.List; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; - -import io.helidon.common.http.HashParameters; -import io.helidon.common.http.Parameters; - -import org.hamcrest.collection.IsCollectionWithSize; -import org.junit.jupiter.api.Test; - -import static java.util.Optional.empty; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.collection.IsCollectionWithSize.hasSize; -import static org.hamcrest.collection.IsIterableContainingInOrder.contains; -import static org.hamcrest.core.Is.is; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * The HashParametersTest. - */ -public class HashParametersTest { - - @Test - public void nonExistentKey() throws Exception { - HashParameters hashParameters = HashParameters.create(); - - assertThat(hashParameters.all("a"), hasSize(0)); - assertThat(hashParameters.first("a"), is(empty())); - } - - @Test - public void addNull() throws Exception { - HashParameters hashParameters = HashParameters.create(); - - hashParameters.add("a", ((String[]) null)); - hashParameters.add("a", "value"); - hashParameters.add("a", ((String[]) null)); - hashParameters.add("a", (Iterable) null); - - assertThat(hashParameters.all("a"), contains(is("value"))); - assertThat(hashParameters.first("a").get(), is("value")); - } - - @SuppressWarnings("unchecked") - @Test - public void addMultipleAtOnce() throws Exception { - HashParameters hashParameters = HashParameters.create(); - - hashParameters.add("a", "v1", "v2"); - - assertThat(hashParameters.all("a"), contains(is("v1"), is("v2"))); - assertThat(hashParameters.first("a").get(), is("v1")); - } - - @SuppressWarnings("unchecked") - @Test - public void addMultipleOneByOne() throws Exception { - HashParameters hashParameters = HashParameters.create(); - - hashParameters.add("a", "v1"); - hashParameters.add("a", "v2"); - - assertThat(hashParameters.all("a"), contains(is("v1"), is("v2"))); - assertThat(hashParameters.first("a").get(), is("v1")); - } - - @Test - public void unmodifiabilityNonEmpty() throws Exception { - HashParameters hashParameters = HashParameters.create(); - - hashParameters.add("a", "v1", "v2"); - - assertThrows(UnsupportedOperationException.class, () -> { - hashParameters.all("a").add("this should fail"); - }); - } - - @Test - public void unmodifiabilityEmpty() throws Exception { - HashParameters hashParameters = HashParameters.create(); - - assertThrows(UnsupportedOperationException.class, () -> { - hashParameters.all("a").add("this should fail"); - }); - } - - @Test - public void put() throws Exception { - HashParameters hp = HashParameters.create(); - List result = hp.put("a", "v1", "v2", "v3"); - assertThat(hp.all("a"), contains("v1", "v2", "v3")); - assertThat(result, IsCollectionWithSize.hasSize(0)); - result =hp.put("a", "x1", "x2"); - assertThat(result, contains("v1", "v2", "v3")); - assertThat(hp.all("a"), contains("x1", "x2")); - List l = new ArrayList<>(Arrays.asList("y1", "y2")); - hp.put("a", l); - assertThat(hp.all("a"), contains("y1", "y2")); - l.add("y3"); - assertThat(hp.all("a").size(), is(2)); - hp.put("a"); - assertThat(hp.first("a").isPresent(), is(false)); - hp.put("b", "b1", "b2"); - hp.put("b", (Iterable) null); - assertThat(hp.first("b").isPresent(), is(false)); - } - - @Test - public void putIfAbsent() throws Exception { - HashParameters hp = HashParameters.create(); - List result = hp.putIfAbsent("a", "v1", "v2", "v3"); - assertThat(result, IsCollectionWithSize.hasSize(0)); - assertThat(hp.all("a"), contains("v1", "v2", "v3")); - result = hp.putIfAbsent("a", "x1", "x2", "x3"); - assertThat(result, contains("v1", "v2", "v3")); - assertThat(hp.all("a"), contains("v1", "v2", "v3")); - - hp.putIfAbsent("b", Arrays.asList("v1", "v2", "v3")); - assertThat(hp.all("b"), contains("v1", "v2", "v3")); - hp.putIfAbsent("b", Arrays.asList("x1", "x2", "x3")); - assertThat(hp.all("b"), contains("v1", "v2", "v3")); - - hp.putIfAbsent("b"); - assertThat(hp.all("b"), contains("v1", "v2", "v3")); - hp.putIfAbsent("b", (Iterable) null); - assertThat(hp.all("b"), contains("v1", "v2", "v3")); - - result = hp.putIfAbsent("c"); - assertThat(result, IsCollectionWithSize.hasSize(0)); - result = hp.putIfAbsent("c", (Iterable) null); - assertThat(result, IsCollectionWithSize.hasSize(0)); - } - - @Test - public void computeIfAbsent() throws Exception { - AtomicBoolean visited = new AtomicBoolean(false); - HashParameters hp = HashParameters.create(); - - List result = hp.computeIfAbsent("a", k -> { - visited.set(true); - return null; - }); - assertThat(visited.get(), is(true)); - assertThat(result, IsCollectionWithSize.hasSize(0)); - assertThat(hp.all("a"), IsCollectionWithSize.hasSize(0)); - - visited.set(false); - result = hp.computeIfAbsent("a", k -> { - visited.set(true); - return Arrays.asList("v1", "v2", "v3"); - }); - assertThat(visited.get(), is(true)); - assertThat(result, contains("v1", "v2", "v3")); - assertThat(hp.all("a"), contains("v1", "v2", "v3")); - - visited.set(false); - result = hp.computeIfAbsent("a", k -> { - visited.set(true); - return Arrays.asList("x1", "x2", "x3"); - }); - assertThat(visited.get(), is(false)); - assertThat(result, contains("v1", "v2", "v3")); - assertThat(hp.all("a"), contains("v1", "v2", "v3")); - - visited.set(false); - result = hp.computeSingleIfAbsent("b", k -> { - visited.set(true); - return "x1"; - }); - assertThat(visited.get(), is(true)); - assertThat(result, contains("x1")); - assertThat(hp.all("b"), contains("x1")); - - visited.set(false); - result = hp.computeSingleIfAbsent("c", k -> { - visited.set(true); - return null; - }); - assertThat(visited.get(), is(true)); - assertThat(result, IsCollectionWithSize.hasSize(0)); - assertThat(hp.first("c").isPresent(), is(false)); - } - - @Test - public void remove() throws Exception { - HashParameters hp = HashParameters.create(); - List removed = hp.remove("a"); - assertThat(removed, IsCollectionWithSize.hasSize(0)); - hp.put("a", "v1", "v2"); - removed = hp.remove("a"); - assertThat(removed, contains("v1", "v2")); - assertThat(hp.all("a"), IsCollectionWithSize.hasSize(0)); - } - - @Test - public void toMap() throws Exception { - HashParameters hp = HashParameters.create(); - hp.put("a", "v1", "v2"); - hp.put("b", "v3", "v4"); - Map> map = hp.toMap(); - assertThat(map.size(), is(2)); - assertThat(map.get("a"), contains("v1", "v2")); - assertThat(map.get("b"), contains("v3", "v4")); - } - - @Test - public void putAll() throws Exception { - HashParameters hp = HashParameters.create(); - hp.put("a", "a1", "a2"); - hp.put("b", "b1", "b2"); - HashParameters hp2 = HashParameters.create(); - hp2.put("c", "c1", "c2"); - hp2.put("b", "b3", "b4"); - - hp.putAll(hp2); - assertThat(hp.all("a"), contains("a1", "a2")); - assertThat(hp.all("b"), contains("b3", "b4")); - assertThat(hp.all("c"), contains("c1", "c2")); - - hp.putAll(null); - assertThat(hp.all("a"), contains("a1", "a2")); - assertThat(hp.all("b"), contains("b3", "b4")); - } - - @Test - public void addAll() throws Exception { - HashParameters hp = HashParameters.create(); - hp.put("a", "a1", "a2"); - hp.put("b", "b1", "b2"); - HashParameters hp2 = HashParameters.create(); - hp2.put("c", "c1", "c2"); - hp2.put("b", "b3", "b4"); - - hp.addAll(hp2); - assertThat(hp.all("a"), contains("a1", "a2")); - assertThat(hp.all("b"), contains("b1", "b2", "b3", "b4")); - assertThat(hp.all("c"), contains("c1", "c2")); - - hp.addAll(null); - assertThat(hp.all("b"), contains("b1", "b2", "b3", "b4")); - } - - @Test - public void concatNullAndEmpty() throws Exception { - Parameters[] prms = null; - HashParameters concat = HashParameters.concat(prms); - assertThat(concat, notNullValue()); - prms = new Parameters[10]; - concat = HashParameters.concat(prms); - assertThat(concat, notNullValue()); - concat = HashParameters.concat(); - assertThat(concat, notNullValue()); - } - - @Test - public void concat() throws Exception { - HashParameters p1 = HashParameters.create(); - p1.add("a", "1", "2"); - p1.add("b", "3", "4", "5"); - HashParameters p2 = HashParameters.create(); - p2.add("a", "6"); - p2.add("c", "7", "8"); - HashParameters p3 = HashParameters.create(); - HashParameters p4 = HashParameters.create(); - p2.add("a", "9"); - p2.add("c", "10"); - p2.add("d", "11", "12"); - HashParameters concat = HashParameters.concat(p1, p2, null, p3, null, p4, null, null); - assertThat(concat.all("a"), contains("1", "2", "6", "9")); - assertThat(concat.all("b"), contains("3", "4", "5")); - assertThat(concat.all("c"), contains("7", "8", "10")); - assertThat(concat.all("d"), contains("11", "12")); - - concat = HashParameters.concat(p1); - assertThat(concat, is(p1)); - } - -} diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/HashRequestHeadersTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/HashRequestHeadersTest.java deleted file mode 100644 index 2288793f851..00000000000 --- a/webserver/webserver/src/test/java/io/helidon/webserver/HashRequestHeadersTest.java +++ /dev/null @@ -1,193 +0,0 @@ -/* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.webserver; - -import java.net.URI; -import java.time.ZoneId; -import java.time.ZonedDateTime; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.List; -import java.util.Map; - -import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; - -import org.hamcrest.number.IsCloseTo; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.collection.IsIterableContainingInOrder.contains; -import static org.hamcrest.core.IsCollectionContaining.hasItems; - -/** - * Tests {@link HashRequestHeaders}. - */ -public class HashRequestHeadersTest { - private static final ZonedDateTime ZDT = ZonedDateTime.of(2008, 6, 3, 11, 5, 30, 0, ZoneId.of("Z")); - - HashRequestHeaders withHeader(String name, String... values) { - Map> map = new HashMap<>(1); - map.put(name, new ArrayList<>(Arrays.asList(values))); - return new HashRequestHeaders(map); - } - - @Test - public void allConcatenated() { - HashRequestHeaders hs = withHeader("Foo", "val1", "val 2", "val3,val4", "val5"); - assertThat(hs.value("Foo").orElse(null), is("val1,val 2,val3,val4,val5")); - } - - @Test - public void allSepar() { - HashRequestHeaders hs = withHeader("Foo", "val1", "val 2", "val3,val4", "val5", "val 6,val7,val 8", "val9"); - assertThat(hs.values("Foo"), - hasItems("val1", "val 2", "val3", "val4", "val5", "val 6", "val7", "val 8", "val9")); - } - - @Test - public void contentType() { - HashRequestHeaders hs = withHeader(Http.Header.CONTENT_TYPE, MediaType.APPLICATION_JSON.toString()); - assertThat(hs.contentType().isPresent(), is(true)); - assertThat(hs.contentType().get(), is(MediaType.APPLICATION_JSON)); - } - - @Test - public void contentLength() { - HashRequestHeaders hs = withHeader(Http.Header.CONTENT_LENGTH, "1024"); - assertThat(hs.contentLength().isPresent(), is(true)); - assertThat(hs.contentLength().getAsLong(), is(1024L)); - } - - @Test - public void cookies() { - HashRequestHeaders hs - = withHeader(Http.Header.COOKIE, - "foo=bar; aaa=bbb; c=what_the_hell; aaa=ccc", - "$version=1; some=other; $Domain=google.com, aaa=eee, d=cool; $Domain=google.com; $Path=\"/foo\""); - Parameters cookies = hs.cookies(); - assertThat(cookies.all("foo"), contains("bar")); - assertThat(cookies.all("c"), contains("what_the_hell")); - assertThat(cookies.all("aaa"), contains("bbb", "ccc", "eee")); - assertThat(cookies.all("some"), contains("other")); - assertThat(cookies.all("d"), contains("cool")); - } - - @Test - public void acceptedTypes() { - HashRequestHeaders hs = withHeader(Http.Header.ACCEPT, - "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4"); - assertThat(hs.acceptedTypes().size(), is(4)); - assertThat(hs.acceptedTypes().get(0), is(createMt("text", "*", Map.of("q", "0.3")))); - assertThat(hs.acceptedTypes().get(0).qualityFactor(), IsCloseTo.closeTo(0, 0.3)); - assertThat(hs.acceptedTypes().get(1), is(createMt("text", "html", Map.of("q", "0.7")))); - assertThat(hs.acceptedTypes().get(2), is(createMt("text", "html", Map.of("level", "1")))); - assertThat(hs.acceptedTypes().get(3), - is(createMt("text", "html", Map.of("level", "2", "q", "0.4")))); - } - - @Test - public void hucDefaultAccept(){ - try { - HashRequestHeaders hs = withHeader(Http.Header.ACCEPT, HashRequestHeaders.HUC_ACCEPT_DEFAULT); - assertThat(hs.acceptedTypes().get(0), is(MediaType.TEXT_HTML)); - assertThat(hs.acceptedTypes().get(1), is(createMt("image", "gif"))); - assertThat(hs.acceptedTypes().get(2), is(createMt("image", "jpeg"))); - assertThat(hs.acceptedTypes().get(3), is(createMt("*", "*", Map.of("q", ".2")))); - } catch(IllegalStateException ex){ - Assertions.fail(ex.getMessage(), ex); - } - } - - private MediaType createMt(String type, String subtype) { - return MediaType.builder() - .type(type) - .subtype(subtype) - .build(); - } - - private MediaType createMt(String type, String subtype, Map params) { - return MediaType.builder() - .type(type) - .subtype(subtype) - .parameters(params) - .build(); - } - - @Test - public void isAccepted() { - HashRequestHeaders hs = withHeader(Http.Header.ACCEPT, "text/*;q=0.3, application/json;q=0.7"); - assertThat(hs.isAccepted(MediaType.TEXT_HTML), is(true)); - assertThat(hs.isAccepted(MediaType.TEXT_XML), is(true)); - assertThat(hs.isAccepted(MediaType.APPLICATION_JSON), is(true)); - assertThat(hs.isAccepted(MediaType.APPLICATION_OCTET_STREAM), is(false)); - } - - @Test - public void bestAccepted() { - HashRequestHeaders hs = withHeader(Http.Header.ACCEPT, - "text/*;q=0.3, text/html;q=0.7, text/xml;q=0.4"); - assertThat(hs.bestAccepted(MediaType.APPLICATION_JSON, - MediaType.TEXT_PLAIN, - null, - MediaType.TEXT_XML).orElse(null), - is(MediaType.TEXT_XML)); - assertThat(hs.bestAccepted(MediaType.APPLICATION_JSON, - MediaType.TEXT_HTML, - MediaType.TEXT_XML).orElse(null), - is(MediaType.TEXT_HTML)); - assertThat(hs.bestAccepted(MediaType.APPLICATION_JSON, - MediaType.TEXT_PLAIN).orElse(null), - is(MediaType.TEXT_PLAIN)); - assertThat(hs.bestAccepted(MediaType.APPLICATION_JSON).isPresent(), is(false)); - assertThat(hs.bestAccepted().isPresent(), is(false)); - } - - @Test - public void acceptDatetime() { - HashRequestHeaders hs = withHeader(Http.Header.ACCEPT_DATETIME, "Tue, 3 Jun 2008 11:05:30 GMT"); - assertThat(hs.acceptDatetime().orElse(null), is(ZDT)); - } - - @Test - public void date() { - HashRequestHeaders hs = withHeader(Http.Header.DATE, "Tue, 3 Jun 2008 11:05:30 GMT"); - assertThat(hs.date().orElse(null), is(ZDT)); - } - - @Test - public void ifModifiedSince() { - HashRequestHeaders hs = withHeader(Http.Header.IF_MODIFIED_SINCE, "Tue, 3 Jun 2008 11:05:30 GMT"); - assertThat(hs.ifModifiedSince().orElse(null), is(ZDT)); - } - - @Test - public void ifUnmodifiedSince() { - HashRequestHeaders hs = withHeader(Http.Header.IF_UNMODIFIED_SINCE, "Tue, 3 Jun 2008 11:05:30 GMT"); - assertThat(hs.ifUnmodifiedSince().orElse(null), is(ZDT)); - } - - @Test - public void referer() { - HashRequestHeaders hs = withHeader(Http.Header.REFERER, "http://www.google.com"); - assertThat(hs.referer().map(URI::toString).orElse(null), is("http://www.google.com")); - } -} diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/HashResponseHeadersTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/HashResponseHeadersTest.java index 8c8258a9279..3e741616f30 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/HashResponseHeadersTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/HashResponseHeadersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,16 +22,17 @@ import java.time.ZoneId; import java.time.ZonedDateTime; import java.time.temporal.ChronoUnit; -import java.util.Arrays; +import java.util.List; import java.util.concurrent.CompletableFuture; -import io.helidon.common.http.AlreadyCompletedException; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.Http.Header; +import io.helidon.common.http.Http.HeaderValue; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.http.SetCookie; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Single; -import org.hamcrest.collection.IsIterableContainingInOrder; import org.junit.jupiter.api.Test; import static io.helidon.webserver.utils.TestUtils.assertException; @@ -48,31 +49,33 @@ /** * Tests {@link HashResponseHeaders}. */ -public class HashResponseHeadersTest { +class HashResponseHeadersTest { + + public static final Http.HeaderName HEADER_A = Header.create("a"); @Test - public void acceptPatches() throws Exception { + void acceptPatches() { HashResponseHeaders h = new HashResponseHeaders(null); - h.addAcceptPatches(MediaType.APPLICATION_JSON, MediaType.TEXT_XML); - assertThat(h.acceptPatches(), IsIterableContainingInOrder.contains(MediaType.APPLICATION_JSON, MediaType.TEXT_XML)); + h.addAcceptPatches(MediaTypes.APPLICATION_JSON, MediaTypes.TEXT_XML); + assertThat(h.acceptPatches(), contains(HttpMediaType.APPLICATION_JSON, HttpMediaType.create(MediaTypes.TEXT_XML))); } @Test - public void contentType() throws Exception { + void contentType() { HashResponseHeaders h = new HashResponseHeaders(null); - h.contentType(MediaType.APPLICATION_JSON); - assertThat(h.contentType().orElse(null), is(MediaType.APPLICATION_JSON)); - h.contentType(null); + h.contentType(MediaTypes.APPLICATION_JSON); + assertThat(h.contentType().orElse(null), is(HttpMediaType.APPLICATION_JSON)); + h.remove(Header.CONTENT_TYPE); assertThat(h.contentType().isPresent(), is(false)); } @Test - public void expires() throws Exception { + void expires() { HashResponseHeaders h = new HashResponseHeaders(null); ZonedDateTime now = ZonedDateTime.now(); h.expires(now); assertThat(h.expires().orElse(null), is(now.truncatedTo(ChronoUnit.SECONDS).withFixedOffsetZone())); - h.expires((ZonedDateTime) null); + h.remove(Header.EXPIRES); assertThat(h.expires().isPresent(), is(false)); Instant instant = Instant.now(); h.expires(instant); @@ -80,12 +83,12 @@ public void expires() throws Exception { } @Test - public void lastModified() throws Exception { + void lastModified() { HashResponseHeaders h = new HashResponseHeaders(null); ZonedDateTime now = ZonedDateTime.now(); h.lastModified(now); assertThat(h.lastModified().orElse(null), is(now.truncatedTo(ChronoUnit.SECONDS).withFixedOffsetZone())); - h.lastModified((ZonedDateTime) null); + h.remove(Header.LAST_MODIFIED); assertThat(h.lastModified().isPresent(), is(false)); Instant instant = Instant.now(); h.lastModified(instant); @@ -93,17 +96,17 @@ public void lastModified() throws Exception { } @Test - public void location() throws Exception { + void location() { HashResponseHeaders h = new HashResponseHeaders(null); URI uri = URI.create("http://www.oracle.com"); h.location(uri); assertThat(h.location().orElse(null), is(uri)); - h.location(null); + h.remove(Header.LOCATION); assertThat(h.location().isPresent(), is(false)); } @Test - public void addCookie() { + void addCookie() { HashResponseHeaders h = new HashResponseHeaders(null); h.addCookie("foo", "bar"); @@ -116,66 +119,52 @@ public void addCookie() { .secure(true) .build()); - assertThat(h.all(Http.Header.SET_COOKIE), contains("foo=bar", - "aaa=bbbb; Max-Age=600", - "who=me", - "itis=cool; Expires=Mon, 1 Jan 2080 00:00:00 GMT; Domain=oracle.com;" + assertThat(h.all(Header.SET_COOKIE, List::of), contains("foo=bar", + "aaa=bbbb; Max-Age=600", + "who=me", + "itis=cool; Expires=Mon, 1 Jan 2080 00:00:00 GMT; Domain=oracle.com;" + " Path=/foo; Secure")); } @Test - public void addAndClearCookies() { + void addAndClearCookies() { HashResponseHeaders h = new HashResponseHeaders(null); h.addCookie("foo1", "bar1"); h.addCookie("foo2", "bar2"); - assertThat(h.all(Http.Header.SET_COOKIE), contains( + assertThat(h.all(Header.SET_COOKIE, List::of), contains( "foo1=bar1", "foo2=bar2")); h.clearCookie("foo1"); - assertThat(h.all(Http.Header.SET_COOKIE), contains( + assertThat(h.all(Header.SET_COOKIE, List::of), contains( "foo1=deleted; Expires=Thu, 1 Jan 1970 00:00:00 GMT; Path=/", "foo2=bar2")); } @Test - public void clearCookie() { + void clearCookie() { HashResponseHeaders h = new HashResponseHeaders(null); h.clearCookie("foo1"); - assertThat(h.all(Http.Header.SET_COOKIE), contains( + assertThat(h.all(Header.SET_COOKIE, List::of), contains( "foo1=deleted; Expires=Thu, 1 Jan 1970 00:00:00 GMT; Path=/")); } @Test - public void immutableWhenCompleted() throws Exception { + void immutableWhenCompleted() throws Exception { HashResponseHeaders h = new HashResponseHeaders(mockBareResponse()); - h.put("a", "b"); - h.put("a", Arrays.asList("b")); - h.add("a", "b"); - h.add("a", Arrays.asList("b")); - h.putIfAbsent("a", "b"); - h.putIfAbsent("a", Arrays.asList("b")); - h.computeIfAbsent("a", k -> Arrays.asList("b")); - h.computeSingleIfAbsent("a", k -> "b"); - h.putAll(h); - h.addAll(h); - h.remove("a"); + h.set(HEADER_A, "b"); + h.add(HeaderValue.create(HEADER_A, "b")); + h.setIfAbsent(HeaderValue.create(HEADER_A, "b")); + h.remove(HEADER_A); h.send().toCompletableFuture().get(); - assertException(() -> h.put("a", "b"), AlreadyCompletedException.class); - assertException(() -> h.put("a", Arrays.asList("b")), AlreadyCompletedException.class); - assertException(() -> h.add("a", "b"), AlreadyCompletedException.class); - assertException(() -> h.add("a", Arrays.asList("b")), AlreadyCompletedException.class); - assertException(() -> h.putIfAbsent("a", "b"), AlreadyCompletedException.class); - assertException(() -> h.putIfAbsent("a", Arrays.asList("b")), AlreadyCompletedException.class); - assertException(() -> h.computeIfAbsent("a", k -> Arrays.asList("b")), AlreadyCompletedException.class); - assertException(() -> h.computeSingleIfAbsent("a", k -> "b"), AlreadyCompletedException.class); - assertException(() -> h.putAll(h), AlreadyCompletedException.class); - assertException(() -> h.addAll(h), AlreadyCompletedException.class); - assertException(() -> h.remove("a"), AlreadyCompletedException.class); + assertException(() -> h.set(HEADER_A, "b"), AlreadyCompletedException.class); + assertException(() -> h.add(HeaderValue.create(HEADER_A, "b")), AlreadyCompletedException.class); + assertException(() -> h.setIfAbsent(HeaderValue.create(HEADER_A, "b")), AlreadyCompletedException.class); + assertException(() -> h.remove(HEADER_A), AlreadyCompletedException.class); } @Test - public void beforeSent() throws Exception { + void beforeSent() throws Exception { StringBuffer sb = new StringBuffer(); HashResponseHeaders h = new HashResponseHeaders(mockBareResponse()); h.beforeSend(headers -> sb.append("B:" + (h == headers))); @@ -188,17 +177,17 @@ public void beforeSent() throws Exception { } @Test - public void headersFiltrationFor204() throws Exception { + void headersFiltrationFor204() throws Exception { BareResponse bareResponse = mockBareResponse(); HashResponseHeaders h = new HashResponseHeaders(bareResponse); - h.put(Http.Header.CONTENT_TYPE, "text/plain"); - h.put("some", "some_value"); - h.put(Http.Header.TRANSFER_ENCODING, "custom"); + h.set(Header.CONTENT_TYPE, "text/plain"); + h.set(Header.create("some"), "some_value"); + h.set(Header.TRANSFER_ENCODING, "custom"); h.httpStatus(Http.Status.NO_CONTENT_204); h.send().toCompletableFuture().get(); verify(bareResponse).writeStatusAndHeaders(any(), argThat(m -> m.containsKey("some") - && !m.containsKey(Http.Header.CONTENT_TYPE) - && !m.containsKey(Http.Header.TRANSFER_ENCODING))); + && !m.containsKey(Header.CONTENT_TYPE.defaultCase()) + && !m.containsKey(Header.TRANSFER_ENCODING.defaultCase()))); } private BareResponse mockBareResponse() { diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/KeepAliveTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/KeepAliveTest.java index 54025e8a642..b75c96c7546 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/KeepAliveTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/KeepAliveTest.java @@ -17,7 +17,6 @@ import java.nio.ByteBuffer; import java.time.Duration; -import java.util.List; import java.util.Optional; import java.util.concurrent.CompletionException; import java.util.concurrent.Executors; @@ -25,22 +24,21 @@ import io.helidon.common.LogConfig; import io.helidon.common.http.DataChunk; +import io.helidon.common.http.Http; import io.helidon.common.reactive.Multi; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientResponse; -import io.netty.handler.codec.http.HttpHeaderNames; import io.netty.handler.codec.http.HttpHeaderValues; import io.netty.util.AsciiString; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.RepeatedTest; +import static io.helidon.common.testing.http.HttpHeaderMatcher.hasHeader; +import static io.helidon.common.testing.http.HttpHeaderMatcher.noHeader; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.hasEntry; -import static org.hamcrest.Matchers.hasKey; -import static org.hamcrest.Matchers.not; public class KeepAliveTest { @@ -120,10 +118,10 @@ private static void testCall(WebClient webClient, assertThat(res.status().code(), is(expectedStatus)); if (expectedConnectionHeader != null) { - assertThat(res.headers().toMap(), - hasEntry(HttpHeaderNames.CONNECTION.toString(), List.of(expectedConnectionHeader.toString()))); + assertThat(res.headers(), + hasHeader(Http.HeaderValue.create(Http.Header.CONNECTION, expectedConnectionHeader.toString()))); } else { - assertThat(res.headers().toMap(), not(hasKey(HttpHeaderNames.CONNECTION.toString()))); + assertThat(res.headers(), noHeader(Http.Header.CONNECTION)); } res.content().forEach(DataChunk::release); } catch (CompletionException e) { diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/MaxPayloadSizeTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/MaxPayloadSizeTest.java index fc43ddae256..c095d9ebbd2 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/MaxPayloadSizeTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/MaxPayloadSizeTest.java @@ -24,7 +24,7 @@ import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; import io.helidon.webclient.WebClientResponse; @@ -125,7 +125,7 @@ public void testContentLengthExceeded() { WebClientRequestBuilder builder = webClient.post(); builder.headers().add("Content-Length", "512"); // over max WebClientResponse response = builder.path("/maxpayload") - .contentType(MediaType.APPLICATION_OCTET_STREAM) + .contentType(MediaTypes.APPLICATION_OCTET_STREAM) .request() .await(5, TimeUnit.SECONDS); assertThat(response.status().code(), is(Http.Status.REQUEST_ENTITY_TOO_LARGE_413.code())); @@ -138,7 +138,7 @@ public void testContentLengthExceeded() { public void testContentLengthExceededWithPayload() { WebClientRequestBuilder builder = webClient.post(); WebClientResponse response = builder.path("/maxpayload") - .contentType(MediaType.APPLICATION_OCTET_STREAM) + .contentType(MediaTypes.APPLICATION_OCTET_STREAM) .submit(PAYLOAD) .await(5, TimeUnit.SECONDS); assertThat(response.status().code(), is(Http.Status.REQUEST_ENTITY_TOO_LARGE_413.code())); @@ -154,7 +154,7 @@ public void testActualLengthExceededWithPayload() { try { WebClientRequestBuilder builder = webClient.post(); WebClientResponse response = builder.path("/maxpayload") - .contentType(MediaType.APPLICATION_OCTET_STREAM) + .contentType(MediaTypes.APPLICATION_OCTET_STREAM) .submit(new PayloadPublisher(PAYLOAD, 3)) .await(5, TimeUnit.SECONDS); assertThat(response.status().code(), is(Http.Status.REQUEST_ENTITY_TOO_LARGE_413.code())); @@ -170,21 +170,21 @@ public void testActualLengthExceededWithPayload() { public void testMixedGoodAndBadPayloads() { WebClientRequestBuilder builder = webClient.post(); WebClientResponse response = builder.path("/maxpayload") - .contentType(MediaType.APPLICATION_OCTET_STREAM) + .contentType(MediaTypes.APPLICATION_OCTET_STREAM) .submit(PAYLOAD.substring(0, 100)) .await(5, TimeUnit.SECONDS); assertThat(response.status().code(), is(Http.Status.OK_200.code())); builder = webClient.post(); response = builder.path("/maxpayload") - .contentType(MediaType.APPLICATION_OCTET_STREAM) + .contentType(MediaTypes.APPLICATION_OCTET_STREAM) .submit(new PayloadPublisher(PAYLOAD, 1)) .await(5, TimeUnit.SECONDS); assertThat(response.status().code(), is(Http.Status.REQUEST_ENTITY_TOO_LARGE_413.code())); builder = webClient.post(); response = builder.path("/maxpayload") - .contentType(MediaType.APPLICATION_OCTET_STREAM) + .contentType(MediaTypes.APPLICATION_OCTET_STREAM) .submit(PAYLOAD.substring(0, (int) MAX_PAYLOAD_SIZE)) .await(5, TimeUnit.SECONDS); assertThat(response.status().code(), is(Http.Status.OK_200.code())); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/MultiPortTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/MultiPortTest.java index eaf3deac0bf..1e8fe62e1fc 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/MultiPortTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/MultiPortTest.java @@ -19,6 +19,8 @@ import java.time.Duration; import java.util.concurrent.atomic.AtomicLong; +import java.util.Optional; + import io.helidon.common.configurable.Resource; import io.helidon.common.http.Http; import io.helidon.common.pki.KeyConfig; @@ -35,6 +37,7 @@ import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; +import static io.helidon.common.testing.http.HttpHeaderMatcher.hasHeaderValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.core.Is.is; import static org.junit.jupiter.api.Assertions.fail; @@ -212,6 +215,7 @@ public void compositeRedirectWebServer() throws Exception { String.format("https://%s:%d%s", req.headers() .first(Http.Header.HOST) + .map(Http.HeaderValue::value) .map(s -> s.contains(":") ? s .subSequence(0, s.indexOf(":")) : s) .orElseThrow(() -> new IllegalStateException( @@ -236,14 +240,14 @@ public void compositeRedirectWebServer() throws Exception { .request() .thenApply(it -> { assertThat("Unexpected response: " + it, - it.headers().first(Http.Header.LOCATION).get(), + it.headers(), hasHeaderValue(Http.Header.LOCATION, AllOf.allOf(StringContains.containsString("https://localhost:"), - StringContains.containsString("/foo"))); + StringContains.containsString("/foo")))); assertThat("Unexpected response: " + it, it.status(), is(Http.Status.MOVED_PERMANENTLY_301)); return it; }) .thenCompose(it -> webClient.get() - .uri(it.headers().first(Http.Header.LOCATION).get()) + .uri(it.headers().get(Http.Header.LOCATION).value()) .request(String.class)) .thenAccept(it -> assertThat("Unexpected response: " + it, it, is("Root! 2"))) .await(TIMEOUT); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/NettyRequestHeadersTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/NettyRequestHeadersTest.java new file mode 100644 index 00000000000..d6d73da88af --- /dev/null +++ b/webserver/webserver/src/test/java/io/helidon/webserver/NettyRequestHeadersTest.java @@ -0,0 +1,192 @@ +/* + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.webserver; + +import java.net.URI; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.util.List; +import java.util.Map; + +import io.helidon.common.http.Http; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; +import io.helidon.common.parameters.Parameters; + +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaders; +import org.hamcrest.number.IsCloseTo; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.CoreMatchers.is; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.collection.IsIterableContainingInOrder.contains; +import static org.hamcrest.core.IsCollectionContaining.hasItems; + +/** + * Tests {@link io.helidon.webserver.NettyRequestHeaders}. + */ +class NettyRequestHeadersTest { + private static final ZonedDateTime ZDT = ZonedDateTime.of(2008, 6, 3, 11, 5, 30, 0, ZoneId.of("Z")); + private static final Http.HeaderName FOO_HEADER = Http.Header.create("Foo"); + + NettyRequestHeaders withHeader(String name, String... values) { + HttpHeaders headers = new DefaultHttpHeaders(); + headers.set(name, List.of(values)); + return new NettyRequestHeaders(headers); + } + + @Test + void allConcatenated() { + NettyRequestHeaders hs = withHeader("Foo", "val1", "val 2", "val3,val4", "val5"); + assertThat(hs.value(FOO_HEADER).orElse(null), is("val1,val 2,val3,val4,val5")); + } + + @Test + void allSepar() { + NettyRequestHeaders hs = withHeader("Foo", "val1", "val 2", "val3,val4", "val5", "val 6,val7,val 8", "val9"); + assertThat(hs.values(Http.Header.create("Foo")), + hasItems("val1", "val 2", "val3", "val4", "val5", "val 6", "val7", "val 8", "val9")); + } + + @Test + void contentType() { + NettyRequestHeaders hs = withHeader(Http.Header.CONTENT_TYPE.defaultCase(), MediaTypes.APPLICATION_JSON.text()); + assertThat(hs.contentType().isPresent(), is(true)); + assertThat(hs.contentType().get().mediaType(), is(MediaTypes.APPLICATION_JSON)); + } + + @Test + void contentLength() { + NettyRequestHeaders hs = withHeader(Http.Header.CONTENT_LENGTH.defaultCase(), "1024"); + assertThat(hs.contentLength().isPresent(), is(true)); + assertThat(hs.contentLength().getAsLong(), is(1024L)); + } + + @Test + void cookies() { + NettyRequestHeaders hs + = withHeader(Http.Header.COOKIE.defaultCase(), + "foo=bar; aaa=bbb; c=what_the_hell; aaa=ccc", + "$version=1; some=other; $Domain=google.com, aaa=eee, d=cool; $Domain=google.com; $Path=\"/foo\""); + Parameters cookies = hs.cookies(); + assertThat(cookies.all("foo"), contains("bar")); + assertThat(cookies.all("c"), contains("what_the_hell")); + assertThat(cookies.all("aaa"), contains("bbb", "ccc", "eee")); + assertThat(cookies.all("some"), contains("other")); + assertThat(cookies.all("d"), contains("cool")); + } + + @Test + void acceptedTypes() { + NettyRequestHeaders hs = withHeader(Http.Header.ACCEPT.defaultCase(), + "text/*;q=0.3, text/html;q=0.7, text/html;level=1, text/html;level=2;q=0.4"); + assertThat(hs.acceptedTypes().size(), is(4)); + // ordered by q + assertThat(hs.acceptedTypes().get(0), is(createMt("text", "html", Map.of("level", "1")))); + assertThat(hs.acceptedTypes().get(1), is(createMt("text", "html", Map.of("q", "0.7")))); + assertThat(hs.acceptedTypes().get(2), + is(createMt("text", "html", Map.of("level", "2", "q", "0.4")))); + assertThat(hs.acceptedTypes().get(3), is(createMt("text", "*", Map.of("q", "0.3")))); + assertThat(hs.acceptedTypes().get(3).qualityFactor(), IsCloseTo.closeTo(0, 0.3)); + } + + @Test + void hucDefaultAccept() { + try { + NettyRequestHeaders hs = withHeader(Http.Header.ACCEPT.defaultCase(), + NettyRequestHeaders.HUC_ACCEPT_DEFAULT.values()); + assertThat(hs.acceptedTypes().get(0).mediaType(), is(MediaTypes.TEXT_HTML)); + assertThat(hs.acceptedTypes().get(1), is(createMt("image", "gif"))); + assertThat(hs.acceptedTypes().get(2), is(createMt("image", "jpeg"))); + assertThat(hs.acceptedTypes().get(3).mediaType(), is(MediaTypes.WILDCARD)); + assertThat(hs.acceptedTypes().get(3).qualityFactor(), IsCloseTo.closeTo(0, 0.2)); + } catch (IllegalStateException ex) { + Assertions.fail(ex.getMessage(), ex); + } + } + + @Test + void isAccepted() { + NettyRequestHeaders hs = withHeader(Http.Header.ACCEPT.defaultCase(), "text/*;q=0.3, application/json;q=0.7"); + assertThat(hs.isAccepted(MediaTypes.TEXT_HTML), is(true)); + assertThat(hs.isAccepted(MediaTypes.TEXT_XML), is(true)); + assertThat(hs.isAccepted(MediaTypes.APPLICATION_JSON), is(true)); + assertThat(hs.isAccepted(MediaTypes.APPLICATION_OCTET_STREAM), is(false)); + } + + @Test + void bestAccepted() { + NettyRequestHeaders hs = withHeader(Http.Header.ACCEPT.defaultCase(), + "text/*;q=0.3, text/html;q=0.7, text/xml;q=0.4"); + assertThat(hs.bestAccepted(MediaTypes.APPLICATION_JSON, + MediaTypes.TEXT_PLAIN, + MediaTypes.TEXT_XML).orElse(null), + is(MediaTypes.TEXT_XML)); + assertThat(hs.bestAccepted(MediaTypes.APPLICATION_JSON, + MediaTypes.TEXT_HTML, + MediaTypes.TEXT_XML).orElse(null), + is(MediaTypes.TEXT_HTML)); + assertThat(hs.bestAccepted(MediaTypes.APPLICATION_JSON, + MediaTypes.TEXT_PLAIN).orElse(null), + is(MediaTypes.TEXT_PLAIN)); + assertThat(hs.bestAccepted(MediaTypes.APPLICATION_JSON).isPresent(), is(false)); + assertThat(hs.bestAccepted().isPresent(), is(false)); + } + + @Test + void acceptDatetime() { + NettyRequestHeaders hs = withHeader(Http.Header.ACCEPT_DATETIME.defaultCase(), "Tue, 3 Jun 2008 11:05:30 GMT"); + assertThat(hs.acceptDatetime().orElse(null), is(ZDT)); + } + + @Test + void date() { + NettyRequestHeaders hs = withHeader(Http.Header.DATE.defaultCase(), "Tue, 3 Jun 2008 11:05:30 GMT"); + assertThat(hs.date().orElse(null), is(ZDT)); + } + + @Test + void ifModifiedSince() { + NettyRequestHeaders hs = withHeader(Http.Header.IF_MODIFIED_SINCE.defaultCase(), "Tue, 3 Jun 2008 11:05:30 GMT"); + assertThat(hs.ifModifiedSince().orElse(null), is(ZDT)); + } + + @Test + void ifUnmodifiedSince() { + NettyRequestHeaders hs = withHeader(Http.Header.IF_UNMODIFIED_SINCE.defaultCase(), "Tue, 3 Jun 2008 11:05:30 GMT"); + assertThat(hs.ifUnmodifiedSince().orElse(null), is(ZDT)); + } + + @Test + void referer() { + NettyRequestHeaders hs = withHeader(Http.Header.REFERER.defaultCase(), "http://www.google.com"); + assertThat(hs.referer().map(URI::toString).orElse(null), is("http://www.google.com")); + } + + private HttpMediaType createMt(String type, String subtype) { + return HttpMediaType.create(MediaTypes.create(type, subtype)); + } + + private HttpMediaType createMt(String type, String subtype, Map params) { + return HttpMediaType.builder() + .mediaType(MediaTypes.create(type, subtype)) + .parameters(params) + .build(); + } +} diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/OrderOfWritesTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/OrderOfWritesTest.java index 07e3eecd295..a51a46c17f6 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/OrderOfWritesTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/OrderOfWritesTest.java @@ -78,7 +78,7 @@ void threadMixUp() throws Exception { response = client.get() .request() .await(TIME_OUT); - Assertions.assertEquals(Http.ResponseStatus.Family.SUCCESSFUL, response.status().family()); + Assertions.assertEquals(Http.Status.Family.SUCCESSFUL, response.status().family()); String content = response.content().as(String.class).await(TIME_OUT); Assertions.assertEquals(expected, content, "Failed at " + i.get() + " " + content); } finally { diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/PlainTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/PlainTest.java index 4be1a120c2d..3134eb0e4cc 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/PlainTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/PlainTest.java @@ -40,6 +40,7 @@ import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; +import static io.helidon.common.http.Http.HeaderValues.TRANSFER_ENCODING_CHUNKED; import static io.helidon.webserver.utils.SocketHttpClient.entityFromResponse; import static io.helidon.webserver.utils.SocketHttpClient.headersFromResponse; import static org.hamcrest.CoreMatchers.containsString; @@ -74,7 +75,7 @@ static void startServer() { .host("localhost") ) .routing(r -> r.any((req, res) -> { - res.headers().add(Http.Header.TRANSFER_ENCODING, "chunked"); + res.headers().set(TRANSFER_ENCODING_CHUNKED); req.next(); }) .any("/exception", (req, res) -> { @@ -92,7 +93,7 @@ static void startServer() { res.send("In trace!"); }) .get("/force-chunked", (req, res) -> { - res.headers().put(Http.Header.TRANSFER_ENCODING, "chunked"); + res.headers().set(TRANSFER_ENCODING_CHUNKED); res.send("abcd"); }) .get("/multi", (req, res) -> { @@ -408,7 +409,7 @@ public void testForcedChunkedWithConnectionCloseHeader() throws Exception { webServer); Map headers = headersFromResponse(s); assertThat(headers, not(hasKey(equalToIgnoringCase("connection")))); - assertThat(headers, hasEntry(equalToIgnoringCase(Http.Header.TRANSFER_ENCODING), is("chunked"))); + assertThat(headers, hasEntry(equalToIgnoringCase(Http.Header.TRANSFER_ENCODING.defaultCase()), is("chunked"))); assertThat(entityFromResponse(s, false), is("4\nabcd\n0\n\n")); } @@ -497,7 +498,7 @@ public void testMultiFirstError() throws Exception { System.out.println(s); assertThat(s, startsWith("HTTP/1.1 500 Internal Server Error\n")); - assertThat(headersFromResponse(s), hasKey(equalToIgnoringCase(Http.Header.TRAILER))); + assertThat(headersFromResponse(s), hasKey(equalToIgnoringCase(Http.Header.TRAILER.defaultCase()))); Map trailerHeaders = cutTrailerHeaders(s); assertThat(trailerHeaders, hasEntry(equalToIgnoringCase("stream-status"), is("500"))); assertThat(trailerHeaders, hasEntry(equalToIgnoringCase("stream-result"), is(TEST_EXCEPTION.toString()))); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/ReasonPhraseTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/ReasonPhraseTest.java index 32dfd462052..8b75e38ab82 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/ReasonPhraseTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/ReasonPhraseTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,8 +33,8 @@ class ReasonPhraseTest { public static final String CUSTOM_ERROR = "Custom error"; - private static final Http.ResponseStatus CUSTOM_BAD_REQUEST = Http.ResponseStatus.create(BAD_REQUEST_400.code(), - CUSTOM_ERROR); + private static final Http.Status CUSTOM_BAD_REQUEST = Http.Status.create(BAD_REQUEST_400.code(), + CUSTOM_ERROR); private static WebServer server; private static WebClient client; @@ -70,7 +70,7 @@ void testDefaultReasonPhrase() { .request() .await(10, TimeUnit.SECONDS); - Http.ResponseStatus status = response.status(); + Http.Status status = response.status(); assertThat(status.code(), is(400)); assertThat(status.reasonPhrase(), is(BAD_REQUEST_400.reasonPhrase())); @@ -84,7 +84,7 @@ void testCustomReasonPhrase() { .request() .await(10, TimeUnit.SECONDS); - Http.ResponseStatus status = response.status(); + Http.Status status = response.status(); assertThat(status.code(), is(400)); assertThat(status.reasonPhrase(), is(CUSTOM_ERROR)); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/RequestContentTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/RequestContentTest.java index 4f8e15af747..99050968045 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/RequestContentTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/RequestContentTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,6 @@ import java.net.URI; import java.time.Duration; import java.time.LocalDate; -import java.util.ArrayList; import java.util.List; import java.util.concurrent.CompletableFuture; import java.util.concurrent.CompletionStage; @@ -34,14 +33,18 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; +import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; import io.helidon.media.common.ContentReaders; import io.helidon.media.common.MediaContext; import io.helidon.media.common.MessageBodyFilter; +import io.helidon.media.common.MessageBodyReader; +import io.helidon.media.common.MessageBodyReaderContext; import io.helidon.webserver.utils.TestUtils; +import io.netty.handler.codec.http.DefaultHttpHeaders; import org.junit.jupiter.api.Test; import static io.helidon.webserver.utils.TestUtils.requestChunkAsString; @@ -60,21 +63,11 @@ /** * The RequestContentTest. */ +@SuppressWarnings("unchecked") public class RequestContentTest { - private static Request requestTestStub(Publisher flux) { - BareRequest bareRequestMock = mock(BareRequest.class); - doReturn(URI.create("http://0.0.0.0:1234")).when(bareRequestMock).uri(); - doReturn(flux).when(bareRequestMock).bodyPublisher(); - WebServer webServer = mock(WebServer.class); - MediaContext mediaContext = MediaContext.create(); - doReturn(mediaContext.readerContext()).when(webServer).readerContext(); - doReturn(mediaContext.writerContext()).when(webServer).writerContext(); - return new RequestTestStub(bareRequestMock, webServer); - } - @Test - public void directSubscriptionTest() throws Exception { + public void directSubscriptionTest() { Request request = requestTestStub(Multi.just("first", "second", "third").map(s -> DataChunk.create(s.getBytes()))); StringBuilder sb = new StringBuilder(); Multi.create(request.content()).subscribe(chunk -> sb.append(requestChunkAsString(chunk)).append("-")); @@ -82,7 +75,7 @@ public void directSubscriptionTest() throws Exception { } @Test - public void upperCaseFilterTest() throws Exception { + public void upperCaseFilterTest() { Request request = requestTestStub(Multi.just("first", "second", "third").map(s -> DataChunk.create(s.getBytes()))); StringBuilder sb = new StringBuilder(); request.content().registerFilter((Publisher publisher) -> { @@ -124,67 +117,72 @@ public void multiThreadingFilterAndReaderTest() throws Exception { request.content().registerFilter(originalPublisher -> subscriberDelegate -> originalPublisher.subscribe(new Subscriber() { + @Override + public void onSubscribe(Subscription subscription) { + subscriberDelegate.onSubscribe(subscription); + subscribedLatch.countDown(); + } + + @Override + public void onNext(DataChunk item) { + // mapping the on next call only + subscriberDelegate.onNext( + DataChunk.create(requestChunkAsString(item) + .toUpperCase().getBytes())); + } + + @Override + public void onError(Throwable throwable) { + subscriberDelegate.onError(throwable); + } + + @Override + public void onComplete() { + subscriberDelegate.onComplete(); + } + })); + + request.content().registerReader(new MessageBodyReader() { + @Override + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + if (type.rawType().equals(List.class)) { + return PredicateResult.SUPPORTED; + } + return PredicateResult.NOT_SUPPORTED; + } + + @Override + public Single read(Publisher publisher, + GenericType type, + MessageBodyReaderContext context) { + + CompletableFuture future = new CompletableFuture<>(); + List list = new CopyOnWriteArrayList<>(); + + publisher.subscribe(new Subscriber<>() { @Override public void onSubscribe(Subscription subscription) { - subscriberDelegate.onSubscribe(subscription); + subscription.request(Long.MAX_VALUE); subscribedLatch.countDown(); } @Override public void onNext(DataChunk item) { - // mapping the on next call only - subscriberDelegate.onNext( - DataChunk.create(requestChunkAsString(item) - .toUpperCase().getBytes())); + list.add(TestUtils.requestChunkAsString(item)); } @Override public void onError(Throwable throwable) { - subscriberDelegate.onError(throwable); + fail("Received an exception: " + throwable.getMessage()); } @Override public void onComplete() { - subscriberDelegate.onComplete(); + future.complete(list); } - })); - request.content().registerReader(Iterable.class, (publisher1, clazz) -> { - fail("Iterable reader should have not been used!"); - throw new IllegalStateException("unreachable code"); - }); - - request.content().registerReader(ArrayList.class, (publisher1, clazz) -> { - fail("ArrayList reader should have not been used!"); - throw new IllegalStateException("unreachable code"); - }); - - request.content().registerReader(List.class, (publisher1, clazz) -> { - CompletableFuture future = new CompletableFuture<>(); - List list = new CopyOnWriteArrayList<>(); - - publisher1.subscribe(new Subscriber() { - @Override - public void onSubscribe(Subscription subscription) { - subscription.request(Long.MAX_VALUE); - subscribedLatch.countDown(); - } - - @Override - public void onNext(DataChunk item) { - list.add(TestUtils.requestChunkAsString(item)); - } - - @Override - public void onError(Throwable throwable) { - fail("Received an exception: " + throwable.getMessage()); - } - - @Override - public void onComplete() { - future.complete(list); - } - }); - return future; + }); + return Single.create(future); + } }); List result = (List) request.content().as(List.class).toCompletableFuture() @@ -203,9 +201,21 @@ public void failingFilter() throws Exception { throw new IllegalStateException("failed-publisher-transformation"); }); - request.content().registerReader(Duration.class, (publisher, clazz) -> { - fail("Should not be called"); - throw new IllegalStateException("unreachable code"); + request.content().registerReader(new MessageBodyReader() { + @Override + public Single read(Publisher publisher, GenericType type, + MessageBodyReaderContext context) { + fail("Should not be called"); + throw new IllegalStateException("unreachable code"); + } + + @Override + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + if (type.equals(GenericType.create(Duration.class))) { + return PredicateResult.SUPPORTED; + } + return PredicateResult.NOT_SUPPORTED; + } }); CompletableFuture future = request.content().as(Duration.class) @@ -215,10 +225,10 @@ public void failingFilter() throws Exception { fail("Should have thrown an exception"); } catch (ExecutionException e) { assertThat(e.getCause(), - allOf(instanceOf(IllegalStateException.class), - hasProperty("message", containsString("Transformation failed!")))); + allOf(instanceOf(IllegalStateException.class), + hasProperty("message", containsString("Transformation failed!")))); assertThat(e.getCause().getCause(), - hasProperty("message", containsString("failed-publisher-transformation"))); + hasProperty("message", containsString("failed-publisher-transformation"))); } } @@ -226,8 +236,20 @@ public void failingFilter() throws Exception { public void failingReader() throws Exception { Request request = requestTestStub(Single.never()); - request.content().registerReader(Duration.class, (publisher, clazz) -> { - throw new IllegalStateException("failed-read"); + request.content().registerReader(new MessageBodyReader() { + @Override + public Single read(Publisher publisher, GenericType type, + MessageBodyReaderContext context) { + throw new IllegalStateException("failed read"); + } + + @Override + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + if (type.equals(GenericType.create(Duration.class))) { + return PredicateResult.SUPPORTED; + } + return PredicateResult.NOT_SUPPORTED; + } }); try { @@ -235,10 +257,10 @@ public void failingReader() throws Exception { fail("Should have thrown an exception"); } catch (ExecutionException e) { assertThat(e.getCause(), - allOf(instanceOf(IllegalStateException.class), - hasProperty("message", containsString("Transformation failed!")))); + allOf(instanceOf(IllegalStateException.class), + hasProperty("message", containsString("Transformation failed!")))); assertThat(e.getCause().getCause(), - hasProperty("message", containsString("failed-read"))); + hasProperty("message", containsString("failed-read"))); } } @@ -246,10 +268,6 @@ public void failingReader() throws Exception { public void missingReaderTest() throws Exception { Request request = requestTestStub(Single.just(DataChunk.create("hello".getBytes()))); - request.content().registerReader(LocalDate.class, (publisher, clazz) -> { - throw new IllegalStateException("Should not be called"); - }); - CompletableFuture future = request.content().as(Duration.class).toCompletableFuture(); try { future.get(10, TimeUnit.SECONDS); @@ -260,15 +278,15 @@ public void missingReaderTest() throws Exception { } @Test - public void nullFilter() throws Exception { + public void nullFilter() { Request request = requestTestStub(Single.never()); assertThrows(NullPointerException.class, () -> { - request.content().registerFilter((MessageBodyFilter)null); + request.content().registerFilter((MessageBodyFilter) null); }); } @Test - public void failingSubscribe() throws Exception { + public void failingSubscribe() { Request request = requestTestStub(Multi.singleton(DataChunk.create("data".getBytes()))); request.content().registerFilter((Publisher publisher) -> { @@ -284,7 +302,8 @@ public void failingSubscribe() throws Exception { Throwable throwable = receivedThrowable.get(); assertThat(throwable, allOf(instanceOf(IllegalArgumentException.class), - hasProperty("message", containsString("Unexpected exception occurred during publishers chaining")))); + hasProperty("message", + containsString("Unexpected exception occurred during publishers chaining")))); assertThat(throwable.getCause(), hasProperty("message", containsString("failed-publisher-transformation"))); } @@ -292,14 +311,26 @@ public void failingSubscribe() throws Exception { public void readerTest() throws Exception { Request request = requestTestStub(Multi.singleton(DataChunk.create("2010-01-02".getBytes()))); - request.content().registerReader(LocalDate.class, - (publisher, clazz) -> ContentReaders + request.content().registerReader(new MessageBodyReader() { + @Override + public Single read(Publisher publisher, GenericType type, MessageBodyReaderContext context) { + return (Single) Single.create(ContentReaders .readString(publisher, Request.contentCharset(request)) .toStage() .thenApply(LocalDate::parse)); + } + + @Override + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + if (type.rawType().equals(LocalDate.class)) { + return PredicateResult.SUPPORTED; + } + return PredicateResult.NOT_SUPPORTED; + } + }); CompletionStage complete = request.content().as(LocalDate.class) - .thenApply(o -> o.getDayOfMonth() + "/" + o.getMonthValue() + "/" + o.getYear()); + .thenApply(o -> o.getDayOfMonth() + "/" + o.getMonthValue() + "/" + o.getYear()); String result = complete.toCompletableFuture().get(10, TimeUnit.SECONDS); assertThat(result, is("2/1/2010")); @@ -309,7 +340,7 @@ public void readerTest() throws Exception { public void implicitByteArrayContentReader() throws Exception { Request request = requestTestStub(Multi.singleton(DataChunk.create("test-string".getBytes()))); CompletionStage complete = request.content().as(byte[].class).thenApply(String::new); - assertThat(complete.toCompletableFuture().get(10, TimeUnit.SECONDS), is("test-string")); + assertThat(complete.toCompletableFuture().get(10, TimeUnit.SECONDS), is("test-string")); } @Test @@ -323,20 +354,53 @@ public void implicitStringContentReader() throws Exception { public void overridingStringContentReader() throws Exception { Request request = requestTestStub(Multi.singleton(DataChunk.create("test-string".getBytes()))); - request.content().registerReader(String.class, (publisher, clazz) -> { - fail("Should not be called"); - throw new IllegalStateException("unreachable code"); + request.content().registerReader(new MessageBodyReader() { + @Override + public Single read(Publisher publisher, GenericType type, MessageBodyReaderContext context) { + fail("Should not be called"); + throw new IllegalStateException("unreachable code"); + } + + @Override + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + if (type.equals(GenericType.STRING)) { + return PredicateResult.SUPPORTED; + } + return PredicateResult.NOT_SUPPORTED; + } }); - request.content().registerReader(String.class, (publisher, clazz) -> { - return Multi.create(publisher) - .map(TestUtils::requestChunkAsString) - .map(String::toUpperCase) - .collectList() - .map((strings -> strings.get(0))) - .toStage(); + request.content().registerReader(new MessageBodyReader() { + @Override + public Single read(Publisher publisher, GenericType type, MessageBodyReaderContext context) { + return (Single) Multi.create(publisher) + .map(TestUtils::requestChunkAsString) + .map(String::toUpperCase) + .collectList() + .map((strings -> strings.get(0))); + } + + @Override + public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { + if (type.equals(GenericType.STRING)) { + return PredicateResult.SUPPORTED; + } + return PredicateResult.NOT_SUPPORTED; + } }); CompletionStage complete = request.content().as(String.class); assertThat(complete.toCompletableFuture().get(10, TimeUnit.SECONDS), is("TEST-STRING")); } + + private static Request requestTestStub(Publisher flux) { + BareRequest bareRequestMock = mock(BareRequest.class); + doReturn(URI.create("http://0.0.0.0:1234")).when(bareRequestMock).uri(); + doReturn(flux).when(bareRequestMock).bodyPublisher(); + WebServer webServer = mock(WebServer.class); + MediaContext mediaContext = MediaContext.create(); + doReturn(mediaContext.readerContext()).when(webServer).readerContext(); + doReturn(mediaContext.writerContext()).when(webServer).writerContext(); + doReturn(new NettyRequestHeaders(new DefaultHttpHeaders())).when(bareRequestMock).headers(); + return new RequestTestStub(bareRequestMock, webServer); + } } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/RequestPredicateTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/RequestPredicateTest.java index f5295e3af71..5d424ae17ab 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/RequestPredicateTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/RequestPredicateTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,12 +20,17 @@ import java.util.function.Predicate; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.webserver.RoutingTest.RoutingChecker; +import io.netty.handler.codec.http.DefaultHttpHeaders; +import io.netty.handler.codec.http.HttpHeaders; import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import static io.helidon.common.http.Http.Header.ACCEPT; +import static io.helidon.common.http.Http.Header.CONTENT_TYPE; +import static io.helidon.common.http.Http.Header.COOKIE; import static io.helidon.webserver.RoutingTest.mockResponse; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -36,6 +41,8 @@ */ public class RequestPredicateTest { + public static final Http.HeaderName MY_HEADER = Http.Header.create("my-header"); + @Test public void isOfMethod1() { final RoutingChecker checker = new RoutingChecker(); @@ -93,21 +100,21 @@ public void containsHeader() { final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .get("/exists", RequestPredicate.create() - .containsHeader("my-header") + .containsHeader(MY_HEADER) .thenApply((req, resp) -> { checker.handlerInvoked("headerFound"); }).otherwise((req, res) -> { checker.handlerInvoked("headerNotFound"); })) .get("/valid", RequestPredicate.create() - .containsHeader("my-header", "abc"::equals) + .containsHeader(MY_HEADER, "abc"::equals) .thenApply((req, resp) -> { checker.handlerInvoked("headerIsValid"); }).otherwise((req, res) -> { checker.handlerInvoked("headerIsNotValid"); })) .get("/equals", RequestPredicate.create() - .containsHeader("my-header", "abc") + .containsHeader(MY_HEADER, "abc") .thenApply((req, resp) -> { checker.handlerInvoked("headerIsEqual"); }).otherwise((req, res) -> { @@ -120,7 +127,7 @@ public void containsHeader() { .containsHeader(null); }); - routing.route(mockRequest("/exists", Map.of("my-header", List.of("abc"))), + routing.route(mockRequest("/exists", Map.of(MY_HEADER, List.of("abc"))), mockResponse()); assertThat(checker.handlersInvoked(), is("headerFound")); @@ -134,7 +141,7 @@ public void containsValidHeader() { final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .get("/valid", RequestPredicate.create() - .containsHeader("my-header", "abc"::equals) + .containsHeader(MY_HEADER, "abc"::equals) .thenApply((req, resp) -> { checker.handlerInvoked("headerIsValid"); }).otherwise((req, res) -> { @@ -144,7 +151,7 @@ public void containsValidHeader() { assertThrows(NullPointerException.class, () -> { RequestPredicate.create() - .containsHeader("my-header", (Predicate) null); + .containsHeader(MY_HEADER, (Predicate) null); }); assertThrows(NullPointerException.class, () -> { @@ -152,12 +159,12 @@ public void containsValidHeader() { .containsHeader(null, "abc"::equals); }); - routing.route(mockRequest("/valid", Map.of("my-header", List.of("abc"))), + routing.route(mockRequest("/valid", Map.of(MY_HEADER, List.of("abc"))), mockResponse()); assertThat(checker.handlersInvoked(), is("headerIsValid")); checker.reset(); - routing.route(mockRequest("/valid", Map.of("my-header", List.of("def"))), + routing.route(mockRequest("/valid", Map.of(MY_HEADER, List.of("def"))), mockResponse()); assertThat(checker.handlersInvoked(), is("headerIsNotValid")); } @@ -167,7 +174,7 @@ public void containsExactHeader() { final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .get("/equals", RequestPredicate.create() - .containsHeader("my-header", "abc") + .containsHeader(MY_HEADER, "abc") .thenApply((req, resp) -> { checker.handlerInvoked("headerIsEqual"); }).otherwise((req, res) -> { @@ -177,7 +184,7 @@ public void containsExactHeader() { assertThrows(NullPointerException.class, () -> { RequestPredicate.create() - .containsHeader("my-header", (String) null); + .containsHeader(MY_HEADER, (String) null); }); assertThrows(NullPointerException.class, () -> { @@ -185,12 +192,12 @@ public void containsExactHeader() { .containsHeader(null, "abc"); }); - routing.route(mockRequest("/equals", Map.of("my-header", List.of("abc"))), + routing.route(mockRequest("/equals", Map.of(MY_HEADER, List.of("abc"))), mockResponse()); assertThat(checker.handlersInvoked(), is("headerIsEqual")); checker.reset(); - routing.route(mockRequest("/equals", Map.of("my-header", List.of("def"))), + routing.route(mockRequest("/equals", Map.of(MY_HEADER, List.of("def"))), mockResponse()); assertThat(checker.handlersInvoked(), is("headerIsNotEqual")); } @@ -315,12 +322,12 @@ public void containsCookie() { .containsCookie(null); }); - routing.route(mockRequest("/exists", Map.of("cookie", - List.of("my-cookie=abc"))), mockResponse()); + routing.route(mockRequest("/exists", Map.of(COOKIE, + List.of("my-cookie=abc"))), mockResponse()); assertThat(checker.handlersInvoked(), is("cookieFound")); checker.reset(); - routing.route(mockRequest("/exists", Map.of("cookie", + routing.route(mockRequest("/exists", Map.of(COOKIE, List.of("other-cookie=abc"))), mockResponse()); assertThat(checker.handlersInvoked(), is("cookieNotFound")); } @@ -348,17 +355,17 @@ public void containsValidCookie() { .containsCookie(null, "abc"); }); - routing.route(mockRequest("/valid", Map.of("cookie", + routing.route(mockRequest("/valid", Map.of(COOKIE, List.of("my-cookie=abc"))), mockResponse()); assertThat(checker.handlersInvoked(), is("cookieIsValid")); checker.reset(); - routing.route(mockRequest("/valid", Map.of("cookie", + routing.route(mockRequest("/valid", Map.of(COOKIE, List.of("my-cookie=def"))), mockResponse()); assertThat(checker.handlersInvoked(), is("cookieIsNotValid")); checker.reset(); - routing.route(mockRequest("/valid", Map.of("cookie", + routing.route(mockRequest("/valid", Map.of(COOKIE, List.of("my-cookie="))), mockResponse()); assertThat(checker.handlersInvoked(), is("cookieIsNotValid")); } @@ -386,12 +393,12 @@ public void containsExactCookie() { .containsCookie(null, "abc"); }); - routing.route(mockRequest("/equals", Map.of("cookie", + routing.route(mockRequest("/equals", Map.of(COOKIE, List.of("my-cookie=abc"))), mockResponse()); assertThat(checker.handlersInvoked(), is("cookieIsEqual")); checker.reset(); - routing.route(mockRequest("/equals", Map.of("cookie", + routing.route(mockRequest("/equals", Map.of(COOKIE, List.of("my-cookie=def"))), mockResponse()); assertThat(checker.handlersInvoked(), is("cookieIsNotEqual")); } @@ -414,22 +421,22 @@ public void accepts1() { .accepts((String[]) null); }); - routing.route(mockRequest("/accepts1", Map.of("Accept", + routing.route(mockRequest("/accepts1", Map.of(ACCEPT, List.of("application/json"))), mockResponse()); assertThat(checker.handlersInvoked(), is("acceptsMediaType")); checker.reset(); - routing.route(mockRequest("/accepts1", Map.of("Accept", + routing.route(mockRequest("/accepts1", Map.of(ACCEPT, List.of("text/plain"))), mockResponse()); assertThat(checker.handlersInvoked(), is("acceptsMediaType")); checker.reset(); - routing.route(mockRequest("/accepts1", Map.of("Accept", + routing.route(mockRequest("/accepts1", Map.of(ACCEPT, List.of("text/plain", "application/xml"))), mockResponse()); assertThat(checker.handlersInvoked(), is("acceptsMediaType")); checker.reset(); - routing.route(mockRequest("/accepts1", Map.of("Accept", List.of())), + routing.route(mockRequest("/accepts1", Map.of(ACCEPT, List.of())), mockResponse()); assertThat(checker.handlersInvoked(), is("acceptsMediaType")); @@ -438,7 +445,7 @@ public void accepts1() { assertThat(checker.handlersInvoked(), is("acceptsMediaType")); checker.reset(); - routing.route(mockRequest("/accepts1", Map.of("Accept", + routing.route(mockRequest("/accepts1", Map.of(ACCEPT, List.of("application/xml"))), mockResponse()); assertThat(checker.handlersInvoked(), is("doesNotAcceptMediaType")); } @@ -448,8 +455,8 @@ public void accepts2() { final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .get("/accepts2", RequestPredicate.create() - .accepts(MediaType.TEXT_PLAIN, - MediaType.APPLICATION_JSON) + .accepts(HttpMediaType.PLAINTEXT_UTF_8, + HttpMediaType.JSON_UTF_8) .thenApply((req, resp) -> { checker.handlerInvoked("acceptsMediaType"); }).otherwise((req, res) -> { @@ -459,25 +466,25 @@ public void accepts2() { assertThrows(NullPointerException.class, () -> { RequestPredicate.create() - .accepts((MediaType[]) null); + .accepts((HttpMediaType[]) null); }); - routing.route(mockRequest("/accepts2", Map.of("Accept", + routing.route(mockRequest("/accepts2", Map.of(ACCEPT, List.of("application/json"))), mockResponse()); assertThat(checker.handlersInvoked(), is("acceptsMediaType")); checker.reset(); - routing.route(mockRequest("/accepts2", Map.of("Accept", + routing.route(mockRequest("/accepts2", Map.of(ACCEPT, List.of("text/plain"))), mockResponse()); assertThat(checker.handlersInvoked(), is("acceptsMediaType")); checker.reset(); - routing.route(mockRequest("/accepts2", Map.of("Accept", + routing.route(mockRequest("/accepts2", Map.of(ACCEPT, List.of("text/plain", "application/xml"))), mockResponse()); assertThat(checker.handlersInvoked(), is("acceptsMediaType")); checker.reset(); - routing.route(mockRequest("/accepts2", Map.of("Accept", List.of())), + routing.route(mockRequest("/accepts2", Map.of(ACCEPT, List.of())), mockResponse()); assertThat(checker.handlersInvoked(), is("acceptsMediaType")); @@ -486,7 +493,7 @@ public void accepts2() { assertThat(checker.handlersInvoked(), is("acceptsMediaType")); checker.reset(); - routing.route(mockRequest("/accepts2", Map.of("Accept", + routing.route(mockRequest("/accepts2", Map.of(ACCEPT, List.of("application/xml"))), mockResponse()); assertThat(checker.handlersInvoked(), is("doesNotAcceptMediaType")); } @@ -509,27 +516,27 @@ public void hasContentType1() { .hasContentType((String[]) null); }); - routing.route(mockRequest("/contentType1", Map.of("Content-Type", + routing.route(mockRequest("/contentType1", Map.of(CONTENT_TYPE, List.of("text/plain"))), mockResponse()); assertThat(checker.handlersInvoked(), is("hasContentType")); checker.reset(); - routing.route(mockRequest("/contentType1", Map.of("Content-Type", + routing.route(mockRequest("/contentType1", Map.of(CONTENT_TYPE, List.of("text/plain"))), mockResponse()); assertThat(checker.handlersInvoked(), is("hasContentType")); checker.reset(); - routing.route(mockRequest("/contentType1", Map.of("Content-Type", + routing.route(mockRequest("/contentType1", Map.of(CONTENT_TYPE, List.of("application/json"))), mockResponse()); assertThat(checker.handlersInvoked(), is("hasContentType")); checker.reset(); - routing.route(mockRequest("/contentType1", Map.of("Content-Type", + routing.route(mockRequest("/contentType1", Map.of(CONTENT_TYPE, List.of("application/xml"))), mockResponse()); assertThat(checker.handlersInvoked(), is("doesNotHaveContentType")); checker.reset(); - routing.route(mockRequest("/contentType1", Map.of("Content-Type", + routing.route(mockRequest("/contentType1", Map.of(CONTENT_TYPE, List.of())), mockResponse()); assertThat(checker.handlersInvoked(), is("doesNotHaveContentType")); @@ -543,8 +550,8 @@ public void hasContentType2() { final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .get("/contentType2", RequestPredicate.create() - .hasContentType(MediaType.TEXT_PLAIN, - MediaType.APPLICATION_JSON) + .hasContentType(HttpMediaType.PLAINTEXT_UTF_8, + HttpMediaType.JSON_UTF_8) .thenApply((req, resp) -> { checker.handlerInvoked("hasContentType"); }).otherwise((req, res) -> { @@ -554,30 +561,30 @@ public void hasContentType2() { assertThrows(NullPointerException.class, () -> { RequestPredicate.create() - .hasContentType((MediaType[]) null); + .hasContentType(new HttpMediaType[0]); }); - routing.route(mockRequest("/contentType2", Map.of("Content-Type", + routing.route(mockRequest("/contentType2", Map.of(CONTENT_TYPE, List.of("text/plain"))), mockResponse()); assertThat(checker.handlersInvoked(), is("hasContentType")); checker.reset(); - routing.route(mockRequest("/contentType2", Map.of("Content-Type", + routing.route(mockRequest("/contentType2", Map.of(CONTENT_TYPE, List.of("text/plain"))), mockResponse()); assertThat(checker.handlersInvoked(), is("hasContentType")); checker.reset(); - routing.route(mockRequest("/contentType2", Map.of("Content-Type", + routing.route(mockRequest("/contentType2", Map.of(CONTENT_TYPE, List.of("application/json"))), mockResponse()); assertThat(checker.handlersInvoked(), is("hasContentType")); checker.reset(); - routing.route(mockRequest("/contentType2", Map.of("Content-Type", + routing.route(mockRequest("/contentType2", Map.of(CONTENT_TYPE, List.of("application/xml"))), mockResponse()); assertThat(checker.handlersInvoked(), is("doesNotHaveContentType")); checker.reset(); - routing.route(mockRequest("/contentType2", Map.of("Content-Type", + routing.route(mockRequest("/contentType2", Map.of(CONTENT_TYPE, List.of())), mockResponse()); assertThat(checker.handlersInvoked(), is("doesNotHaveContentType")); @@ -591,8 +598,8 @@ public void multipleConditions() { final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .any("/multiple", RequestPredicate.create() - .accepts(MediaType.TEXT_PLAIN) - .hasContentType(MediaType.TEXT_PLAIN) + .accepts(HttpMediaType.PLAINTEXT_UTF_8) + .hasContentType(HttpMediaType.PLAINTEXT_UTF_8) .containsQueryParameter("my-param") .containsCookie("my-cookie") .isOfMethod(Http.Method.GET) @@ -604,15 +611,15 @@ public void multipleConditions() { .build(); routing.route(mockRequest("/multiple?my-param=abc", - Map.of("Content-Type", List.of("text/plain"), - "Accept", List.of("text/plain"), - "Cookie", List.of("my-cookie=abc"))), mockResponse()); + Map.of(CONTENT_TYPE, List.of("text/plain"), + ACCEPT, List.of("text/plain"), + COOKIE, List.of("my-cookie=abc"))), mockResponse()); assertThat(checker.handlersInvoked(), is("hasAllConditions")); checker.reset(); routing.route(mockRequest("/multiple?my-param=abc", - Map.of("Accept", List.of("text/plain"), - "Cookie", List.of("my-cookie=abc"))), mockResponse()); + Map.of(ACCEPT, List.of("text/plain"), + COOKIE, List.of("my-cookie=abc"))), mockResponse()); assertThat(checker.handlersInvoked(), is("doesNotHaveAllConditions")); } @@ -621,8 +628,8 @@ public void and(){ final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .get("/and", RequestPredicate.create() - .accepts(MediaType.TEXT_PLAIN) - .and((req) -> req.headers().first("my-header").isPresent()) + .accepts(HttpMediaType.PLAINTEXT_UTF_8) + .and((req) -> req.headers().contains(MY_HEADER)) .thenApply((req, resp) -> { checker.handlerInvoked("hasAllConditions"); }).otherwise((req, res) -> { @@ -631,13 +638,13 @@ public void and(){ .build(); routing.route(mockRequest("/and", - Map.of("Accept", List.of("text/plain"), - "my-header", List.of("abc"))), mockResponse()); + Map.of(ACCEPT, List.of("text/plain"), + MY_HEADER, List.of("abc"))), mockResponse()); assertThat(checker.handlersInvoked(), is("hasAllConditions")); checker.reset(); routing.route(mockRequest("/and", - Map.of("Accept", List.of("text/plain"))), mockResponse()); + Map.of(ACCEPT, List.of("text/plain"))), mockResponse()); assertThat(checker.handlersInvoked(), is("doesNotHaveAllConditions")); } @@ -646,8 +653,8 @@ public void or(){ final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .get("/or", RequestPredicate.create() - .hasContentType(MediaType.TEXT_PLAIN) - .or((req) -> req.headers().first("my-header").isPresent()) + .hasContentType(HttpMediaType.PLAINTEXT_UTF_8) + .or((req) -> req.headers().contains(MY_HEADER)) .thenApply((req, resp) -> { checker.handlerInvoked("hasAnyCondition"); }).otherwise((req, res) -> { @@ -656,12 +663,12 @@ public void or(){ .build(); routing.route(mockRequest("/or", - Map.of("Content-Type", List.of("text/plain"))), mockResponse()); + Map.of(CONTENT_TYPE, List.of("text/plain"))), mockResponse()); assertThat(checker.handlersInvoked(), is("hasAnyCondition")); checker.reset(); routing.route(mockRequest("/or", - Map.of("my-header", List.of("abc"))), mockResponse()); + Map.of(MY_HEADER, List.of("abc"))), mockResponse()); assertThat(checker.handlersInvoked(), is("hasAnyCondition")); checker.reset(); @@ -674,7 +681,7 @@ public void negate(){ final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .get("/negate", RequestPredicate.create() - .hasContentType(MediaType.TEXT_PLAIN) + .hasContentType(HttpMediaType.PLAINTEXT_UTF_8) .containsCookie("my-cookie") .negate() .thenApply((req, resp) -> { @@ -685,13 +692,13 @@ public void negate(){ .build(); routing.route(mockRequest("/negate", - Map.of("Content-Type", List.of("application/json"))), mockResponse()); + Map.of(CONTENT_TYPE, List.of("application/json"))), mockResponse()); assertThat(checker.handlersInvoked(), is("hasAllConditions")); checker.reset(); routing.route(mockRequest("/negate", - Map.of("Content-Type", List.of("text/plain"), - "Cookie", List.of("my-cookie=abc"))), mockResponse()); + Map.of(CONTENT_TYPE, List.of("text/plain"), + COOKIE, List.of("my-cookie=abc"))), mockResponse()); assertThat(checker.handlersInvoked(), is("doesNotHaveAllConditions")); } @@ -699,9 +706,9 @@ public void negate(){ public void nextAlreadySet(){ RequestPredicate requestPredicate = RequestPredicate.create() .containsCookie("my-cookie"); - requestPredicate.containsHeader("my-header"); + requestPredicate.containsHeader(MY_HEADER); assertThrows(IllegalStateException.class, () -> { - requestPredicate.containsHeader("my-param"); + requestPredicate.containsHeader(Http.Header.create("my-param")); }); } @@ -719,11 +726,13 @@ private static BareRequest mockRequest(final String path, } private static BareRequest mockRequest(final String path, - final Map> headers) { + final Map> headers) { - BareRequest bareRequestMock = RoutingTest.mockRequest(path, - Http.Method.GET); - Mockito.doReturn(headers).when(bareRequestMock).headers(); + BareRequest bareRequestMock = RoutingTest.mockRequest(path, Http.Method.GET); + HttpHeaders nettyHeaders = new DefaultHttpHeaders(); + headers.forEach((key, value) -> nettyHeaders.set(key.defaultCase(), value)); + RequestHeaders rh = new NettyRequestHeaders(nettyHeaders); + Mockito.doReturn(rh).when(bareRequestMock).headers(); return bareRequestMock; } } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/RequestTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/RequestTest.java index a3a9bd1f4f0..5253b58f3c0 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/RequestTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/RequestTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ public class RequestTest { @Test - public void createPathTest() throws Exception { + public void createPathTest() { Request.Path path = Request.Path.create(null, "/foo/bar/baz", Map.of("a", "va", "b", "vb", "var", "1")); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/RequestTestStub.java b/webserver/webserver/src/test/java/io/helidon/webserver/RequestTestStub.java index 6e8bd4bdf99..5e2173635c6 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/RequestTestStub.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/RequestTestStub.java @@ -48,7 +48,7 @@ public Tracer tracer() { } RequestTestStub(BareRequest req, WebServer webServer, Span span) { - super(req, webServer, new HashRequestHeaders(req.headers())); + super(req, webServer, req.headers()); this.span = span == null ? mock(Span.class) : span; } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/ResponseOrderingTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/ResponseOrderingTest.java index a27d8344511..54232412201 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/ResponseOrderingTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/ResponseOrderingTest.java @@ -27,7 +27,7 @@ import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Multi; import io.helidon.webclient.WebClient; @@ -147,7 +147,7 @@ public void testContentOrdering() throws Exception { } webClient.post() - .contentType(MediaType.APPLICATION_OCTET_STREAM) + .contentType(MediaTypes.APPLICATION_OCTET_STREAM) .submit(sb.toString().getBytes(), String.class) .thenAccept(it -> assertThat(it, is(sb.toString()))); } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/ResponseTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/ResponseTest.java index 43f9bf067d6..3d7c9e1a52e 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/ResponseTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/ResponseTest.java @@ -32,18 +32,20 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.Http.Header; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; import io.helidon.tracing.SpanContext; import org.junit.jupiter.api.Test; +import static io.helidon.common.testing.http.HttpHeaderMatcher.hasHeader; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; -import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.beans.HasPropertyWithValue.hasProperty; import static org.hamcrest.core.AllOf.allOf; import static org.hamcrest.core.IsInstanceOf.instanceOf; @@ -88,9 +90,9 @@ public void headersAreCaseInsensitive() throws Exception { headers.addCookie("cookie1", "cookie-value-1"); headers.addCookie("cookie2", "cookie-value-2"); - headers.add("Header", "hv1"); - headers.add("header", "hv2"); - headers.add("heaDer", "hv3"); + headers.add(Header.create("Header"), "hv1"); + headers.add(Header.create("header"), "hv2"); + headers.add(Header.create("heaDer"), "hv3"); assertHeaders(headers, "Set-Cookie","cookie1=cookie-value-1", "cookie2=cookie-value-2"); assertHeadersToMap(headers, "Set-Cookie","cookie1=cookie-value-1", "cookie2=cookie-value-2"); @@ -109,15 +111,11 @@ public void headersAreCaseInsensitive() throws Exception { } private static void assertHeaders(ResponseHeaders headers, String headerName, String... expectedValues) { - final List actualValues = headers.all(headerName); - assertThat("Value count doesn't match for header: " + headerName, actualValues, hasSize(expectedValues.length)); - assertThat("Content does not match for header: " + headerName, actualValues, containsInAnyOrder(expectedValues)); + assertThat(headers, hasHeader(Header.create(headerName), containsInAnyOrder(expectedValues))); } private static void assertHeadersToMap(ResponseHeaders headers, String headerName, String... expectedValues) { - final List actualValues = headers.toMap().get(headerName); - assertThat("Value count doesn't match for header: " + headerName, actualValues, hasSize(expectedValues.length)); - assertThat("Content does not match for header: " + headerName, actualValues, containsInAnyOrder(expectedValues)); + assertThat(headers, hasHeader(Header.create(headerName), containsInAnyOrder(expectedValues))); } @SuppressWarnings("unchecked") @@ -220,19 +218,19 @@ public void writerWithMediaType() throws Exception { return Single.empty(); }; Response response = new ResponseImpl(new NoOpBareResponse(null)); - response.registerWriter(CharSequence.class, MediaType.TEXT_PLAIN, stringWriter); - response.registerWriter(Number.class, MediaType.APPLICATION_JSON, numberWriter); + response.registerWriter(CharSequence.class, MediaTypes.TEXT_PLAIN, stringWriter); + response.registerWriter(Number.class, MediaTypes.APPLICATION_JSON, numberWriter); marshall(response, "foo"); assertThat(sb.toString(), is("A")); - assertThat(response.headers().contentType().orElse(null), is(MediaType.TEXT_PLAIN)); + assertThat(response.headers().contentType().orElse(null), is(HttpMediaType.TEXT_PLAIN)); sb.setLength(0); response = new ResponseImpl(new NoOpBareResponse(null)); - response.registerWriter(CharSequence.class, MediaType.TEXT_PLAIN, stringWriter); - response.registerWriter(Number.class, MediaType.APPLICATION_JSON, numberWriter); + response.registerWriter(CharSequence.class, MediaTypes.TEXT_PLAIN, stringWriter); + response.registerWriter(Number.class, MediaTypes.APPLICATION_JSON, numberWriter); marshall(response, 1); assertThat(sb.toString(), is("B")); - assertThat(response.headers().contentType().orElse(null), is(MediaType.APPLICATION_JSON)); + assertThat(response.headers().contentType().orElse(null), is(HttpMediaType.APPLICATION_JSON)); } @Test @@ -277,7 +275,7 @@ static class NoOpBareResponse implements BareResponse { } @Override - public void writeStatusAndHeaders(Http.ResponseStatus status, Map> headers) { + public void writeStatusAndHeaders(Http.Status status, Map> headers) { sb.append("h").append(status.code()); } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/RouteListTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/RouteListTest.java index c37620d712b..c84926906fe 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/RouteListTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/RouteListTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,16 +60,16 @@ public void testAcceptMethod() throws Exception { routes.add(new RouteList(List.of(new HandlerRoute(null, VOID_HANDLER, Http.Method.GET, - Http.RequestMethod.create("FOO"))))); - routes.add(new HandlerRoute(null, VOID_HANDLER, Http.RequestMethod.create("BAR"))); + Http.Method.create("FOO"))))); + routes.add(new HandlerRoute(null, VOID_HANDLER, Http.Method.create("BAR"))); RouteList r = new RouteList(routes); // assertion assertThat(r.accepts(Http.Method.POST), is(true)); assertThat(r.accepts(Http.Method.PUT), is(true)); assertThat(r.accepts(Http.Method.DELETE), is(true)); assertThat(r.accepts(Http.Method.GET), is(true)); - assertThat(r.accepts(Http.RequestMethod.create("FOO")), is(true)); - assertThat(r.accepts(Http.RequestMethod.create("BAR")), is(true)); + assertThat(r.accepts(Http.Method.create("FOO")), is(true)); + assertThat(r.accepts(Http.Method.create("BAR")), is(true)); assertThat(r.accepts(Http.Method.OPTIONS), is(false)); assertThat(r.acceptedMethods().size(), is(6)); } @@ -82,18 +82,18 @@ public void testAcceptMethodAny() throws Exception { routes.add(new RouteList(List.of(new HandlerRoute(null, VOID_HANDLER, Http.Method.GET, - Http.RequestMethod.create("FOO")), + Http.Method.create("FOO")), new HandlerRoute(null, VOID_HANDLER)))); - routes.add(new HandlerRoute(null, VOID_HANDLER, Http.RequestMethod.create("BAR"))); + routes.add(new HandlerRoute(null, VOID_HANDLER, Http.Method.create("BAR"))); RouteList r = new RouteList(routes); // assertion assertThat(r.accepts(Http.Method.POST), is(true)); assertThat(r.accepts(Http.Method.PUT), is(true)); assertThat(r.accepts(Http.Method.DELETE), is(true)); assertThat(r.accepts(Http.Method.GET), is(true)); - assertThat(r.accepts(Http.RequestMethod.create("FOO")), is(true)); - assertThat(r.accepts(Http.RequestMethod.create("BAR")), is(true)); - assertThat(r.accepts(Http.RequestMethod.create("BAZ")), is(true)); + assertThat(r.accepts(Http.Method.create("FOO")), is(true)); + assertThat(r.accepts(Http.Method.create("BAR")), is(true)); + assertThat(r.accepts(Http.Method.create("BAZ")), is(true)); assertThat(r.accepts(Http.Method.OPTIONS), is(true)); assertThat(r.acceptedMethods().size(), is(0)); } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/ServerConfigurationTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/ServerConfigurationTest.java index 18c32be506b..6672eda5fdb 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/ServerConfigurationTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/ServerConfigurationTest.java @@ -17,18 +17,19 @@ package io.helidon.webserver; import java.net.InetAddress; +import java.util.Optional; import io.helidon.config.Config; import io.helidon.config.ConfigSources; import org.junit.jupiter.api.Test; +import static io.helidon.common.testing.OptionalMatcher.optionalPresent; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; -import static org.hamcrest.Matchers.hasSize; /** * Tests {@link ServerConfiguration.Builder}. @@ -90,8 +91,6 @@ public void fromConfig() throws Exception { assertThat(sc.receiveBufferSize(), is(30)); assertThat(sc.timeoutMillis(), is(40)); assertThat(sc.bindAddress(), is(InetAddress.getByName("127.0.0.1"))); - assertThat(sc.enabledSslProtocols(), hasSize(0)); - assertThat(sc.ssl(), nullValue()); assertThat(sc.workersCount(), is(50)); @@ -100,16 +99,12 @@ public void fromConfig() throws Exception { assertThat(sc.socket("secure").receiveBufferSize(), is(31)); assertThat(sc.socket("secure").timeoutMillis(), is(41)); assertThat(sc.socket("secure").bindAddress(), is(InetAddress.getByName("127.0.0.2"))); - assertThat(sc.socket("secure").enabledSslProtocols(), hasSize(0)); - assertThat(sc.socket("secure").ssl(), nullValue()); assertThat(sc.socket("other").port(), is(12)); assertThat(sc.socket("other").backlog(), is(22)); assertThat(sc.socket("other").receiveBufferSize(), is(32)); assertThat(sc.socket("other").timeoutMillis(), is(42)); assertThat(sc.socket("other").bindAddress(), is(InetAddress.getByName("127.0.0.3"))); - assertThat(sc.socket("other").enabledSslProtocols(), hasSize(0)); - assertThat(sc.socket("other").ssl(), nullValue()); } @Test @@ -118,10 +113,11 @@ public void sslFromConfig() throws Exception { ServerConfiguration sc = config.get("webserver").as(ServerConfiguration::create).get(); assertThat(sc, notNullValue()); assertThat(sc.port(), is(10)); - assertThat(sc.ssl(), notNullValue()); assertThat(sc.socket("secure").port(), is(11)); - assertThat(sc.socket("secure").enabledSslProtocols(), contains("TLSv1.2")); - assertThat(sc.socket("secure").ssl(), notNullValue()); + Optional tls = sc.socket("secure").tls(); + assertThat(tls, optionalPresent()); + WebServerTls ssl = tls.get(); + assertThat(ssl.enabledTlsProtocols(), contains("TLSv1.2")); } } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/ServerRequestReaderTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/ServerRequestReaderTest.java deleted file mode 100644 index 031c5cb4c7a..00000000000 --- a/webserver/webserver/src/test/java/io/helidon/webserver/ServerRequestReaderTest.java +++ /dev/null @@ -1,72 +0,0 @@ -/* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.webserver; - -import java.util.concurrent.CompletionStage; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; - -import io.helidon.common.http.Reader; -import io.helidon.common.reactive.Multi; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.instanceOf; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.fail; - -/** - * The ServerRequestReaderTest. - */ -public class ServerRequestReaderTest { - - static class A {} - - static class B extends A {} - - static class C extends B {} - - @Test - public void test1() throws Exception { - Reader reader = (publisher, clazz) -> Multi.create(publisher) - .collectList() - .toStage() - .thenApply(byteBuffers -> new B()); - - CompletionStage apply = reader.apply(Multi.empty()); - - CompletionStage a = reader.apply(Multi.empty(), A.class); - CompletionStage b = reader.apply(Multi.empty(), B.class); - - // this should not be possible to compile: - //CompletionStage apply2 = reader.apply(Multi.empty(), C.class); - // which is why we have the cast method - CompletionStage c = reader.applyAndCast(Multi.empty(), C.class); - - assertThat(apply.toCompletableFuture().get(10, TimeUnit.SECONDS), instanceOf(B.class)); - assertThat(a.toCompletableFuture().get(10, TimeUnit.SECONDS), instanceOf(A.class)); - assertThat(b.toCompletableFuture().get(10, TimeUnit.SECONDS), instanceOf(B.class)); - - try { - B bOrC = c.toCompletableFuture().get(10, TimeUnit.SECONDS); - fail("Should have thrown an exception.. " + bOrC); - // if there was no explicit cast, only this would fail: Assert.assertThat(actual, instanceOf(C.class)); - } catch (ExecutionException e) { - assertThat(e.getCause(), instanceOf(ClassCastException.class)); - } - } -} diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/StatusTypeTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/StatusTypeTest.java index 6ff22deb16e..d62bc39c805 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/StatusTypeTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/StatusTypeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,13 +30,13 @@ public class StatusTypeTest { @Test public void adHocStatusTypeObjectPropertiesTest() throws Exception { - assertThat(Http.ResponseStatus.create(200) == Http.Status.OK_200, is(true)); - assertThat(Http.ResponseStatus.create(200).hashCode() == Http.Status.OK_200.hashCode(), is(true)); - assertThat(Http.ResponseStatus.create(200).equals(Http.Status.OK_200), is(true)); + assertThat(Http.Status.create(200) == Http.Status.OK_200, is(true)); + assertThat(Http.Status.create(200).hashCode() == Http.Status.OK_200.hashCode(), is(true)); + assertThat(Http.Status.create(200).equals(Http.Status.OK_200), is(true)); - assertThat(Http.ResponseStatus.create(999).equals(Http.ResponseStatus.create(999)), is(true)); + assertThat(Http.Status.create(999).equals(Http.Status.create(999)), is(true)); // TODO if we were able to maintain even the '==' property, it would be awesome (cache the created ones?) - assertThat(Http.ResponseStatus.create(999) != Http.ResponseStatus.create(999), is(true)); - assertThat(Http.ResponseStatus.create(999).hashCode() == Http.ResponseStatus.create(999).hashCode(), is(true)); + assertThat(Http.Status.create(999) != Http.Status.create(999), is(true)); + assertThat(Http.Status.create(999).hashCode() == Http.Status.create(999).hashCode(), is(true)); } } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/TestHttpParsingDefaults.java b/webserver/webserver/src/test/java/io/helidon/webserver/TestHttpParsingDefaults.java index 092bdf4b547..610895dd4e8 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/TestHttpParsingDefaults.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/TestHttpParsingDefaults.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,8 +33,8 @@ import static org.hamcrest.MatcherAssert.assertThat; class TestHttpParsingDefaults { - static final String GOOD_HEADER_NAME = "X_HEADER"; - static final String BAD_HEADER_NAME = "X\tHEADER"; + static final Http.HeaderName GOOD_HEADER_NAME = Http.Header.create("X_HEADER"); + static final Http.HeaderName BAD_HEADER_NAME = Http.Header.create("X\tHEADER"); private static WebServer webServer; private static WebClient client; @@ -103,13 +103,13 @@ void testBadHeaderName() { static void handleRequest(ServerRequest request, ServerResponse response) { RequestHeaders headers = request.headers(); - String value = headers.value(GOOD_HEADER_NAME) + String value = headers.first(GOOD_HEADER_NAME) .or(() -> headers.value(BAD_HEADER_NAME)) .orElse("any"); response.send(value); } - static void testHeaderName(String headerName, boolean success) { + static void testHeaderName(Http.HeaderName headerName, boolean success) { String value = "some random value"; WebClientResponse response = client.get() @@ -123,14 +123,14 @@ static void testHeaderName(String headerName, boolean success) { try { if (success) { - assertThat("Header '" + headerName + "' should have passed", + assertThat("Header '" + headerName.defaultCase() + "' should have passed", response.status(), is(Http.Status.OK_200)); assertThat("This request should return content of the provided header", response.content().as(String.class).await(10, TimeUnit.SECONDS), is(value)); } else { - assertThat("Header '" + headerName + "' should have failed", + assertThat("Header '" + headerName.defaultCase() + "' should have failed", response.status(), is(Http.Status.BAD_REQUEST_400)); } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/TestNettyRejectRequest.java b/webserver/webserver/src/test/java/io/helidon/webserver/TestNettyRejectRequest.java index 4d21b2298bb..51cb61cbab9 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/TestNettyRejectRequest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/TestNettyRejectRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -69,7 +69,7 @@ public void testBadHeader() throws Exception { server); Map headers = SocketHttpClient.headersFromResponse(response); - Http.ResponseStatus status = SocketHttpClient.statusFromResponse(response); + Http.Status status = SocketHttpClient.statusFromResponse(response); String entity = SocketHttpClient.entityFromResponse(response, false); assertThat(headers, hasKey(equalToIgnoringCase("content-length"))); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/TransferEncodingTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/TransferEncodingTest.java index a8bb178f162..96fb9c674cf 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/TransferEncodingTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/TransferEncodingTest.java @@ -34,6 +34,7 @@ import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.startsWith; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.equalToIgnoringCase; import static org.hamcrest.collection.IsMapContaining.hasEntry; /** @@ -61,12 +62,12 @@ private static void startServer(int port) throws Exception { .routing(r -> r .get("/length", (req, res) -> { String payload = "It works!"; - res.headers().add("content-length", String.valueOf(payload.length())); + res.headers().contentLength(payload.length()); res.send(payload); }) .get("/chunked", (req, res) -> { String payload = "It works!"; - res.headers().add("transfer-encoding", "chunked"); + res.headers().set(Http.HeaderValues.TRANSFER_ENCODING_CHUNKED); res.send(payload); }) .get("/optimized", (req, res) -> { @@ -77,7 +78,7 @@ private static void startServer(int port) throws Exception { res.send(); }) .get("/emptychunked", (req, res) -> { - res.headers().add("transfer-encoding", "chunked"); + res.headers().set(Http.HeaderValues.TRANSFER_ENCODING_CHUNKED); res.send(); }) ) @@ -123,7 +124,7 @@ public void testContentLength() throws Exception { String s = SocketHttpClient.sendAndReceive("/length", Http.Method.GET, null, webServer); assertThat(cutPayloadAndCheckHeadersFormat(s), is("It works!")); Map headers = cutHeaders(s); - assertThat(headers, hasEntry("content-length", "9")); + assertThat(headers, hasEntry(equalToIgnoringCase("content-length"), is("9"))); } /** diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/utils/SocketHttpClient.java b/webserver/webserver/src/test/java/io/helidon/webserver/utils/SocketHttpClient.java index 3f33b1d6103..bc1b3b9f4f9 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/utils/SocketHttpClient.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/utils/SocketHttpClient.java @@ -94,7 +94,7 @@ public static String sendAndReceive(Http.Method method, String payload, WebServe * @return the exact string returned by webserver (including {@code HTTP/1.1 200 OK} line for instance) * @throws Exception in case of an error */ - public static String sendAndReceive(String path, Http.RequestMethod method, String payload, WebServer webServer) + public static String sendAndReceive(String path, Http.Method method, String payload, WebServer webServer) throws Exception { return sendAndReceive(path, method, payload, Collections.emptyList(), webServer); } @@ -112,7 +112,7 @@ public static String sendAndReceive(String path, Http.RequestMethod method, Stri * @throws Exception in case of an error */ public static String sendAndReceive(String path, - Http.RequestMethod method, + Http.Method method, String payload, Iterable headers, WebServer webServer) throws Exception { @@ -212,7 +212,7 @@ public static Map headersFromResponse(String response) { * @param response full HTTP response * @return status */ - public static Http.ResponseStatus statusFromResponse(String response) { + public static Http.Status statusFromResponse(String response) { // response should start with HTTP/1.1 000 reasonPhrase\n int eol = response.indexOf('\n'); assertThat("There must be at least a line end after first line: " + response, eol > -1); @@ -225,7 +225,7 @@ public static Http.ResponseStatus statusFromResponse(String response) { int statusCode = Integer.parseInt(matcher.group(1)); String phrase = matcher.group(2); - return Http.ResponseStatus.create(statusCode, phrase); + return Http.Status.create(statusCode, phrase); } /** @@ -298,7 +298,7 @@ public String receive() throws IOException { * @param method the http method * @throws IOException in case of an IO error */ - public void request(Http.RequestMethod method) throws IOException { + public void request(Http.Method method) throws IOException { request(method, null); } @@ -310,7 +310,7 @@ public void request(Http.RequestMethod method) throws IOException { * otherwise it's not a valid payload) * @throws IOException in case of an IO error */ - public void request(Http.RequestMethod method, String payload) throws IOException { + public void request(Http.Method method, String payload) throws IOException { request(method, "/", payload); } @@ -323,7 +323,7 @@ public void request(Http.RequestMethod method, String payload) throws IOExceptio * otherwise it's not a valid payload) * @throws IOException in case of an IO error */ - public void request(Http.RequestMethod method, String path, String payload) throws IOException { + public void request(Http.Method method, String path, String payload) throws IOException { request(method, path, payload, List.of("Content-Type: application/x-www-form-urlencoded")); } @@ -337,7 +337,7 @@ public void request(Http.RequestMethod method, String path, String payload) thro * @param headers the headers (e.g., {@code Content-Type: application/json}) * @throws IOException in case of an IO error */ - public void request(Http.RequestMethod method, String path, String payload, Iterable headers) throws IOException { + public void request(Http.Method method, String path, String payload, Iterable headers) throws IOException { request(method.name(), path, "HTTP/1.1", "127.0.0.1", headers, payload); } diff --git a/webserver/webserver/src/test/resources/logging-test.properties b/webserver/webserver/src/test/resources/logging-test.properties index e9529a432ea..aa03176d59c 100644 --- a/webserver/webserver/src/test/resources/logging-test.properties +++ b/webserver/webserver/src/test/resources/logging-test.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, 2020 Oracle and/or its affiliates. +# Copyright (c) 2017, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ #All attributes details -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details diff --git a/webserver/websocket/src/test/resources/logging.properties b/webserver/websocket/src/test/resources/logging.properties index 3d159673022..98bdbc37001 100644 --- a/webserver/websocket/src/test/resources/logging.properties +++ b/webserver/websocket/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2022 Oracle and/or its affiliates. +# Copyright (c) 2017, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ #All attributes details -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler -io.helidon.common.HelidonConsoleHandler.level=ALL +io.helidon.logging.jul.HelidonConsoleHandler.level=ALL java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details From 2e66bfd132907d0c8ffa1cd46daac23a6b6beffe Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:50:08 +0200 Subject: [PATCH 17/54] WebClient refactored to new common (2nd part). --- webclient/jaxrs/pom.xml | 14 +++++++++ .../webclient/metrics/WebClientCounter.java | 4 +-- .../webclient/metrics/WebClientMeter.java | 4 +-- .../webclient/metrics/WebClientMetric.java | 10 +++---- .../webclient/metrics/WebClientTimer.java | 4 +-- .../webclient/tracing/WebClientTracing.java | 1 + .../java/io/helidon/webclient/WebClient.java | 14 +++++++++ .../webclient/WebClientRequestBuilder.java | 29 ++++++++++++++++++- .../WebClientRequestBuilderImpl.java | 2 +- .../webclient/WebClientRequestHeaders.java | 5 ---- .../WebClientRequestHeadersImpl.java | 4 ++- .../WebClientResponseHeadersImpl.java | 8 ++--- .../webclient/WebClientServiceRequest.java | 4 +-- .../WebClientServiceRequestImpl.java | 4 +-- 14 files changed, 79 insertions(+), 28 deletions(-) diff --git a/webclient/jaxrs/pom.xml b/webclient/jaxrs/pom.xml index 77f04613bae..3730cf02889 100644 --- a/webclient/jaxrs/pom.xml +++ b/webclient/jaxrs/pom.xml @@ -50,4 +50,18 @@ helidon-common-configurable + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + diff --git a/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientCounter.java b/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientCounter.java index 63122cf3fd7..3b3316dba39 100644 --- a/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientCounter.java +++ b/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientCounter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ MetricType metricType() { @Override public Single request(WebClientServiceRequest request) { - Http.RequestMethod method = request.method(); + Http.Method method = request.method(); request.whenResponseReceived() .thenAccept(response -> { diff --git a/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientMeter.java b/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientMeter.java index 4adc5fc8599..e542328abd4 100644 --- a/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientMeter.java +++ b/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientMeter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -39,7 +39,7 @@ MetricType metricType() { @Override public Single request(WebClientServiceRequest request) { - Http.RequestMethod method = request.method(); + Http.Method method = request.method(); request.whenResponseReceived() .thenAccept(response -> { if (shouldContinueOnError(method, response.status().code())) { diff --git a/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientMetric.java b/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientMetric.java index 78bc05c77e3..06760dc7784 100644 --- a/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientMetric.java +++ b/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientMetric.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,15 +84,15 @@ boolean measureErrors() { return errors; } - boolean shouldContinueOnSuccess(Http.RequestMethod method, int status) { + boolean shouldContinueOnSuccess(Http.Method method, int status) { return handlesMethod(method) && measureSuccess() && status < ERROR_STATUS_CODE; } - boolean shouldContinueOnError(Http.RequestMethod method) { + boolean shouldContinueOnError(Http.Method method) { return handlesMethod(method) && measureErrors(); } - boolean shouldContinueOnError(Http.RequestMethod method, int status) { + boolean shouldContinueOnError(Http.Method method, int status) { if (status >= ERROR_STATUS_CODE) { return shouldContinueOnError(method); } @@ -121,7 +121,7 @@ String createName(WebClientServiceRequest request) { return String.format(nameFormat(), request.method().name(), request.uri().getHost()); } - boolean handlesMethod(Http.RequestMethod method) { + boolean handlesMethod(Http.Method method) { return methods().isEmpty() || methods().contains(method.name()); } diff --git a/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientTimer.java b/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientTimer.java index dc78a46bf07..e8c60097638 100644 --- a/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientTimer.java +++ b/webclient/metrics/src/main/java/io/helidon/webclient/metrics/WebClientTimer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -42,7 +42,7 @@ MetricType metricType() { @Override public Single request(WebClientServiceRequest request) { long start = System.nanoTime(); - Http.RequestMethod method = request.method(); + Http.Method method = request.method(); request.whenResponseReceived() .thenAccept(response -> { diff --git a/webclient/tracing/src/main/java/io/helidon/webclient/tracing/WebClientTracing.java b/webclient/tracing/src/main/java/io/helidon/webclient/tracing/WebClientTracing.java index bb2a06ba309..e9fb09654ee 100644 --- a/webclient/tracing/src/main/java/io/helidon/webclient/tracing/WebClientTracing.java +++ b/webclient/tracing/src/main/java/io/helidon/webclient/tracing/WebClientTracing.java @@ -19,6 +19,7 @@ import java.util.Map; import java.util.Optional; +import io.helidon.common.http.Http; import io.helidon.common.LazyValue; import io.helidon.common.reactive.Single; import io.helidon.tracing.HeaderConsumer; diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClient.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClient.java index 8bcc75816ba..719189bca51 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClient.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClient.java @@ -315,12 +315,26 @@ public Builder addCookie(String name, String value) { * @param header header name * @param value header values * @return updated builder instance + * @deprecated use {@link #addHeader(io.helidon.common.http.Http.HeaderName, String...)} instead */ + @Deprecated(forRemoval = true) public Builder addHeader(String header, String... value) { configuration.defaultHeader(Http.HeaderValue.create(Http.Header.create(header), value)); return this; } + /** + * Add a default header (such as accept). + * + * @param header header name + * @param value header values + * @return updated builder instance + */ + public Builder addHeader(Http.HeaderName header, String... value) { + configuration.defaultHeader(Http.HeaderValue.create(header, value)); + return this; + } + /** * Sets base uri for each request. * diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilder.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilder.java index f5e85f8434a..cdd293d78d3 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilder.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilder.java @@ -30,6 +30,7 @@ import io.helidon.common.http.Headers; import io.helidon.common.http.Http; import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaType; import io.helidon.common.reactive.Single; import io.helidon.common.uri.UriPath; import io.helidon.common.uri.UriQuery; @@ -321,13 +322,39 @@ default WebClientRequestBuilder addHeaders(Headers parameters){ WebClientRequestBuilder contentType(HttpMediaType contentType); /** - * Media types which are accepted in the response. + * Content type of the request. + * + * @param contentType content type + * @return updated builder instance + */ + default WebClientRequestBuilder contentType(MediaType contentType) { + return contentType(HttpMediaType.create(contentType)); + } + + /** + * Media types which are accepted in the response, support for quality factor and additional parameters. * * @param mediaTypes media types * @return updated builder instance */ WebClientRequestBuilder accept(HttpMediaType... mediaTypes); + /** + * Media types which are accepted in the response. + * + * @param mediaTypes media types + * @return updated builder instance + */ + default WebClientRequestBuilder accept(MediaType... mediaTypes) { + HttpMediaType[] httpMediaTypes = new HttpMediaType[mediaTypes.length]; + for (int i = 0; i < httpMediaTypes.length; i++) { + httpMediaTypes[i] = HttpMediaType.create(mediaTypes[i]); + + } + accept(httpMediaTypes); + return this; + } + /** * Whether connection should be kept alive after request. * diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilderImpl.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilderImpl.java index 3225f6dd003..4385a518f45 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilderImpl.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilderImpl.java @@ -473,7 +473,7 @@ URI uri() { return finalUri; } - UriQuery queryParams() { + UriQueryWriteable queryParams() { return queryParams; } diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeaders.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeaders.java index c12089b8611..4c7e11caf3a 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeaders.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeaders.java @@ -167,11 +167,6 @@ default WebClientRequestHeaders unsetHeader(String name) { */ Optional ifRangeString(); - /** - * Clears all currently set headers. - */ - void clear(); - /** * Add each header, replacing values if header already exists. * diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeadersImpl.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeadersImpl.java index 6aad324dd93..722d858fa05 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeadersImpl.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestHeadersImpl.java @@ -183,8 +183,9 @@ public Optional ifRangeString() { } @Override - public void clear() { + public WebClientRequestHeaders clear() { this.headers.clear(); + return this; } @Override @@ -282,6 +283,7 @@ public WebClientRequestHeaders addAll(Headers headers) { return this; } + private Optional parseToDate(Http.HeaderName header) { return first(header).map(Http.DateTime::parse); } diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseHeadersImpl.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseHeadersImpl.java index 2f4f6e851dc..ac556c5b527 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseHeadersImpl.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientResponseHeadersImpl.java @@ -128,11 +128,9 @@ public Iterator iterator() { return headers.iterator(); } - private Optional first(Http.HeaderName headerName) { - if (headers.contains(headerName)) { - return Optional.of(headers.get(headerName).value()); - } - return Optional.empty(); + @Override + public List acceptedTypes() { + return List.of(); } private String unquoteETag(String etag) { diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequest.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequest.java index e40781568ff..3ee73aebc76 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequest.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequest.java @@ -22,7 +22,7 @@ import io.helidon.common.http.Http; import io.helidon.common.reactive.Single; import io.helidon.common.uri.UriPath; -import io.helidon.common.uri.UriQuery; +import io.helidon.common.uri.UriQueryWriteable; import io.helidon.webclient.spi.WebClientService; /** @@ -68,7 +68,7 @@ public interface WebClientServiceRequest { * * @return an parameters representing query parameters */ - UriQuery queryParams(); + UriQueryWriteable queryParams(); /** * Returns a path which was accepted by matcher in actual routing. It is path without a context root diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequestImpl.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequestImpl.java index 9e325342f56..aac63280bf4 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequestImpl.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientServiceRequestImpl.java @@ -23,7 +23,7 @@ import io.helidon.common.http.Http; import io.helidon.common.reactive.Single; import io.helidon.common.uri.UriPath; -import io.helidon.common.uri.UriQuery; +import io.helidon.common.uri.UriQueryWriteable; /** * Implementation of the {@link WebClientServiceRequest} interface. @@ -146,7 +146,7 @@ public String query() { } @Override - public UriQuery queryParams() { + public UriQueryWriteable queryParams() { return requestBuilder.queryParams(); } From 5b3632835a78bafa07e6b675968511f0bb817a4a Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:50:27 +0200 Subject: [PATCH 18/54] DbClient refactored to new common. --- .../common/DbClientMapperProvider.java | 9 ++++----- .../dbclient/common/mapper/MapperTest.java | 20 +++++++++---------- .../java/io/helidon/dbclient/DbClient.java | 5 +++++ dbclient/jdbc/pom.xml | 14 +++++++++++++ .../dbclient/jdbc/JdbcStatementQuery.java | 7 ++++--- .../dbclient/mongodb/MongoDbColumn.java | 6 +++--- 6 files changed, 40 insertions(+), 21 deletions(-) diff --git a/dbclient/common/src/main/java/io/helidon/dbclient/common/DbClientMapperProvider.java b/dbclient/common/src/main/java/io/helidon/dbclient/common/DbClientMapperProvider.java index 9387e445acd..9ae6f1050c1 100644 --- a/dbclient/common/src/main/java/io/helidon/dbclient/common/DbClientMapperProvider.java +++ b/dbclient/common/src/main/java/io/helidon/dbclient/common/DbClientMapperProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,6 @@ import java.util.GregorianCalendar; import java.util.HashMap; import java.util.Map; -import java.util.Optional; import io.helidon.common.mapper.Mapper; import io.helidon.common.mapper.spi.MapperProvider; @@ -108,13 +107,13 @@ public class DbClientMapperProvider implements MapperProvider { } @Override - public Optional> mapper(Class sourceClass, Class targetClass) { + public ProviderResponse mapper(Class sourceClass, Class targetClass, String qualifier) { Map, Mapper> targetMap = MAPPERS.get(sourceClass); if (targetMap == null) { - return Optional.empty(); + return ProviderResponse.unsupported(); } Mapper mapper = targetMap.get(targetClass); - return mapper == null ? Optional.empty() : Optional.of(mapper); + return mapper == null ? ProviderResponse.unsupported() : new ProviderResponse(Support.SUPPORTED, mapper); } /** diff --git a/dbclient/common/src/test/java/io/helidon/dbclient/common/mapper/MapperTest.java b/dbclient/common/src/test/java/io/helidon/dbclient/common/mapper/MapperTest.java index 0e28e4814ba..ee4047dd63e 100644 --- a/dbclient/common/src/test/java/io/helidon/dbclient/common/mapper/MapperTest.java +++ b/dbclient/common/src/test/java/io/helidon/dbclient/common/mapper/MapperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -76,7 +76,7 @@ private static OffsetDateTime currentTime() { public void testSqlDateToLocalDate() { OffsetDateTime dt = currentDate(); java.sql.Date source = new java.sql.Date(dt.toInstant().toEpochMilli()); - LocalDate target = mm.map(source, java.sql.Date.class, LocalDate.class); + LocalDate target = mm.map(source, java.sql.Date.class, LocalDate.class, "dbclient"); assertThat(target.toEpochSecond(LocalTime.MIN, dt.getOffset()), is(source.getTime()/1000)); } @@ -84,7 +84,7 @@ public void testSqlDateToLocalDate() { public void testSqlDateToUtilDate() { OffsetDateTime dt = OffsetDateTime.now(); java.sql.Date source = new java.sql.Date(dt.toInstant().toEpochMilli()); - Date target = mm.map(source, java.sql.Date.class, Date.class); + Date target = mm.map(source, java.sql.Date.class, Date.class, "dbclient"); assertThat(target.getTime(), is(source.getTime())); } @@ -92,7 +92,7 @@ public void testSqlDateToUtilDate() { public void testSqlTimeToLocalTime() { OffsetDateTime dt = currentTime(); java.sql.Time source = new java.sql.Time(dt.toInstant().toEpochMilli()); - LocalTime target = mm.map(source, java.sql.Time.class, LocalTime.class); + LocalTime target = mm.map(source, java.sql.Time.class, LocalTime.class, "dbclient"); assertThat(target, is(source.toLocalTime())); } @@ -101,21 +101,21 @@ public void testSqlTimeToLocalTime() { public void testSqlTimeToUtilDate() { OffsetDateTime dt = OffsetDateTime.now(); java.sql.Time source = new java.sql.Time(dt.toInstant().toEpochMilli()); - Date target = mm.map(source, java.sql.Time.class, Date.class); + Date target = mm.map(source, java.sql.Time.class, Date.class, "dbclient"); assertThat(target.getTime(), is(source.getTime())); } @Test public void testSqlTimestampToGregorianCalendar() { Timestamp source = new Timestamp(System.currentTimeMillis()); - GregorianCalendar target = mm.map(source, Timestamp.class, GregorianCalendar.class); + GregorianCalendar target = mm.map(source, Timestamp.class, GregorianCalendar.class, "dbclient"); assertThat(target.getTimeInMillis(), is(source.getTime())); } @Test public void testSqlTimestampToCalendar() { Timestamp source = new Timestamp(System.currentTimeMillis()); - Calendar target = mm.map(source, Timestamp.class, Calendar.class); + Calendar target = mm.map(source, Timestamp.class, Calendar.class, "dbclient"); assertThat(target.getTimeInMillis(), is(source.getTime())); } @@ -124,21 +124,21 @@ public void testSqlTimestampToLocalDateTime() { // Need to know time zone too. OffsetDateTime dt = OffsetDateTime.now(); Timestamp source = new Timestamp(dt.toInstant().toEpochMilli()); - LocalDateTime target = mm.map(source, Timestamp.class, LocalDateTime.class); + LocalDateTime target = mm.map(source, Timestamp.class, LocalDateTime.class, "dbclient"); assertThat(target.atOffset(dt.getOffset()).toInstant().toEpochMilli(), is(source.getTime())); } @Test public void testSqlTimestampToUtilDate() { Timestamp source = new Timestamp(System.currentTimeMillis()); - Date target = mm.map(source, Timestamp.class, Date.class); + Date target = mm.map(source, Timestamp.class, Date.class, "dbclient"); assertThat(target.getTime(), is(source.getTime())); } @Test public void testSqlTimestampToZonedDateTime() { Timestamp source = new Timestamp(System.currentTimeMillis()); - ZonedDateTime target = mm.map(source, Timestamp.class, ZonedDateTime.class); + ZonedDateTime target = mm.map(source, Timestamp.class, ZonedDateTime.class, "dbclient"); assertThat(target.toInstant().toEpochMilli(), is(source.getTime())); } diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClient.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClient.java index 43256394121..f0e32062146 100644 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClient.java +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClient.java @@ -35,6 +35,11 @@ * Helidon database client. */ public interface DbClient { + /** + * Qualifier used for mapping using {@link io.helidon.common.mapper.MapperManager#map(Object, Class, Class, String)}. + */ + String MAPPING_QUALIFIER = "dbclient"; + /** * Execute database statements in transaction. * diff --git a/dbclient/jdbc/pom.xml b/dbclient/jdbc/pom.xml index dc9dcb5ab6b..0a4bed778a8 100644 --- a/dbclient/jdbc/pom.xml +++ b/dbclient/jdbc/pom.xml @@ -66,4 +66,18 @@ test + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + diff --git a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementQuery.java b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementQuery.java index 47b5a847fce..787d072e7a4 100644 --- a/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementQuery.java +++ b/dbclient/jdbc/src/main/java/io/helidon/dbclient/jdbc/JdbcStatementQuery.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -41,6 +41,7 @@ import io.helidon.common.mapper.MapperManager; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; +import io.helidon.dbclient.DbClient; import io.helidon.dbclient.DbClientServiceContext; import io.helidon.dbclient.DbColumn; import io.helidon.dbclient.DbMapperManager; @@ -352,7 +353,7 @@ T map(SRC value, Class type) { Class theClass = (Class) value.getClass(); try { - return mapperManager.map(value, theClass, type); + return mapperManager.map(value, theClass, type, DbClient.MAPPING_QUALIFIER); } catch (MapperException e) { if (type.equals(String.class)) { return (T) String.valueOf(value); @@ -364,7 +365,7 @@ T map(SRC value, Class type) { @SuppressWarnings("unchecked") T map(SRC value, GenericType type) { Class theClass = (Class) value.getClass(); - return mapperManager.map(value, GenericType.create(theClass), type); + return mapperManager.map(value, GenericType.create(theClass), type, DbClient.MAPPING_QUALIFIER); } @Override diff --git a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbColumn.java b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbColumn.java index 2a216126d6f..cca1306780d 100644 --- a/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbColumn.java +++ b/dbclient/mongodb/src/main/java/io/helidon/dbclient/mongodb/MongoDbColumn.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -56,7 +56,7 @@ private T map(S value, Class targetType) { Class sourceType = (Class) javaType(); try { - return mapperManager.map(value, sourceType, targetType); + return mapperManager.map(value, sourceType, targetType, "dbclient-mongo"); } catch (MapperException e) { if (targetType.equals(String.class)) { return (T) String.valueOf(value); @@ -70,7 +70,7 @@ private T map(S value, GenericType targetType) { Class sourceClass = (Class) javaType(); GenericType sourceType = GenericType.create(sourceClass); - return mapperManager.map(value, sourceType, targetType); + return mapperManager.map(value, sourceType, targetType, "dbclient-mongo"); } @Override From a097bf8ed6db0d9d5b1cb649ddb75871a8a9b631 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:51:04 +0200 Subject: [PATCH 19/54] grpc refactored to new common. --- .../src/test/resources/logging.properties | 4 +- .../java/io/helidon/grpc/core/GrpcHelper.java | 46 +++--- .../io/helidon/grpc/core/GrpcHelperTest.java | 138 +++++++++--------- .../src/test/resources/logging.properties | 4 +- .../io/helidon/grpc/metrics/MetricsIT.java | 6 +- .../src/test/resources/logging.properties | 4 +- .../src/test/resources/logging.properties | 4 +- 7 files changed, 103 insertions(+), 103 deletions(-) diff --git a/grpc/client/src/test/resources/logging.properties b/grpc/client/src/test/resources/logging.properties index f9dfd25b9da..f9bcbc4fc4b 100644 --- a/grpc/client/src/test/resources/logging.properties +++ b/grpc/client/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/grpc/core/src/main/java/io/helidon/grpc/core/GrpcHelper.java b/grpc/core/src/main/java/io/helidon/grpc/core/GrpcHelper.java index ec18e07b69f..f6342dda8a1 100644 --- a/grpc/core/src/main/java/io/helidon/grpc/core/GrpcHelper.java +++ b/grpc/core/src/main/java/io/helidon/grpc/core/GrpcHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -73,70 +73,70 @@ public static String extractMethodName(String fullMethodName) { } /** - * Convert a gRPC {@link StatusException} to a {@link Http.ResponseStatus}. + * Convert a gRPC {@link StatusException} to a {@link Http.Status}. * * @param ex the gRPC {@link StatusException} to convert * - * @return the gRPC {@link StatusException} converted to a {@link Http.ResponseStatus} + * @return the gRPC {@link StatusException} converted to a {@link Http.Status} */ - public static Http.ResponseStatus toHttpResponseStatus(StatusException ex) { + public static Http.Status toHttpResponseStatus(StatusException ex) { return toHttpResponseStatus(ex.getStatus()); } /** - * Convert a gRPC {@link StatusRuntimeException} to a {@link Http.ResponseStatus}. + * Convert a gRPC {@link StatusRuntimeException} to a {@link Http.Status}. * * @param ex the gRPC {@link StatusRuntimeException} to convert * - * @return the gRPC {@link StatusRuntimeException} converted to a {@link Http.ResponseStatus} + * @return the gRPC {@link StatusRuntimeException} converted to a {@link Http.Status} */ - public static Http.ResponseStatus toHttpResponseStatus(StatusRuntimeException ex) { + public static Http.Status toHttpResponseStatus(StatusRuntimeException ex) { return toHttpResponseStatus(ex.getStatus()); } /** - * Convert a gRPC {@link Status} to a {@link Http.ResponseStatus}. + * Convert a gRPC {@link Status} to a {@link Http.Status}. * * @param status the gRPC {@link Status} to convert * - * @return the gRPC {@link Status} converted to a {@link Http.ResponseStatus} + * @return the gRPC {@link Status} converted to a {@link Http.Status} */ - public static Http.ResponseStatus toHttpResponseStatus(Status status) { - Http.ResponseStatus httpStatus; + public static Http.Status toHttpResponseStatus(Status status) { + Http.Status httpStatus; switch (status.getCode()) { case OK: - httpStatus = Http.ResponseStatus.create(200, status.getDescription()); + httpStatus = Http.Status.create(200, status.getDescription()); break; case INVALID_ARGUMENT: - httpStatus = Http.ResponseStatus.create(400, status.getDescription()); + httpStatus = Http.Status.create(400, status.getDescription()); break; case DEADLINE_EXCEEDED: - httpStatus = Http.ResponseStatus.create(408, status.getDescription()); + httpStatus = Http.Status.create(408, status.getDescription()); break; case NOT_FOUND: - httpStatus = Http.ResponseStatus.create(404, status.getDescription()); + httpStatus = Http.Status.create(404, status.getDescription()); break; case ALREADY_EXISTS: - httpStatus = Http.ResponseStatus.create(412, status.getDescription()); + httpStatus = Http.Status.create(412, status.getDescription()); break; case PERMISSION_DENIED: - httpStatus = Http.ResponseStatus.create(403, status.getDescription()); + httpStatus = Http.Status.create(403, status.getDescription()); break; case FAILED_PRECONDITION: - httpStatus = Http.ResponseStatus.create(412, status.getDescription()); + httpStatus = Http.Status.create(412, status.getDescription()); break; case OUT_OF_RANGE: - httpStatus = Http.ResponseStatus.create(400, status.getDescription()); + httpStatus = Http.Status.create(400, status.getDescription()); break; case UNIMPLEMENTED: - httpStatus = Http.ResponseStatus.create(501, status.getDescription()); + httpStatus = Http.Status.create(501, status.getDescription()); break; case UNAVAILABLE: - httpStatus = Http.ResponseStatus.create(503, status.getDescription()); + httpStatus = Http.Status.create(503, status.getDescription()); break; case UNAUTHENTICATED: - httpStatus = Http.ResponseStatus.create(401, status.getDescription()); + httpStatus = Http.Status.create(401, status.getDescription()); break; case ABORTED: case CANCELLED: @@ -145,7 +145,7 @@ public static Http.ResponseStatus toHttpResponseStatus(Status status) { case RESOURCE_EXHAUSTED: case UNKNOWN: default: - httpStatus = Http.ResponseStatus.create(500, status.getDescription()); + httpStatus = Http.Status.create(500, status.getDescription()); } return httpStatus; diff --git a/grpc/core/src/test/java/io/helidon/grpc/core/GrpcHelperTest.java b/grpc/core/src/test/java/io/helidon/grpc/core/GrpcHelperTest.java index 4e1a8cf5459..8efbe16a9ae 100644 --- a/grpc/core/src/test/java/io/helidon/grpc/core/GrpcHelperTest.java +++ b/grpc/core/src/test/java/io/helidon/grpc/core/GrpcHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -55,7 +55,7 @@ public void shouldExtractNamePrefix() { @Test public void shouldConvertAbortedStatusException() { StatusException exception = Status.ABORTED.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Internal Server Error")); @@ -64,7 +64,7 @@ public void shouldConvertAbortedStatusException() { @Test public void shouldConvertAbortedStatusExceptionWithDescription() { StatusException exception = Status.ABORTED.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -73,7 +73,7 @@ public void shouldConvertAbortedStatusExceptionWithDescription() { @Test public void shouldConvertAlreadyExistsStatusException() { StatusException exception = Status.ALREADY_EXISTS.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(412)); assertThat(status.reasonPhrase(), is("Precondition Failed")); @@ -82,7 +82,7 @@ public void shouldConvertAlreadyExistsStatusException() { @Test public void shouldConvertAlreadyExistsStatusExceptionWithDescription() { StatusException exception = Status.ALREADY_EXISTS.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(412)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -91,7 +91,7 @@ public void shouldConvertAlreadyExistsStatusExceptionWithDescription() { @Test public void shouldConvertOkStatusException() { StatusException exception = Status.OK.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(200)); assertThat(status.reasonPhrase(), is("OK")); @@ -100,7 +100,7 @@ public void shouldConvertOkStatusException() { @Test public void shouldConvertOkStatusExceptionWithDescription() { StatusException exception = Status.OK.withDescription("Good!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(200)); assertThat(status.reasonPhrase(), is("Good!")); @@ -109,7 +109,7 @@ public void shouldConvertOkStatusExceptionWithDescription() { @Test public void shouldConvertInvalidArgumentStatusException() { StatusException exception = Status.INVALID_ARGUMENT.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(400)); assertThat(status.reasonPhrase(), is("Bad Request")); @@ -118,7 +118,7 @@ public void shouldConvertInvalidArgumentStatusException() { @Test public void shouldConvertInvalidArgumentStatusExceptionWithDescription() { StatusException exception = Status.INVALID_ARGUMENT.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(400)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -127,7 +127,7 @@ public void shouldConvertInvalidArgumentStatusExceptionWithDescription() { @Test public void shouldConvertDeadlineExceededStatusException() { StatusException exception = Status.DEADLINE_EXCEEDED.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(408)); assertThat(status.reasonPhrase(), is("Request Timeout")); @@ -136,7 +136,7 @@ public void shouldConvertDeadlineExceededStatusException() { @Test public void shouldConvertDeadlineExceededStatusExceptionWithDescription() { StatusException exception = Status.DEADLINE_EXCEEDED.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(408)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -145,7 +145,7 @@ public void shouldConvertDeadlineExceededStatusExceptionWithDescription() { @Test public void shouldConvertNotFoundStatusException() { StatusException exception = Status.NOT_FOUND.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(404)); assertThat(status.reasonPhrase(), is("Not Found")); @@ -154,7 +154,7 @@ public void shouldConvertNotFoundStatusException() { @Test public void shouldConvertNotFoundStatusExceptionWithDescription() { StatusException exception = Status.NOT_FOUND.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(404)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -163,7 +163,7 @@ public void shouldConvertNotFoundStatusExceptionWithDescription() { @Test public void shouldConvertPermissionDeniedStatusException() { StatusException exception = Status.PERMISSION_DENIED.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(403)); assertThat(status.reasonPhrase(), is("Forbidden")); @@ -172,7 +172,7 @@ public void shouldConvertPermissionDeniedStatusException() { @Test public void shouldConvertPermissionDeniedStatusExceptionWithDescription() { StatusException exception = Status.PERMISSION_DENIED.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(403)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -181,7 +181,7 @@ public void shouldConvertPermissionDeniedStatusExceptionWithDescription() { @Test public void shouldConvertFailedPreconditionStatusException() { StatusException exception = Status.FAILED_PRECONDITION.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(412)); assertThat(status.reasonPhrase(), is("Precondition Failed")); @@ -190,7 +190,7 @@ public void shouldConvertFailedPreconditionStatusException() { @Test public void shouldConvertFailedPreconditionStatusExceptionWithDescription() { StatusException exception = Status.FAILED_PRECONDITION.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(412)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -199,7 +199,7 @@ public void shouldConvertFailedPreconditionStatusExceptionWithDescription() { @Test public void shouldConvertOutOfRangeStatusException() { StatusException exception = Status.OUT_OF_RANGE.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(400)); assertThat(status.reasonPhrase(), is("Bad Request")); @@ -208,7 +208,7 @@ public void shouldConvertOutOfRangeStatusException() { @Test public void shouldConvertOutOfRangeStatusExceptionWithDescription() { StatusException exception = Status.OUT_OF_RANGE.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(400)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -217,7 +217,7 @@ public void shouldConvertOutOfRangeStatusExceptionWithDescription() { @Test public void shouldConvertUnimplementedStatusException() { StatusException exception = Status.UNIMPLEMENTED.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(501)); assertThat(status.reasonPhrase(), is("Not Implemented")); @@ -226,7 +226,7 @@ public void shouldConvertUnimplementedStatusException() { @Test public void shouldConvertUnimplementedStatusExceptionWithDescription() { StatusException exception = Status.UNIMPLEMENTED.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(501)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -235,7 +235,7 @@ public void shouldConvertUnimplementedStatusExceptionWithDescription() { @Test public void shouldConvertUnavailableStatusException() { StatusException exception = Status.UNAVAILABLE.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(503)); assertThat(status.reasonPhrase(), is("Service Unavailable")); @@ -244,7 +244,7 @@ public void shouldConvertUnavailableStatusException() { @Test public void shouldConvertUnavailableStatusExceptionWithDescription() { StatusException exception = Status.UNAVAILABLE.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(503)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -253,7 +253,7 @@ public void shouldConvertUnavailableStatusExceptionWithDescription() { @Test public void shouldConvertUnauthenticatedStatusException() { StatusException exception = Status.UNAUTHENTICATED.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(401)); assertThat(status.reasonPhrase(), is("Unauthorized")); @@ -262,7 +262,7 @@ public void shouldConvertUnauthenticatedStatusException() { @Test public void shouldConvertUnauthenticatedStatusExceptionWithDescription() { StatusException exception = Status.UNAUTHENTICATED.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(401)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -271,7 +271,7 @@ public void shouldConvertUnauthenticatedStatusExceptionWithDescription() { @Test public void shouldConvertCancelledStatusException() { StatusException exception = Status.CANCELLED.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Internal Server Error")); @@ -280,7 +280,7 @@ public void shouldConvertCancelledStatusException() { @Test public void shouldConvertCancelledStatusExceptionWithDescription() { StatusException exception = Status.CANCELLED.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -289,7 +289,7 @@ public void shouldConvertCancelledStatusExceptionWithDescription() { @Test public void shouldConvertDataLossStatusException() { StatusException exception = Status.DATA_LOSS.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Internal Server Error")); @@ -298,7 +298,7 @@ public void shouldConvertDataLossStatusException() { @Test public void shouldConvertDataLossStatusExceptionWithDescription() { StatusException exception = Status.DATA_LOSS.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -307,7 +307,7 @@ public void shouldConvertDataLossStatusExceptionWithDescription() { @Test public void shouldConvertInternalStatusException() { StatusException exception = Status.INTERNAL.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Internal Server Error")); @@ -316,7 +316,7 @@ public void shouldConvertInternalStatusException() { @Test public void shouldConvertInternalStatusExceptionWithDescription() { StatusException exception = Status.INTERNAL.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -325,7 +325,7 @@ public void shouldConvertInternalStatusExceptionWithDescription() { @Test public void shouldConvertResourceExhaustedStatusException() { StatusException exception = Status.RESOURCE_EXHAUSTED.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Internal Server Error")); @@ -334,7 +334,7 @@ public void shouldConvertResourceExhaustedStatusException() { @Test public void shouldConvertResourceExhaustedStatusExceptionWithDescription() { StatusException exception = Status.RESOURCE_EXHAUSTED.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -343,7 +343,7 @@ public void shouldConvertResourceExhaustedStatusExceptionWithDescription() { @Test public void shouldConvertUnknownStatusException() { StatusException exception = Status.UNKNOWN.asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Internal Server Error")); @@ -352,7 +352,7 @@ public void shouldConvertUnknownStatusException() { @Test public void shouldConvertUnknownStatusExceptionWithDescription() { StatusException exception = Status.UNKNOWN.withDescription("Oops!").asException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -361,7 +361,7 @@ public void shouldConvertUnknownStatusExceptionWithDescription() { @Test public void shouldConvertAbortedStatusRuntimeException() { StatusRuntimeException exception = Status.ABORTED.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Internal Server Error")); @@ -370,7 +370,7 @@ public void shouldConvertAbortedStatusRuntimeException() { @Test public void shouldConvertAbortedStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.ABORTED.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -379,7 +379,7 @@ public void shouldConvertAbortedStatusRuntimeExceptionWithDescription() { @Test public void shouldConvertAlreadyExistsStatusRuntimeException() { StatusRuntimeException exception = Status.ALREADY_EXISTS.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(412)); assertThat(status.reasonPhrase(), is("Precondition Failed")); @@ -388,7 +388,7 @@ public void shouldConvertAlreadyExistsStatusRuntimeException() { @Test public void shouldConvertAlreadyExistsStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.ALREADY_EXISTS.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(412)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -397,7 +397,7 @@ public void shouldConvertAlreadyExistsStatusRuntimeExceptionWithDescription() { @Test public void shouldConvertOkStatusRuntimeException() { StatusRuntimeException exception = Status.OK.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(200)); assertThat(status.reasonPhrase(), is("OK")); @@ -406,7 +406,7 @@ public void shouldConvertOkStatusRuntimeException() { @Test public void shouldConvertOkStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.OK.withDescription("Good!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(200)); assertThat(status.reasonPhrase(), is("Good!")); @@ -415,7 +415,7 @@ public void shouldConvertOkStatusRuntimeExceptionWithDescription() { @Test public void shouldConvertInvalidArgumentStatusRuntimeException() { StatusRuntimeException exception = Status.INVALID_ARGUMENT.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(400)); assertThat(status.reasonPhrase(), is("Bad Request")); @@ -424,7 +424,7 @@ public void shouldConvertInvalidArgumentStatusRuntimeException() { @Test public void shouldConvertInvalidArgumentStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.INVALID_ARGUMENT.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(400)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -433,7 +433,7 @@ public void shouldConvertInvalidArgumentStatusRuntimeExceptionWithDescription() @Test public void shouldConvertDeadlineExceededStatusRuntimeException() { StatusRuntimeException exception = Status.DEADLINE_EXCEEDED.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(408)); assertThat(status.reasonPhrase(), is("Request Timeout")); @@ -442,7 +442,7 @@ public void shouldConvertDeadlineExceededStatusRuntimeException() { @Test public void shouldConvertDeadlineExceededStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.DEADLINE_EXCEEDED.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(408)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -451,7 +451,7 @@ public void shouldConvertDeadlineExceededStatusRuntimeExceptionWithDescription() @Test public void shouldConvertNotFoundStatusRuntimeException() { StatusRuntimeException exception = Status.NOT_FOUND.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(404)); assertThat(status.reasonPhrase(), is("Not Found")); @@ -460,7 +460,7 @@ public void shouldConvertNotFoundStatusRuntimeException() { @Test public void shouldConvertNotFoundStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.NOT_FOUND.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(404)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -469,7 +469,7 @@ public void shouldConvertNotFoundStatusRuntimeExceptionWithDescription() { @Test public void shouldConvertPermissionDeniedStatusRuntimeException() { StatusRuntimeException exception = Status.PERMISSION_DENIED.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(403)); assertThat(status.reasonPhrase(), is("Forbidden")); @@ -478,7 +478,7 @@ public void shouldConvertPermissionDeniedStatusRuntimeException() { @Test public void shouldConvertPermissionDeniedStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.PERMISSION_DENIED.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(403)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -487,7 +487,7 @@ public void shouldConvertPermissionDeniedStatusRuntimeExceptionWithDescription() @Test public void shouldConvertFailedPreconditionStatusRuntimeException() { StatusRuntimeException exception = Status.FAILED_PRECONDITION.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(412)); assertThat(status.reasonPhrase(), is("Precondition Failed")); @@ -496,7 +496,7 @@ public void shouldConvertFailedPreconditionStatusRuntimeException() { @Test public void shouldConvertFailedPreconditionStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.FAILED_PRECONDITION.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(412)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -505,7 +505,7 @@ public void shouldConvertFailedPreconditionStatusRuntimeExceptionWithDescription @Test public void shouldConvertOutOfRangeStatusRuntimeException() { StatusRuntimeException exception = Status.OUT_OF_RANGE.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(400)); assertThat(status.reasonPhrase(), is("Bad Request")); @@ -514,7 +514,7 @@ public void shouldConvertOutOfRangeStatusRuntimeException() { @Test public void shouldConvertOutOfRangeStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.OUT_OF_RANGE.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(400)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -523,7 +523,7 @@ public void shouldConvertOutOfRangeStatusRuntimeExceptionWithDescription() { @Test public void shouldConvertUnimplementedStatusRuntimeException() { StatusRuntimeException exception = Status.UNIMPLEMENTED.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(501)); assertThat(status.reasonPhrase(), is("Not Implemented")); @@ -532,7 +532,7 @@ public void shouldConvertUnimplementedStatusRuntimeException() { @Test public void shouldConvertUnimplementedStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.UNIMPLEMENTED.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(501)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -541,7 +541,7 @@ public void shouldConvertUnimplementedStatusRuntimeExceptionWithDescription() { @Test public void shouldConvertUnavailableStatusRuntimeException() { StatusRuntimeException exception = Status.UNAVAILABLE.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(503)); assertThat(status.reasonPhrase(), is("Service Unavailable")); @@ -550,7 +550,7 @@ public void shouldConvertUnavailableStatusRuntimeException() { @Test public void shouldConvertUnavailableStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.UNAVAILABLE.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(503)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -559,7 +559,7 @@ public void shouldConvertUnavailableStatusRuntimeExceptionWithDescription() { @Test public void shouldConvertUnauthenticatedStatusRuntimeException() { StatusRuntimeException exception = Status.UNAUTHENTICATED.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(401)); assertThat(status.reasonPhrase(), is("Unauthorized")); @@ -568,7 +568,7 @@ public void shouldConvertUnauthenticatedStatusRuntimeException() { @Test public void shouldConvertUnauthenticatedStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.UNAUTHENTICATED.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(401)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -577,7 +577,7 @@ public void shouldConvertUnauthenticatedStatusRuntimeExceptionWithDescription() @Test public void shouldConvertCancelledStatusRuntimeException() { StatusRuntimeException exception = Status.CANCELLED.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Internal Server Error")); @@ -586,7 +586,7 @@ public void shouldConvertCancelledStatusRuntimeException() { @Test public void shouldConvertCancelledStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.CANCELLED.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -595,7 +595,7 @@ public void shouldConvertCancelledStatusRuntimeExceptionWithDescription() { @Test public void shouldConvertDataLossStatusRuntimeException() { StatusRuntimeException exception = Status.DATA_LOSS.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Internal Server Error")); @@ -604,7 +604,7 @@ public void shouldConvertDataLossStatusRuntimeException() { @Test public void shouldConvertDataLossStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.DATA_LOSS.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -613,7 +613,7 @@ public void shouldConvertDataLossStatusRuntimeExceptionWithDescription() { @Test public void shouldConvertInternalStatusRuntimeException() { StatusRuntimeException exception = Status.INTERNAL.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Internal Server Error")); @@ -622,7 +622,7 @@ public void shouldConvertInternalStatusRuntimeException() { @Test public void shouldConvertInternalStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.INTERNAL.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -631,7 +631,7 @@ public void shouldConvertInternalStatusRuntimeExceptionWithDescription() { @Test public void shouldConvertResourceExhaustedStatusRuntimeException() { StatusRuntimeException exception = Status.RESOURCE_EXHAUSTED.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Internal Server Error")); @@ -640,7 +640,7 @@ public void shouldConvertResourceExhaustedStatusRuntimeException() { @Test public void shouldConvertResourceExhaustedStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.RESOURCE_EXHAUSTED.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Oops!")); @@ -649,7 +649,7 @@ public void shouldConvertResourceExhaustedStatusRuntimeExceptionWithDescription( @Test public void shouldConvertUnknownStatusRuntimeException() { StatusRuntimeException exception = Status.UNKNOWN.asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Internal Server Error")); @@ -658,7 +658,7 @@ public void shouldConvertUnknownStatusRuntimeException() { @Test public void shouldConvertUnknownStatusRuntimeExceptionWithDescription() { StatusRuntimeException exception = Status.UNKNOWN.withDescription("Oops!").asRuntimeException(); - Http.ResponseStatus status = GrpcHelper.toHttpResponseStatus(exception); + Http.Status status = GrpcHelper.toHttpResponseStatus(exception); assertThat(status.code(), is(500)); assertThat(status.reasonPhrase(), is("Oops!")); diff --git a/grpc/core/src/test/resources/logging.properties b/grpc/core/src/test/resources/logging.properties index f9dfd25b9da..f9bcbc4fc4b 100644 --- a/grpc/core/src/test/resources/logging.properties +++ b/grpc/core/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/grpc/metrics/src/test/java/io/helidon/grpc/metrics/MetricsIT.java b/grpc/metrics/src/test/java/io/helidon/grpc/metrics/MetricsIT.java index b27c6cad053..0b33dc9b1df 100644 --- a/grpc/metrics/src/test/java/io/helidon/grpc/metrics/MetricsIT.java +++ b/grpc/metrics/src/test/java/io/helidon/grpc/metrics/MetricsIT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import java.util.logging.Logger; import io.helidon.common.LogConfig; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.grpc.server.GrpcRouting; import io.helidon.grpc.server.GrpcServer; import io.helidon.grpc.server.GrpcServerConfiguration; @@ -120,7 +120,7 @@ public void shouldPublishMetrics() throws ExecutionException, InterruptedExcepti client.get() .uri("http://localhost:" + webServer.port()) .path("metrics/application") - .accept(MediaType.APPLICATION_JSON) + .accept(HttpMediaType.APPLICATION_JSON) .request(JsonStructure.class) .thenAccept(it -> { JsonValue value = it.getValue("/EchoService.Echo"); diff --git a/grpc/metrics/src/test/resources/logging.properties b/grpc/metrics/src/test/resources/logging.properties index f9dfd25b9da..f9bcbc4fc4b 100644 --- a/grpc/metrics/src/test/resources/logging.properties +++ b/grpc/metrics/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/grpc/server/src/test/resources/logging.properties b/grpc/server/src/test/resources/logging.properties index f9dfd25b9da..f9bcbc4fc4b 100644 --- a/grpc/server/src/test/resources/logging.properties +++ b/grpc/server/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n From adebd63ba11b18d5947e9695f9c63cc9fc39f947 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:51:48 +0200 Subject: [PATCH 20/54] Security refactored to new common. --- .../integration/grpc/OutboundSecurityIT.java | 8 ++-- .../src/test/resources/logging.properties | 4 +- .../webserver/SecurityHandler.java | 38 +++++++++---------- .../integration/webserver/WebSecurity.java | 6 +-- .../WebSecurityBuilderGateDefaultsTest.java | 6 +-- .../webserver/WebSecurityFromConfigTest.java | 6 +-- .../WebSecurityProgrammaticTest.java | 6 +-- .../webserver/WebSecurityQueryParamTest.java | 6 +-- .../mapper/IdcsRoleMapperRxProviderBase.java | 7 ++-- security/providers/oidc-common/pom.xml | 4 ++ .../providers/oidc/common/IdcsSupport.java | 12 +++--- .../providers/oidc/common/OidcConfig.java | 10 ++--- .../src/main/java/module-info.java | 1 + .../src/test/resources/logging.properties | 4 +- .../security/providers/oidc/OidcProvider.java | 14 ++++--- .../security/providers/oidc/OidcSupport.java | 20 ++++++---- .../src/test/resources/logging.properties | 4 +- security/security/pom.xml | 14 +++++++ .../webclient/security/WebClientSecurity.java | 7 ++-- 19 files changed, 100 insertions(+), 77 deletions(-) diff --git a/security/integration/grpc/src/test/java/io/helidon/security/integration/grpc/OutboundSecurityIT.java b/security/integration/grpc/src/test/java/io/helidon/security/integration/grpc/OutboundSecurityIT.java index 90fcf24d59b..abb1b3f7f03 100644 --- a/security/integration/grpc/src/test/java/io/helidon/security/integration/grpc/OutboundSecurityIT.java +++ b/security/integration/grpc/src/test/java/io/helidon/security/integration/grpc/OutboundSecurityIT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -185,7 +185,7 @@ private static void echoWebRequest(ServerRequest req, ServerResponse res) { if (message != null) { res.send(message); } else { - res.status(Http.ResponseStatus.create(401, "missing message query parameter")).send(); + res.status(Http.Status.create(401, "missing message query parameter")).send(); } } @@ -201,7 +201,7 @@ private static void propagateCredentialsWebRequest(ServerRequest req, ServerResp } catch (StatusRuntimeException e) { res.status(GrpcHelper.toHttpResponseStatus(e)).send(); } catch (Throwable thrown) { - res.status(Http.ResponseStatus.create(500, thrown.getMessage())).send(); + res.status(Http.Status.create(500, thrown.getMessage())).send(); } } @@ -220,7 +220,7 @@ private static void overrideCredentialsWebRequest(ServerRequest req, ServerRespo } catch (StatusRuntimeException e) { res.status(GrpcHelper.toHttpResponseStatus(e)).send(); } catch (Throwable thrown) { - res.status(Http.ResponseStatus.create(500, thrown.getMessage())).send(); + res.status(Http.Status.create(500, thrown.getMessage())).send(); } } } diff --git a/security/integration/grpc/src/test/resources/logging.properties b/security/integration/grpc/src/test/resources/logging.properties index c970479a0ae..ed6d1e879f5 100644 --- a/security/integration/grpc/src/test/resources/logging.properties +++ b/security/integration/grpc/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java b/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java index a2973cacaea..8935a3c248a 100644 --- a/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java +++ b/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java @@ -36,7 +36,6 @@ import io.helidon.common.context.Context; import io.helidon.common.context.Contexts; import io.helidon.common.http.Http; -import io.helidon.common.http.HttpRequest; import io.helidon.config.Config; import io.helidon.security.AuditEvent; import io.helidon.security.AuthenticationResponse; @@ -386,21 +385,11 @@ private void processAudit(ServerRequest req, ServerResponse res, SecurityContext if (audited.isEmpty()) { // use defaults - if (req.method() instanceof Http.Method) { - switch ((Http.Method) req.method()) { - case GET: - case HEAD: - // get and head are not audited by default - return; - case OPTIONS: - case POST: - case PUT: - case DELETE: - case TRACE: - default: - //do nothing - we want to audit - } + if (req.method() == Http.Method.GET || req.method() == Http.Method.HEAD) { + // get and head are not audited by default + return; } + //do nothing - we want to audit } //audit @@ -547,16 +536,25 @@ private void atnFinish(ServerResponse res, private void abortRequest(ServerResponse res, SecurityResponse response, int defaultCode, - Map> defaultHeaders) { + Map> defaultHeaders) { int statusCode = ((null == response) ? defaultCode : response.statusCode().orElse(defaultCode)); - Map> responseHeaders = ((null == response) ? defaultHeaders : response.responseHeaders()); + Map> responseHeaders; + if (response == null) { + responseHeaders = defaultHeaders; + } else { + Map> tmpHeaders = new HashMap<>(); + response.responseHeaders() + .forEach((key, value) -> tmpHeaders.put(Http.Header.create(key), value)); + responseHeaders = tmpHeaders; + } + responseHeaders = responseHeaders.isEmpty() ? defaultHeaders : responseHeaders; ResponseHeaders httpHeaders = res.headers(); - for (Map.Entry> entry : responseHeaders.entrySet()) { - httpHeaders.put(entry.getKey(), entry.getValue()); + for (Map.Entry> entry : responseHeaders.entrySet()) { + httpHeaders.set(entry.getKey(), entry.getValue()); } res.status(statusCode); @@ -659,7 +657,7 @@ private CompletionStage processAuthorization(ServerRequest req, } private void auditRoleMissing(SecurityContext context, - HttpRequest.Path path, + ServerRequest.Path path, Optional user, Set rolesSet) { diff --git a/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/WebSecurity.java b/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/WebSecurity.java index 40a65e0d535..824923f21fc 100644 --- a/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/WebSecurity.java +++ b/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/WebSecurity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -374,11 +374,11 @@ private void registerRouting(Routing.Rules routing) { wsConfig.get("paths").asNodeList().ifPresent(configs -> { for (Config pathConfig : configs) { - List methods = pathConfig.get("methods").asNodeList().orElse(List.of()) + List methods = pathConfig.get("methods").asNodeList().orElse(List.of()) .stream() .map(Config::asString) .map(ConfigValue::get) - .map(Http.RequestMethod::create) + .map(Http.Method::create) .collect(Collectors.toList()); String path = pathConfig.get("path") diff --git a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityBuilderGateDefaultsTest.java b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityBuilderGateDefaultsTest.java index 6925f4604d7..d6d7a60b9b4 100644 --- a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityBuilderGateDefaultsTest.java +++ b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityBuilderGateDefaultsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import java.util.concurrent.TimeUnit; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.config.Config; import io.helidon.security.AuditEvent; import io.helidon.security.Security; @@ -97,7 +97,7 @@ public static void initClass() throws InterruptedException { ) .get("/{*}", (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Hello, you are: \n" + securityContext .map(ctx -> ctx.user().orElse(SecurityContext.ANONYMOUS).toString()) .orElse("Security context is null")); diff --git a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityFromConfigTest.java b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityFromConfigTest.java index 99e4491d96c..419541df97e 100644 --- a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityFromConfigTest.java +++ b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityFromConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.config.Config; import io.helidon.security.Security; import io.helidon.security.SecurityContext; @@ -52,7 +52,7 @@ public static void initClass() throws InterruptedException { .register(WebSecurity.create(security, securityConfig)) .get("/{*}", (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Hello, you are: \n" + securityContext .map(ctx -> ctx.user().orElse(SecurityContext.ANONYMOUS).toString()) .orElse("Security context is null")); diff --git a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityProgrammaticTest.java b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityProgrammaticTest.java index 9531cf78233..d2eee812bbe 100644 --- a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityProgrammaticTest.java +++ b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityProgrammaticTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import java.util.regex.Pattern; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.config.Config; import io.helidon.security.Security; import io.helidon.security.SecurityContext; @@ -80,7 +80,7 @@ public static void initClass() throws InterruptedException { ) .get("/{*}", (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Hello, you are: \n" + securityContext .map(ctx -> ctx.user().orElse(SecurityContext.ANONYMOUS).toString()) .orElse("Security context is null")); diff --git a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityQueryParamTest.java b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityQueryParamTest.java index 10a3218c8eb..c0e8f7ca8d8 100644 --- a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityQueryParamTest.java +++ b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityQueryParamTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import java.util.List; import java.util.regex.Pattern; -import io.helidon.common.http.Parameters; +import io.helidon.common.uri.UriQuery; import io.helidon.security.SecurityContext; import io.helidon.security.SecurityEnvironment; import io.helidon.security.util.TokenHandler; @@ -55,7 +55,7 @@ public void testQueryParams() { ServerRequest req = Mockito.mock(ServerRequest.class); - Parameters params = Mockito.mock(Parameters.class); + UriQuery params = Mockito.mock(UriQuery.class); when(params.all("jwt")).thenReturn(List.of("bearer jwt_content")); when(params.all("name")).thenReturn(List.of("name_content")); when(req.queryParams()).thenReturn(params); diff --git a/security/providers/idcs-mapper/src/main/java/io/helidon/security/providers/idcs/mapper/IdcsRoleMapperRxProviderBase.java b/security/providers/idcs-mapper/src/main/java/io/helidon/security/providers/idcs/mapper/IdcsRoleMapperRxProviderBase.java index 04ed3e0bf19..e0a2bc8c128 100644 --- a/security/providers/idcs-mapper/src/main/java/io/helidon/security/providers/idcs/mapper/IdcsRoleMapperRxProviderBase.java +++ b/security/providers/idcs-mapper/src/main/java/io/helidon/security/providers/idcs/mapper/IdcsRoleMapperRxProviderBase.java @@ -29,8 +29,9 @@ import io.helidon.common.context.Context; import io.helidon.common.context.Contexts; -import io.helidon.common.http.FormParams; import io.helidon.common.http.Http; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.parameters.Parameters; import io.helidon.common.reactive.Single; import io.helidon.config.Config; import io.helidon.config.metadata.Configured; @@ -423,7 +424,7 @@ protected Single> getToken(RoleMapTracing tracing) { } private void fromServer(RoleMapTracing tracing, CompletableFuture future) { - FormParams params = FormParams.builder() + Parameters params = Parameters.builder("idcs-form-params") .add("grant_type", "client_credentials") .add("scope", "urn:opc:idm:__myscopes__") .build(); @@ -441,7 +442,7 @@ private void fromServer(RoleMapTracing tracing, CompletableFuture WebClientRequestBuilder request = webClient.post() .uri(tokenEndpointUri) .context(childContext) - .accept(io.helidon.common.http.MediaType.APPLICATION_JSON); + .accept(HttpMediaType.APPLICATION_JSON); postJsonResponse(request, params, diff --git a/security/providers/oidc-common/pom.xml b/security/providers/oidc-common/pom.xml index 70eddfa7d9f..30ffc3fa368 100644 --- a/security/providers/oidc-common/pom.xml +++ b/security/providers/oidc-common/pom.xml @@ -33,6 +33,10 @@ io.helidon.security.providers helidon-security-providers-common + + io.helidon.common + helidon-common-parameters + io.helidon.security diff --git a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/IdcsSupport.java b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/IdcsSupport.java index deb1eded1a9..cce84d27013 100644 --- a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/IdcsSupport.java +++ b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/IdcsSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ import java.time.Duration; import java.util.concurrent.TimeUnit; -import io.helidon.common.http.FormParams; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.parameters.Parameters; import io.helidon.security.SecurityException; import io.helidon.security.jwt.jwk.JwkKeys; import io.helidon.webclient.WebClient; @@ -45,7 +45,7 @@ static JwkKeys signJwk(WebClient appWebClient, URI signJwkUri, Duration clientTimeout) { // need to get token to be able to request this endpoint - FormParams form = FormParams.builder() + Parameters form = Parameters.builder("idcs-form-params") .add("grant_type", "client_credentials") .add("scope", "urn:opc:idm:__myscopes__") .build(); @@ -53,11 +53,11 @@ static JwkKeys signJwk(WebClient appWebClient, try { WebClientResponse response = appWebClient.post() .uri(tokenEndpointUri) - .accept(MediaType.APPLICATION_JSON) + .accept(HttpMediaType.APPLICATION_JSON) .submit(form) .await(clientTimeout.toMillis(), TimeUnit.MILLISECONDS); - if (response.status().family() == Http.ResponseStatus.Family.SUCCESSFUL) { + if (response.status().family() == Http.Status.Family.SUCCESSFUL) { JsonObject json = response.content() .as(JsonObject.class) .await(clientTimeout.toMillis(), TimeUnit.MILLISECONDS); diff --git a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcConfig.java b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcConfig.java index 5f4ab8328f3..7a26675acde 100644 --- a/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcConfig.java +++ b/security/providers/oidc-common/src/main/java/io/helidon/security/providers/oidc/common/OidcConfig.java @@ -27,9 +27,9 @@ import io.helidon.common.Errors; import io.helidon.common.configurable.Resource; -import io.helidon.common.http.FormParams; import io.helidon.common.http.Http; import io.helidon.common.http.SetCookie; +import io.helidon.common.parameters.Parameters; import io.helidon.common.reactive.Single; import io.helidon.config.Config; import io.helidon.config.metadata.Configured; @@ -486,7 +486,7 @@ public static OidcConfig create(Config config) { * This is a helper method to handle possible cases (success, failure with readable entity, failure). * * @param requestBuilder WebClient request builder - * @param toSubmit object to submit (such as {@link io.helidon.common.http.FormParams} + * @param toSubmit object to submit (such as {@link io.helidon.common.parameters.Parameters} * @param jsonProcessor processor of successful JSON response * @param errorEntityProcessor processor of an error that has an entity, to fail the single * @param errorProcessor processor of an error that does not have an entity @@ -497,11 +497,11 @@ public static OidcConfig create(Config config) { public static Single postJsonResponse(WebClientRequestBuilder requestBuilder, Object toSubmit, Function jsonProcessor, - BiFunction> errorEntityProcessor, + BiFunction> errorEntityProcessor, BiFunction> errorProcessor) { return requestBuilder.submit(toSubmit) .flatMapSingle(response -> { - if (response.status().family() == Http.ResponseStatus.Family.SUCCESSFUL) { + if (response.status().family() == Http.Status.Family.SUCCESSFUL) { return response.content() .as(JsonObject.class) .map(jsonProcessor) @@ -948,7 +948,7 @@ public ClientAuthentication tokenEndpointAuthentication() { * @param request request builder * @param form form params builder */ - public void updateRequest(RequestType type, WebClientRequestBuilder request, FormParams.Builder form) { + public void updateRequest(RequestType type, WebClientRequestBuilder request, Parameters.Builder form) { if (type == RequestType.CODE_TO_TOKEN && tokenEndpointAuthentication == ClientAuthentication.CLIENT_SECRET_POST) { form.add("client_id", clientId); form.add("client_secret", clientSecret); diff --git a/security/providers/oidc-common/src/main/java/module-info.java b/security/providers/oidc-common/src/main/java/module-info.java index 3d2cbee529d..91ee818e6a4 100644 --- a/security/providers/oidc-common/src/main/java/module-info.java +++ b/security/providers/oidc-common/src/main/java/module-info.java @@ -26,6 +26,7 @@ requires transitive io.helidon.security.util; // WebClient is part of API requires transitive io.helidon.webclient; + requires io.helidon.common.parameters; requires io.helidon.security.providers.common; requires io.helidon.security.jwt; diff --git a/security/providers/oidc-common/src/test/resources/logging.properties b/security/providers/oidc-common/src/test/resources/logging.properties index 7af6f0d4f63..a8c16c7fcd4 100644 --- a/security/providers/oidc-common/src/test/resources/logging.properties +++ b/security/providers/oidc-common/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler .level=INFO io.helidon.security.level=FINEST AUDIT.level=FINEST diff --git a/security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/OidcProvider.java b/security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/OidcProvider.java index 597d80a4b5d..823ec36fc4a 100644 --- a/security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/OidcProvider.java +++ b/security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/OidcProvider.java @@ -39,9 +39,9 @@ import java.util.stream.Collectors; import io.helidon.common.Errors; -import io.helidon.common.http.FormParams; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.parameters.Parameters; import io.helidon.common.reactive.Single; import io.helidon.config.Config; import io.helidon.config.DeprecatedConfig; @@ -160,13 +160,13 @@ private OidcProvider(Builder builder, OidcOutboundConfig oidcOutboundConfig) { }; } else { this.jwtValidator = (signedJwt, collector) -> { - FormParams.Builder form = FormParams.builder() + Parameters.Builder form = Parameters.builder("oidc-form-param") .add("token", signedJwt.tokenContent()); WebClientRequestBuilder post = oidcConfig.appWebClient() .post() .uri(oidcConfig.introspectUri()) - .accept(MediaType.APPLICATION_JSON) + .accept(HttpMediaType.APPLICATION_JSON) .headers(it -> { it.add(Http.Header.CACHE_CONTROL, "no-cache, no-store, must-revalidate"); return it; @@ -420,14 +420,16 @@ private AuthenticationResponse errorResponseNoRedirect(String code, String descr return AuthenticationResponse.builder() .status(SecurityResponse.SecurityStatus.FAILURE) .statusCode(Http.Status.UNAUTHORIZED_401.code()) - .responseHeader(Http.Header.WWW_AUTHENTICATE, "Bearer realm=\"" + oidcConfig.realm() + "\"") + .responseHeader(Http.Header.WWW_AUTHENTICATE.defaultCase(), + "Bearer realm=\"" + oidcConfig.realm() + "\"") .description(description) .build(); } else { return AuthenticationResponse.builder() .status(SecurityResponse.SecurityStatus.FAILURE) .statusCode(status.code()) - .responseHeader(Http.Header.WWW_AUTHENTICATE, errorHeader(code, description)) + .responseHeader(Http.Header.WWW_AUTHENTICATE.defaultCase(), + errorHeader(code, description)) .description(description) .build(); } diff --git a/security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/OidcSupport.java b/security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/OidcSupport.java index c8f3274264e..8d2e7236fe6 100644 --- a/security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/OidcSupport.java +++ b/security/providers/oidc/src/main/java/io/helidon/security/providers/oidc/OidcSupport.java @@ -26,8 +26,9 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; -import io.helidon.common.http.FormParams; import io.helidon.common.http.Http; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.parameters.Parameters; import io.helidon.config.Config; import io.helidon.security.Security; import io.helidon.security.integration.webserver.WebSecurity; @@ -35,6 +36,7 @@ import io.helidon.security.providers.oidc.common.OidcCookieHandler; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; +import io.helidon.webserver.RequestHeaders; import io.helidon.webserver.ResponseHeaders; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerRequest; @@ -45,6 +47,8 @@ import jakarta.json.JsonObject; +import static io.helidon.common.http.Http.Header.HOST; + /** * OIDC integration requires web resources to be exposed through a web server. * This registers the endpoint to which OIDC redirects browser after successful login. @@ -276,14 +280,14 @@ private void processOidcRedirect(ServerRequest req, ServerResponse res) { private void processCode(String code, ServerRequest req, ServerResponse res) { WebClient webClient = oidcConfig.appWebClient(); - FormParams.Builder form = FormParams.builder() + Parameters.Builder form = Parameters.builder("oidc-form-params") .add("grant_type", "authorization_code") .add("code", code) .add("redirect_uri", redirectUri(req)); WebClientRequestBuilder post = webClient.post() .uri(oidcConfig.tokenEndpointUri()) - .accept(io.helidon.common.http.MediaType.APPLICATION_JSON); + .accept(HttpMediaType.APPLICATION_JSON); oidcConfig.updateRequest(OidcConfig.RequestType.CODE_TO_TOKEN, post, @@ -305,10 +309,10 @@ private Object postLogoutUri(ServerRequest req) { } String path = uri.getPath(); path = path.startsWith("/") ? path : "/" + path; - Optional host = req.headers().first("host"); - if (host.isPresent()) { + RequestHeaders headers = req.headers(); + if (headers.contains(HOST)) { String scheme = oidcConfig.forceHttpsRedirects() || req.isSecure() ? "https" : "http"; - return scheme + "://" + host.get() + path; + return scheme + "://" + headers.get(HOST).value() + path; } else { LOGGER.warning("Request without Host header received, yet post logout URI does not define a host"); return oidcConfig.toString(); @@ -316,7 +320,7 @@ private Object postLogoutUri(ServerRequest req) { } private String redirectUri(ServerRequest req) { - Optional host = req.headers().first("host"); + Optional host = req.headers().first(HOST); if (host.isPresent()) { String scheme = req.isSecure() ? "https" : "http"; @@ -375,7 +379,7 @@ private void sendError(ServerResponse response, Throwable t) { .send(); } - private Optional processError(ServerResponse serverResponse, Http.ResponseStatus status, String entity) { + private Optional processError(ServerResponse serverResponse, Http.Status status, String entity) { LOGGER.log(Level.FINE, "Invalid token or failed request when connecting to OIDC Token Endpoint. Response: " + entity + ", response status: " + status); diff --git a/security/providers/oidc/src/test/resources/logging.properties b/security/providers/oidc/src/test/resources/logging.properties index 5a59caae27f..e3e66bb9511 100644 --- a/security/providers/oidc/src/test/resources/logging.properties +++ b/security/providers/oidc/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler .level=INFO io.helidon.security.level=FINEST AUDIT.level=FINEST diff --git a/security/security/pom.xml b/security/security/pom.xml index ecc8e20bce4..88f43888219 100644 --- a/security/security/pom.xml +++ b/security/security/pom.xml @@ -91,4 +91,18 @@ test + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + diff --git a/webclient/security/src/main/java/io/helidon/webclient/security/WebClientSecurity.java b/webclient/security/src/main/java/io/helidon/webclient/security/WebClientSecurity.java index 4aa67ad8f41..c0a4ec9397f 100644 --- a/webclient/security/src/main/java/io/helidon/webclient/security/WebClientSecurity.java +++ b/webclient/security/src/main/java/io/helidon/webclient/security/WebClientSecurity.java @@ -23,6 +23,7 @@ import io.helidon.common.context.Context; import io.helidon.common.context.Contexts; +import io.helidon.common.http.Http; import io.helidon.common.reactive.Single; import io.helidon.security.EndpointConfig; import io.helidon.security.OutboundSecurityClientBuilder; @@ -174,10 +175,8 @@ private WebClientServiceRequest processResponse(WebClientServiceRequest request, LOGGER.finest(() -> " + Header: " + entry.getKey() + ": " + entry.getValue()); //replace existing - clientHeaders.remove(entry.getKey()); - for (String value : entry.getValue()) { - clientHeaders.put(entry.getKey(), value); - } + Http.HeaderName headerName = Http.Header.create(entry.getKey()); + clientHeaders.set(headerName, entry.getValue().toArray(new String[0])); } span.end(); return request; From 028f34813411462f60b621e66fda8db906fb4b9f Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:52:33 +0200 Subject: [PATCH 21/54] MP refactored to new common. --- microprofile/cdi/pom.xml | 4 ++ .../cdi/HelidonContainerImpl.java | 6 +-- .../cdi/src/main/java/module-info.java | 3 +- .../config/src/main/java/module-info.java | 2 +- .../microprofile/cors/CorsSupportMp.java | 21 +++++----- .../microprofile/cors/CorsDisabledTest.java | 10 ++--- .../microprofile/cors/CrossOriginTest.java | 34 +++++++-------- .../microprofile/cors/ErrorResponseTest.java | 11 +++-- microprofile/fault-tolerance/pom.xml | 41 +++++++++++-------- .../server/AnnotatedServiceConfigurer.java | 2 +- .../src/test/resources/logging.properties | 4 +- .../health/HealthCheckProvider.java | 2 +- .../health/src/main/java/module-info.java | 2 +- .../src/test/resources/logging.properties | 4 +- microprofile/lra/jax-rs/pom.xml | 14 +++++++ .../microprofile/lra/NonJaxRsResource.java | 12 +++--- .../lra/jax-rs/src/main/java/module-info.java | 2 +- .../CoordinatorClusterDeploymentService.java | 7 +--- .../lra/CoordinatorHeaderPropagationTest.java | 41 +++++++++---------- .../src/test/resources/logging.properties | 4 +- .../src/test/resources/logging.properties | 6 +-- .../openapi/TestServerWithConfig.java | 7 ++-- .../microprofile/openapi/TestUtil.java | 31 +++++++------- microprofile/server/pom.xml | 14 +++++++ .../server/ServerCdiExtension.java | 4 +- .../server/JaxRsApplicationTest.java | 2 +- .../server/JaxRsCdiExtensionTest.java | 2 +- .../server/ProducedRouteTest.java | 13 ++++-- microprofile/tracing/pom.xml | 14 +++++++ microprofile/websocket/pom.xml | 14 +++++++ 30 files changed, 201 insertions(+), 132 deletions(-) diff --git a/microprofile/cdi/pom.xml b/microprofile/cdi/pom.xml index 9702b6804fe..1438f581919 100644 --- a/microprofile/cdi/pom.xml +++ b/microprofile/cdi/pom.xml @@ -38,6 +38,10 @@ weld-se-core ${project.version} + + io.helidon.common + helidon-common-features + org.jboss.logging jboss-logging diff --git a/microprofile/cdi/src/main/java/io/helidon/microprofile/cdi/HelidonContainerImpl.java b/microprofile/cdi/src/main/java/io/helidon/microprofile/cdi/HelidonContainerImpl.java index f2f52bb54da..291bff329fc 100644 --- a/microprofile/cdi/src/main/java/io/helidon/microprofile/cdi/HelidonContainerImpl.java +++ b/microprofile/cdi/src/main/java/io/helidon/microprofile/cdi/HelidonContainerImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,13 +31,13 @@ import java.util.logging.LogRecord; import java.util.logging.Logger; -import io.helidon.common.HelidonFeatures; -import io.helidon.common.HelidonFlavor; import io.helidon.common.LogConfig; import io.helidon.common.SerializationConfig; import io.helidon.common.Version; import io.helidon.common.context.Context; import io.helidon.common.context.Contexts; +import io.helidon.common.features.HelidonFeatures; +import io.helidon.common.features.HelidonFlavor; import io.helidon.config.mp.MpConfig; import io.helidon.config.mp.MpConfigProviderResolver; diff --git a/microprofile/cdi/src/main/java/module-info.java b/microprofile/cdi/src/main/java/module-info.java index c5ae0ef4b2a..02cdc0a8f10 100644 --- a/microprofile/cdi/src/main/java/module-info.java +++ b/microprofile/cdi/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ requires io.helidon.common; requires io.helidon.config; requires io.helidon.config.mp; + requires io.helidon.common.features; requires weld.core.impl; requires weld.spi; diff --git a/microprofile/config/src/main/java/module-info.java b/microprofile/config/src/main/java/module-info.java index 6a1672a446b..79b54f7e1eb 100644 --- a/microprofile/config/src/main/java/module-info.java +++ b/microprofile/config/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microprofile/cors/src/main/java/io/helidon/microprofile/cors/CorsSupportMp.java b/microprofile/cors/src/main/java/io/helidon/microprofile/cors/CorsSupportMp.java index dee9a4ba820..5b22054f475 100644 --- a/microprofile/cors/src/main/java/io/helidon/microprofile/cors/CorsSupportMp.java +++ b/microprofile/cors/src/main/java/io/helidon/microprofile/cors/CorsSupportMp.java @@ -19,6 +19,7 @@ import java.util.Optional; import java.util.function.Supplier; +import io.helidon.common.http.Http; import io.helidon.webserver.cors.CorsSupportBase; import io.helidon.webserver.cors.CrossOriginConfig; @@ -116,18 +117,18 @@ public String path() { } @Override - public Optional firstHeader(String s) { - return Optional.ofNullable(requestContext.getHeaders().getFirst(s)); + public Optional firstHeader(Http.HeaderName key) { + return Optional.ofNullable(requestContext.getHeaders().getFirst(key.defaultCase())); } @Override - public boolean headerContainsKey(String s) { - return requestContext.getHeaders().containsKey(s); + public boolean headerContainsKey(Http.HeaderName key) { + return requestContext.getHeaders().containsKey(key.defaultCase()); } @Override - public List allHeaders(String s) { - return requestContext.getHeaders().get(s); + public List allHeaders(Http.HeaderName key) { + return requestContext.getHeaders().get(key.defaultCase()); } @Override @@ -167,14 +168,14 @@ static class ResponseAdapterMp implements ResponseAdapter { } @Override - public ResponseAdapter header(String key, String value) { - headers.add(key, value); + public ResponseAdapter header(Http.HeaderName key, String value) { + headers.add(key.defaultCase(), value); return this; } @Override - public ResponseAdapter header(String key, Object value) { - headers.add(key, value); + public ResponseAdapter header(Http.HeaderName key, Object value) { + headers.add(key.defaultCase(), value); return this; } diff --git a/microprofile/cors/src/test/java/io/helidon/microprofile/cors/CorsDisabledTest.java b/microprofile/cors/src/test/java/io/helidon/microprofile/cors/CorsDisabledTest.java index 9b29c36e577..755544e468b 100644 --- a/microprofile/cors/src/test/java/io/helidon/microprofile/cors/CorsDisabledTest.java +++ b/microprofile/cors/src/test/java/io/helidon/microprofile/cors/CorsDisabledTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,18 +44,18 @@ @AddConfig(key = "cors.enabled", value="false") class CorsDisabledTest { - @Inject - private WebTarget target; - static { System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); } + @Inject + private WebTarget target; + @Test void testCorsIsDisabled() { Response res = target.path("/cors2") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .put(Entity.entity("", MediaType.TEXT_PLAIN_TYPE)); assertThat(res.getStatusInfo(), is(Response.Status.OK)); assertThat("Headers from successful response", res.getHeaders().keySet(), not(hasItem(ACCESS_CONTROL_ALLOW_ORIGIN))); diff --git a/microprofile/cors/src/test/java/io/helidon/microprofile/cors/CrossOriginTest.java b/microprofile/cors/src/test/java/io/helidon/microprofile/cors/CrossOriginTest.java index 72c072c916b..3800d1f1483 100644 --- a/microprofile/cors/src/test/java/io/helidon/microprofile/cors/CrossOriginTest.java +++ b/microprofile/cors/src/test/java/io/helidon/microprofile/cors/CrossOriginTest.java @@ -151,7 +151,7 @@ public void optionsForMainPath() { void test1PreFlightAllowedOrigin() { Response res = target.path("/cors1") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .options(); assertThat(res.getStatusInfo(), is(Response.Status.OK)); @@ -165,7 +165,7 @@ void test1PreFlightAllowedOrigin() { void test1PreFlightAllowedHeaders1() { Response res = target.path("/cors1") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .header(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo") .options(); @@ -180,7 +180,7 @@ void test1PreFlightAllowedHeaders1() { void test1PreFlightAllowedHeaders2() { Response res = target.path("/cors1") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .header(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo, X-bar") .options(); @@ -198,7 +198,7 @@ void test1PreFlightAllowedHeaders2() { void test2PreFlightForbiddenOrigin() { Response res = target.path("/cors2") .request() - .header(ORIGIN, "http://not.allowed") + .header(ORIGIN.defaultCase(), "http://not.allowed") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .options(); assertThat(res.getStatusInfo(), is(Response.Status.FORBIDDEN)); @@ -208,7 +208,7 @@ void test2PreFlightForbiddenOrigin() { void test2PreFlightAllowedOrigin() { Response res = target.path("/cors2") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .options(); assertThat(res.getStatusInfo(), is(Response.Status.OK)); @@ -223,7 +223,7 @@ void test2PreFlightAllowedOrigin() { void test2PreFlightForbiddenMethod() { Response res = target.path("/cors2") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "POST") .options(); assertThat(res.getStatusInfo(), is(Response.Status.FORBIDDEN)); @@ -233,7 +233,7 @@ void test2PreFlightForbiddenMethod() { void test2PreFlightForbiddenHeader() { Response res = target.path("/cors2") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .header(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo, X-bar, X-oops") .options(); @@ -244,7 +244,7 @@ void test2PreFlightForbiddenHeader() { void test2PreFlightAllowedHeaders1() { Response res = target.path("/cors2") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .header(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo") .options(); @@ -261,7 +261,7 @@ void test2PreFlightAllowedHeaders1() { void test2PreFlightAllowedHeaders2() { Response res = target.path("/cors2") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .header(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo, X-bar") .options(); @@ -280,7 +280,7 @@ void test2PreFlightAllowedHeaders2() { void test2PreFlightAllowedHeaders3() { Response res = target.path("/cors2") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .header(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo, X-bar") .header(ACCESS_CONTROL_REQUEST_HEADERS, "X-foo, X-bar") @@ -300,7 +300,7 @@ void test2PreFlightAllowedHeaders3() { void test1ActualAllowedOrigin() { Response res = target.path("/cors1") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .put(Entity.entity("", MediaType.TEXT_PLAIN_TYPE)); assertThat(res.getStatusInfo(), is(Response.Status.OK)); @@ -311,7 +311,7 @@ void test1ActualAllowedOrigin() { void test2ActualAllowedOrigin() { Response res = target.path("/cors2") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .put(Entity.entity("", MediaType.TEXT_PLAIN_TYPE)); assertThat(res.getStatusInfo(), is(Response.Status.OK)); assertThat(res.getHeaders().getFirst(ACCESS_CONTROL_ALLOW_ORIGIN), is("http://foo.bar")); @@ -322,7 +322,7 @@ void test2ActualAllowedOrigin() { void test3PreFlightAllowedOrigin() { Response res = target.path("/cors3") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .options(); assertThat(res.getStatusInfo(), is(Response.Status.OK)); @@ -336,7 +336,7 @@ void test3PreFlightAllowedOrigin() { void test3ActualAllowedOrigin() { Response res = target.path("/cors3") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .put(Entity.entity("", MediaType.TEXT_PLAIN_TYPE)); assertThat(res.getStatusInfo(), is(Response.Status.OK)); @@ -347,7 +347,7 @@ void test3ActualAllowedOrigin() { void testMainPathInPresenceOfSubpath() { Response res = target.path("/cors0") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .get(); assertThat(res.getStatusInfo(), is(Response.Status.OK)); assertThat(res.getHeaders().containsKey(ACCESS_CONTROL_ALLOW_ORIGIN), is(true)); @@ -358,7 +358,7 @@ void testMainPathInPresenceOfSubpath() { void testSubPathPreflightAllowed() { Response res = target.path("/cors0/subpath") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .options(); assertThat(res.getStatusInfo(), is(Response.Status.OK)); @@ -372,7 +372,7 @@ void testSubPathPreflightAllowed() { void testSubPathActualAllowed() { Response res = target.path("/cors0/subpath") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "PUT") .put(Entity.entity("", MediaType.TEXT_PLAIN_TYPE)); assertThat(res.getStatusInfo(), is(Response.Status.OK)); diff --git a/microprofile/cors/src/test/java/io/helidon/microprofile/cors/ErrorResponseTest.java b/microprofile/cors/src/test/java/io/helidon/microprofile/cors/ErrorResponseTest.java index ca8e1dacea7..a0655112668 100644 --- a/microprofile/cors/src/test/java/io/helidon/microprofile/cors/ErrorResponseTest.java +++ b/microprofile/cors/src/test/java/io/helidon/microprofile/cors/ErrorResponseTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,19 +40,18 @@ @AddConfig(key = "cors.paths.0.allow-origins", value = "http://foo.bar, http://bar.foo") @AddConfig(key = "cors.paths.0.allow-methods", value = "DELETE, PUT") class ErrorResponseTest { - - @Inject - private WebTarget target; - static { System.setProperty("sun.net.http.allowRestrictedHeaders", "true"); } + @Inject + private WebTarget target; + @Test void testErrorResponse() { Response res = target.path("/notfound") .request() - .header(ORIGIN, "http://foo.bar") + .header(ORIGIN.defaultCase(), "http://foo.bar") .header(ACCESS_CONTROL_REQUEST_METHOD, "GET") .get(); assertThat("Status from missing endpoint request", res.getStatusInfo(), is(Response.Status.NOT_FOUND)); diff --git a/microprofile/fault-tolerance/pom.xml b/microprofile/fault-tolerance/pom.xml index e52f1f41b3b..19d646b790f 100644 --- a/microprofile/fault-tolerance/pom.xml +++ b/microprofile/fault-tolerance/pom.xml @@ -37,22 +37,6 @@ etc/spotbugs/exclude.xml - - - - org.apache.maven.plugins - maven-surefire-plugin - - 1 - false - true - false - alphabetical - - - - - org.eclipse.microprofile.config @@ -137,4 +121,29 @@ test + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + org.apache.maven.plugins + maven-surefire-plugin + + 1 + false + true + false + alphabetical + + + + diff --git a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/AnnotatedServiceConfigurer.java b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/AnnotatedServiceConfigurer.java index 079aeefde72..85a0953742f 100644 --- a/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/AnnotatedServiceConfigurer.java +++ b/microprofile/grpc/server/src/main/java/io/helidon/microprofile/grpc/server/AnnotatedServiceConfigurer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microprofile/grpc/server/src/test/resources/logging.properties b/microprofile/grpc/server/src/test/resources/logging.properties index f9dfd25b9da..f9bcbc4fc4b 100644 --- a/microprofile/grpc/server/src/test/resources/logging.properties +++ b/microprofile/grpc/server/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCheckProvider.java b/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCheckProvider.java index 4e566e295b3..698cf1c230d 100644 --- a/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCheckProvider.java +++ b/microprofile/health/src/main/java/io/helidon/microprofile/health/HealthCheckProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microprofile/health/src/main/java/module-info.java b/microprofile/health/src/main/java/module-info.java index 983896419b4..57fd2d7478a 100644 --- a/microprofile/health/src/main/java/module-info.java +++ b/microprofile/health/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microprofile/health/src/test/resources/logging.properties b/microprofile/health/src/test/resources/logging.properties index f4961940a42..00aa993367f 100644 --- a/microprofile/health/src/test/resources/logging.properties +++ b/microprofile/health/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/microprofile/lra/jax-rs/pom.xml b/microprofile/lra/jax-rs/pom.xml index dcb3a4b7b26..9f85edafc4c 100644 --- a/microprofile/lra/jax-rs/pom.xml +++ b/microprofile/lra/jax-rs/pom.xml @@ -85,4 +85,18 @@ test + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + \ No newline at end of file diff --git a/microprofile/lra/jax-rs/src/main/java/io/helidon/microprofile/lra/NonJaxRsResource.java b/microprofile/lra/jax-rs/src/main/java/io/helidon/microprofile/lra/NonJaxRsResource.java index ebbc25d3669..5621ab68b00 100644 --- a/microprofile/lra/jax-rs/src/main/java/io/helidon/microprofile/lra/NonJaxRsResource.java +++ b/microprofile/lra/jax-rs/src/main/java/io/helidon/microprofile/lra/NonJaxRsResource.java @@ -27,7 +27,7 @@ import io.helidon.common.Reflected; import io.helidon.common.configurable.ThreadPoolSupplier; -import io.helidon.common.http.HttpRequest; +import io.helidon.common.http.Http; import io.helidon.common.reactive.Single; import io.helidon.config.Config; import io.helidon.lra.coordinator.client.PropagatedHeaders; @@ -43,10 +43,7 @@ import org.eclipse.microprofile.lra.LRAResponse; import org.eclipse.microprofile.lra.annotation.LRAStatus; import org.eclipse.microprofile.lra.annotation.ParticipantStatus; - -import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER; -import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_ENDED_CONTEXT_HEADER; -import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_PARENT_CONTEXT_HEADER; +import org.eclipse.microprofile.lra.annotation.ws.rs.LRA; @Reflected class NonJaxRsResource { @@ -57,6 +54,9 @@ class NonJaxRsResource { static final String CONFIG_CONTEXT_PATH_KEY = CONFIG_CONTEXT_KEY + ".context-path"; static final String CONTEXT_PATH_DEFAULT = "/lra-participant"; private static final String LRA_PARTICIPANT = "lra-participant"; + private static final Http.HeaderName LRA_HTTP_CONTEXT_HEADER = Http.Header.create(LRA.LRA_HTTP_CONTEXT_HEADER); + private static final Http.HeaderName LRA_HTTP_ENDED_CONTEXT_HEADER = Http.Header.create(LRA.LRA_HTTP_ENDED_CONTEXT_HEADER); + private static final Http.HeaderName LRA_HTTP_PARENT_CONTEXT_HEADER = Http.Header.create(LRA.LRA_HTTP_PARENT_CONTEXT_HEADER); private final ExecutorService exec; @@ -98,7 +98,7 @@ Service createNonJaxRsParticipantResource() { .any("/{type}/{fqdn}/{methodName}", (req, res) -> { LOGGER.log(Level.FINE, () -> "Non JAX-RS LRA resource " + req.method().name() + " " + req.absoluteUri()); RequestHeaders headers = req.headers(); - HttpRequest.Path path = req.path(); + ServerRequest.Path path = req.path(); URI lraId = headers.first(LRA_HTTP_CONTEXT_HEADER) .or(() -> headers.first(LRA_HTTP_ENDED_CONTEXT_HEADER)) diff --git a/microprofile/lra/jax-rs/src/main/java/module-info.java b/microprofile/lra/jax-rs/src/main/java/module-info.java index 34ed2324ed3..37570066977 100644 --- a/microprofile/lra/jax-rs/src/main/java/module-info.java +++ b/microprofile/lra/jax-rs/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microprofile/lra/jax-rs/src/test/java/io/helidon/microprofile/lra/CoordinatorClusterDeploymentService.java b/microprofile/lra/jax-rs/src/test/java/io/helidon/microprofile/lra/CoordinatorClusterDeploymentService.java index c798dba3a02..52f84aa6c36 100644 --- a/microprofile/lra/jax-rs/src/test/java/io/helidon/microprofile/lra/CoordinatorClusterDeploymentService.java +++ b/microprofile/lra/jax-rs/src/test/java/io/helidon/microprofile/lra/CoordinatorClusterDeploymentService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -118,10 +118,7 @@ public Service coordinatorLoadBalancerService() { .queryParams(req.queryParams()) .submit(req.content()) .forSingle(wr -> { - wr.headers().toMap() - .forEach((k, values) -> values - .forEach(e -> res.headers().add(k, e)) - ); + wr.headers().forEach(res.headers()::add); res.status(wr.status()) .send(wr.content()); })) diff --git a/microprofile/lra/jax-rs/src/test/java/io/helidon/microprofile/lra/CoordinatorHeaderPropagationTest.java b/microprofile/lra/jax-rs/src/test/java/io/helidon/microprofile/lra/CoordinatorHeaderPropagationTest.java index df19b7124c5..8b568caef61 100644 --- a/microprofile/lra/jax-rs/src/test/java/io/helidon/microprofile/lra/CoordinatorHeaderPropagationTest.java +++ b/microprofile/lra/jax-rs/src/test/java/io/helidon/microprofile/lra/CoordinatorHeaderPropagationTest.java @@ -29,24 +29,6 @@ import java.util.concurrent.atomic.AtomicLong; import java.util.stream.Stream; -import jakarta.annotation.Priority; -import jakarta.enterprise.context.ApplicationScoped; -import jakarta.enterprise.context.Initialized; -import jakarta.enterprise.event.Observes; -import jakarta.enterprise.inject.Produces; -import jakarta.enterprise.inject.spi.BeanManager; -import jakarta.inject.Inject; -import jakarta.ws.rs.GET; -import jakarta.ws.rs.HeaderParam; -import jakarta.ws.rs.PUT; -import jakarta.ws.rs.Path; -import jakarta.ws.rs.client.ClientBuilder; -import jakarta.ws.rs.client.Entity; -import jakarta.ws.rs.client.WebTarget; -import jakarta.ws.rs.core.Context; -import jakarta.ws.rs.core.HttpHeaders; -import jakarta.ws.rs.core.Response; - import io.helidon.common.context.Contexts; import io.helidon.common.reactive.Multi; import io.helidon.lra.coordinator.client.CoordinatorClient; @@ -64,6 +46,23 @@ import io.helidon.webclient.WebClient; import io.helidon.webserver.Service; +import jakarta.annotation.Priority; +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.enterprise.context.Initialized; +import jakarta.enterprise.event.Observes; +import jakarta.enterprise.inject.Produces; +import jakarta.enterprise.inject.spi.BeanManager; +import jakarta.inject.Inject; +import jakarta.ws.rs.GET; +import jakarta.ws.rs.HeaderParam; +import jakarta.ws.rs.PUT; +import jakarta.ws.rs.Path; +import jakarta.ws.rs.client.ClientBuilder; +import jakarta.ws.rs.client.Entity; +import jakarta.ws.rs.client.WebTarget; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.HttpHeaders; +import jakarta.ws.rs.core.Response; import org.eclipse.microprofile.lra.annotation.AfterLRA; import org.eclipse.microprofile.lra.annotation.Compensate; import org.eclipse.microprofile.lra.annotation.Complete; @@ -166,7 +165,7 @@ Service mockCoordinator() { .addHeader(LRA_HTTP_CONTEXT_HEADER, lraId) .headers(reqHeaders -> { // relay all incoming headers - req.headers().toMap().forEach(reqHeaders::add); + req.headers().forEach(reqHeaders::add); return reqHeaders; }) .submit(LRAStatus.Closing.name()) @@ -188,7 +187,7 @@ Service mockCoordinator() { .addHeader(LRA_HTTP_CONTEXT_HEADER, lraId) .headers(reqHeaders -> { // relay all incoming headers - req.headers().toMap().forEach(reqHeaders::add); + req.headers().forEach(reqHeaders::add); return reqHeaders; }) .submit() @@ -205,7 +204,7 @@ Service mockCoordinator() { .addHeader(LRA_HTTP_CONTEXT_HEADER, lraId) .headers(reqHeaders -> { // relay all incoming headers - req.headers().toMap().forEach(reqHeaders::add); + req.headers().forEach(reqHeaders::add); return reqHeaders; }) .submit() diff --git a/microprofile/lra/jax-rs/src/test/resources/logging.properties b/microprofile/lra/jax-rs/src/test/resources/logging.properties index eeeb41ee030..a4d82b92f96 100644 --- a/microprofile/lra/jax-rs/src/test/resources/logging.properties +++ b/microprofile/lra/jax-rs/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/microprofile/metrics/src/test/resources/logging.properties b/microprofile/metrics/src/test/resources/logging.properties index cfd73bbe710..86c3cfbf1fb 100644 --- a/microprofile/metrics/src/test/resources/logging.properties +++ b/microprofile/metrics/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler,java.util.logging.FileHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler,java.util.logging.FileHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n @@ -37,7 +37,7 @@ java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$ #java.util.logging.FileHandler.level=FINEST #java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter -#io.helidon.common.HelidonConsoleHandler.level=FINEST +#io.helidon.logging.jul.HelidonConsoleHandler.level=FINEST #org.jboss.weld.Bootstrap.level = FINEST #org.jboss.weld.Bean.level=FINEST diff --git a/microprofile/openapi/src/test/java/io/helidon/microprofile/openapi/TestServerWithConfig.java b/microprofile/openapi/src/test/java/io/helidon/microprofile/openapi/TestServerWithConfig.java index 9197f4bf0c2..3788733d475 100644 --- a/microprofile/openapi/src/test/java/io/helidon/microprofile/openapi/TestServerWithConfig.java +++ b/microprofile/openapi/src/test/java/io/helidon/microprofile/openapi/TestServerWithConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,8 @@ import java.net.HttpURLConnection; import java.util.Map; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.ClasspathConfigSource; import io.helidon.config.Config; import io.helidon.microprofile.server.Server; @@ -50,7 +51,7 @@ public static void startServer() throws Exception { server.port(), "GET", ALTERNATE_OPENAPI_PATH, - MediaType.APPLICATION_OPENAPI_YAML); + HttpMediaType.create(MediaTypes.APPLICATION_OPENAPI_YAML)); yaml = TestUtil.yamlFromResponse(cnx); } diff --git a/microprofile/openapi/src/test/java/io/helidon/microprofile/openapi/TestUtil.java b/microprofile/openapi/src/test/java/io/helidon/microprofile/openapi/TestUtil.java index 8cf257730ba..908a168605b 100644 --- a/microprofile/openapi/src/test/java/io/helidon/microprofile/openapi/TestUtil.java +++ b/microprofile/openapi/src/test/java/io/helidon/microprofile/openapi/TestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,8 @@ import java.nio.charset.Charset; import java.util.Map; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.microprofile.server.Server; @@ -77,7 +78,7 @@ public static void cleanup(Server server, HttpURLConnection cnx) { /** * Returns a {@code HttpURLConnection} for the requested method and path and - * {code @MediaType} from the specified {@link WebServer}. + * {code @MediaType} from the specified {@link io.helidon.webserver.WebServer}. * * @param port port to connect to * @param method HTTP method to use in building the connection @@ -90,12 +91,12 @@ public static HttpURLConnection getURLConnection( int port, String method, String path, - MediaType mediaType) throws Exception { + HttpMediaType mediaType) throws Exception { URL url = new URL("http://localhost:" + port + path); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod(method); if (mediaType != null) { - conn.setRequestProperty("Accept", mediaType.toString()); + conn.setRequestProperty("Accept", mediaType.text()); } System.out.println("Connecting: " + method + " " + url); return conn; @@ -108,14 +109,10 @@ public static HttpURLConnection getURLConnection( * @param cnx the HttpURLConnection from which to get the content type * @return the MediaType corresponding to the content type in the response */ - public static MediaType mediaTypeFromResponse(HttpURLConnection cnx) { - MediaType returnedMediaType = MediaType.parse(cnx.getContentType()); - if (!returnedMediaType.charset().isPresent()) { - returnedMediaType = MediaType.builder() - .type(returnedMediaType.type()) - .subtype(returnedMediaType.subtype()) - .charset(Charset.defaultCharset().name()) - .build(); + public static HttpMediaType mediaTypeFromResponse(HttpURLConnection cnx) { + HttpMediaType returnedMediaType = HttpMediaType.create(cnx.getContentType()); + if (returnedMediaType.charset().isEmpty()) { + return returnedMediaType.withCharset(Charset.defaultCharset().name()); } return returnedMediaType; } @@ -128,8 +125,8 @@ public static MediaType mediaTypeFromResponse(HttpURLConnection cnx) { * @throws IOException in case of errors reading the HTTP response payload */ public static String stringYAMLFromResponse(HttpURLConnection cnx) throws IOException { - MediaType returnedMediaType = mediaTypeFromResponse(cnx); - assertTrue(MediaType.APPLICATION_OPENAPI_YAML.test(returnedMediaType), + HttpMediaType returnedMediaType = mediaTypeFromResponse(cnx); + assertTrue(HttpMediaType.create(MediaTypes.APPLICATION_OPENAPI_YAML).test(returnedMediaType), "Unexpected returned media type"); return stringFromResponse(cnx, returnedMediaType); } @@ -145,7 +142,7 @@ public static String stringYAMLFromResponse(HttpURLConnection cnx) throws IOExce * specified {@code MediaType} * @throws IOException in case of errors reading the response payload */ - public static String stringFromResponse(HttpURLConnection cnx, MediaType mediaType) throws IOException { + public static String stringFromResponse(HttpURLConnection cnx, HttpMediaType mediaType) throws IOException { try (final InputStreamReader isr = new InputStreamReader( cnx.getInputStream(), mediaType.charset().get())) { StringBuilder sb = new StringBuilder(); @@ -169,7 +166,7 @@ public static String stringFromResponse(HttpURLConnection cnx, MediaType mediaTy */ @SuppressWarnings(value = "unchecked") public static Map yamlFromResponse(HttpURLConnection cnx) throws IOException { - MediaType returnedMediaType = mediaTypeFromResponse(cnx); + HttpMediaType returnedMediaType = mediaTypeFromResponse(cnx); Yaml yaml = new Yaml(); Charset cs = Charset.defaultCharset(); if (returnedMediaType.charset().isPresent()) { diff --git a/microprofile/server/pom.xml b/microprofile/server/pom.xml index f182e4402e3..2d5cea6eb7d 100644 --- a/microprofile/server/pom.xml +++ b/microprofile/server/pom.xml @@ -151,4 +151,18 @@ test + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + diff --git a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java index 919e94c6441..a95bbeae67c 100644 --- a/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java +++ b/microprofile/server/src/main/java/io/helidon/microprofile/server/ServerCdiExtension.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -266,7 +266,7 @@ private void registerDefaultRedirect() { .or(() -> config.get("server.base-path").asString().asOptional()) .ifPresent(basePath -> routingBuilder.any("/", (req, res) -> { res.status(Http.Status.MOVED_PERMANENTLY_301); - res.headers().put(Http.Header.LOCATION, basePath); + res.headers().set(Http.Header.LOCATION, basePath); res.send(); })); STARTUP_LOGGER.finest("Builders ready"); diff --git a/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsApplicationTest.java b/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsApplicationTest.java index dbf8600d2ed..4378c55b8f6 100644 --- a/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsApplicationTest.java +++ b/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsApplicationTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsCdiExtensionTest.java b/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsCdiExtensionTest.java index 2e8ab965a3b..d167f7b5535 100644 --- a/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsCdiExtensionTest.java +++ b/microprofile/server/src/test/java/io/helidon/microprofile/server/JaxRsCdiExtensionTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/microprofile/server/src/test/java/io/helidon/microprofile/server/ProducedRouteTest.java b/microprofile/server/src/test/java/io/helidon/microprofile/server/ProducedRouteTest.java index 507dc51aa29..64548ee0376 100644 --- a/microprofile/server/src/test/java/io/helidon/microprofile/server/ProducedRouteTest.java +++ b/microprofile/server/src/test/java/io/helidon/microprofile/server/ProducedRouteTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; +import io.helidon.common.http.Http.Header; +import io.helidon.common.http.Http.HeaderName; import io.helidon.microprofile.tests.junit5.AddBean; import io.helidon.microprofile.tests.junit5.AddConfig; import io.helidon.microprofile.tests.junit5.AddExtension; @@ -64,10 +66,13 @@ public class ProducedRouteTest { static final String UNFILTERED_PATH = "/unfiltered"; static final String COOL_HEADER = "Cool-Header"; + static final HeaderName COOL_HEADER_NAME = Header.create(COOL_HEADER); static final String COOL_VALUE = "cool value"; static final String COOLER_HEADER = "Cooler-Header"; + static final HeaderName COOLER_HEADER_NAME = Header.create(COOLER_HEADER); static final String COOLER_VALUE = "cooler value"; static final String COOLEST_HEADER = "Coolest-Header"; + static final HeaderName COOLEST_HEADER_NAME = Header.create(COOLEST_HEADER); static final String COOLEST_VALUE = "coolest value"; @Test @@ -139,7 +144,7 @@ public Response unfilteredGet() { @RoutingName(value = "wrong", required = true) @RoutingPath("wrong") Service coolestFieldProducedService = rules -> rules.any((req, res) -> { - res.headers().put(COOLEST_HEADER, COOLEST_VALUE); + res.headers().set(COOLEST_HEADER_NAME, COOLEST_VALUE); req.next(); }); @@ -149,7 +154,7 @@ public Response unfilteredGet() { @RoutingName(RoutingName.DEFAULT_NAME) public Service coolTestService() { return rules -> rules.any((req, res) -> { - res.headers().put(COOL_HEADER, COOL_VALUE); + res.headers().set(COOL_HEADER_NAME, COOL_VALUE); req.next(); }); } @@ -158,7 +163,7 @@ public Service coolTestService() { @ApplicationScoped public Service coolerTestService() { return rules -> rules.any((req, res) -> { - res.headers().put(COOLER_HEADER, COOLER_VALUE); + res.headers().set(COOLER_HEADER_NAME, COOLER_VALUE); req.next(); }); } diff --git a/microprofile/tracing/pom.xml b/microprofile/tracing/pom.xml index d5978accc3d..3e935fe139c 100644 --- a/microprofile/tracing/pom.xml +++ b/microprofile/tracing/pom.xml @@ -98,4 +98,18 @@ compile + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + diff --git a/microprofile/websocket/pom.xml b/microprofile/websocket/pom.xml index 815d64f8aeb..de6f5a8fa36 100644 --- a/microprofile/websocket/pom.xml +++ b/microprofile/websocket/pom.xml @@ -72,4 +72,18 @@ helidon-microprofile-server + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + From 5a69a9dfe6a070d1d69ae617474d4031689fddbd Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:53:20 +0200 Subject: [PATCH 22/54] Integrations refactored to new common. --- .../src/test/logging.properties | 4 +- .../src/test/logging.properties | 4 +- .../cdi/jpa-cdi/src/test/logging.properties | 4 +- .../common/rest/ApiJsonRequest.java | 4 +- .../integrations/common/rest/ApiRequest.java | 18 +-- .../integrations/common/rest/ApiResponse.java | 12 +- .../common/rest/ApiRestException.java | 10 +- .../common/rest/ApiRestRequest.java | 4 +- .../common/rest/ResponseBuilder.java | 4 +- .../integrations/common/rest/RestApi.java | 24 ++-- .../integrations/common/rest/RestApiBase.java | 116 +++++++++--------- .../cdi/src/test/resources/logging.properties | 6 +- .../MicrometerPrometheusRegistrySupport.java | 40 +++--- .../micrometer/MicrometerEndpointTests.java | 8 +- .../MicrometerSimplePrometheusTest.java | 12 +- integrations/micronaut/pom.xml | 5 +- integrations/pom.xml | 1 - .../vault/auths/approle/AppRoleRestApi.java | 4 +- .../vault/auths/common/VaultRestApi.java | 6 +- .../vault/auths/k8s/K8sRestApi.java | 4 +- .../database/src/main/java/module-info.java | 1 + .../sys/sys/src/main/java/module-info.java | 1 + .../io/helidon/integrations/vault/Vault.java | 2 +- .../jersey/connector/HelidonEntity.java | 6 +- .../jersey/connector/HelidonStructures.java | 16 +-- 25 files changed, 157 insertions(+), 159 deletions(-) diff --git a/integrations/cdi/datasource-hikaricp/src/test/logging.properties b/integrations/cdi/datasource-hikaricp/src/test/logging.properties index 8b1001d8e10..5ce9dea79c9 100644 --- a/integrations/cdi/datasource-hikaricp/src/test/logging.properties +++ b/integrations/cdi/datasource-hikaricp/src/test/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,4 +14,4 @@ # limitations under the License. # .level=INFO -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler diff --git a/integrations/cdi/datasource-ucp/src/test/logging.properties b/integrations/cdi/datasource-ucp/src/test/logging.properties index e2a4627384f..564de3a7974 100644 --- a/integrations/cdi/datasource-ucp/src/test/logging.properties +++ b/integrations/cdi/datasource-ucp/src/test/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,5 +14,5 @@ # limitations under the License. # .level=INFO -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler oracle.ucp.level=FINE diff --git a/integrations/cdi/jpa-cdi/src/test/logging.properties b/integrations/cdi/jpa-cdi/src/test/logging.properties index 1da40d587cb..86a35652573 100644 --- a/integrations/cdi/jpa-cdi/src/test/logging.properties +++ b/integrations/cdi/jpa-cdi/src/test/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # .level=INFO -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler io.helidon.integrations.cdi.jpa.level=WARNING org.eclipse.persistence.level=WARNING diff --git a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiJsonRequest.java b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiJsonRequest.java index b39e7aa21fa..36b61ed8ad1 100644 --- a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiJsonRequest.java +++ b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiJsonRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import java.util.Map; import java.util.Optional; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaType; /** * Common base class for REST requests that have an entity. diff --git a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiRequest.java b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiRequest.java index 32732ac0c1e..6084db659bf 100644 --- a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiRequest.java +++ b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import java.util.Map; import java.util.Optional; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaType; import jakarta.json.JsonBuilderFactory; import jakarta.json.JsonObject; @@ -53,8 +53,9 @@ public interface ApiRequest> { T addQueryParam(String name, String... value); /** - * The media type header, defaults to {@link io.helidon.common.http.MediaType#APPLICATION_JSON} when JSON entity is present, - * to {@link io.helidon.common.http.MediaType#APPLICATION_OCTET_STREAM} for publisher base requests, empty otherwise. + * The media type header, defaults to {@link io.helidon.common.media.type.MediaTypes#APPLICATION_JSON} when JSON + * entity is present, to {@link io.helidon.common.media.type.MediaTypes#APPLICATION_OCTET_STREAM} for publisher + * base requests, empty otherwise. * * @param mediaType media type of request * @return updated request @@ -62,8 +63,9 @@ public interface ApiRequest> { T requestMediaType(MediaType mediaType); /** - * The accept header, defaults to {@link io.helidon.common.http.MediaType#APPLICATION_JSON} for most requests, except - * for requests that return publisher, which default to {@link io.helidon.common.http.MediaType#APPLICATION_OCTET_STREAM}. + * The accept header, defaults to {@link io.helidon.common.media.type.MediaTypes#APPLICATION_JSON} for most requests, except + * for requests that return publisher, which default to + * {@link io.helidon.common.media.type.MediaTypes#APPLICATION_OCTET_STREAM}. * * @param mediaType accepted media type * @return updated request @@ -108,7 +110,7 @@ default Optional toJson(JsonBuilderFactory factory) { * Request media type. * * @return media type if configured - * @see #requestMediaType(io.helidon.common.http.MediaType) + * @see #requestMediaType(io.helidon.common.media.type.MediaType) */ Optional requestMediaType(); @@ -116,7 +118,7 @@ default Optional toJson(JsonBuilderFactory factory) { * Response media type. * * @return media type if configured - * @see #responseMediaType(io.helidon.common.http.MediaType) + * @see #responseMediaType(io.helidon.common.media.type.MediaType) */ Optional responseMediaType(); diff --git a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiResponse.java b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiResponse.java index c2822e2c6f9..4238f49aeb7 100644 --- a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiResponse.java +++ b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiResponse.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ */ public abstract class ApiResponse extends ApiJsonParser { private final Headers headers; - private final Http.ResponseStatus status; + private final Http.Status status; private final String requestId; /** @@ -57,7 +57,7 @@ public Headers headers() { * * @return status of the response (only if successful) */ - public Http.ResponseStatus status() { + public Http.Status status() { return status; } @@ -80,7 +80,7 @@ public String requestId() { public abstract static class Builder, T extends ApiResponse> implements io.helidon.common.Builder { private Headers headers; - private Http.ResponseStatus status; + private Http.Status status; private String requestId; /** @@ -106,7 +106,7 @@ public B headers(Headers headers) { * @param status HTTP status * @return updated builder */ - public B status(Http.ResponseStatus status) { + public B status(Http.Status status) { this.status = status; return me(); } @@ -136,7 +136,7 @@ public Headers headers() { * * @return status */ - public Http.ResponseStatus status() { + public Http.Status status() { return status; } diff --git a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiRestException.java b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiRestException.java index 470b2e9b60d..c552bbd6846 100644 --- a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiRestException.java +++ b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiRestException.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,7 +30,7 @@ */ public abstract class ApiRestException extends ApiException { private final String requestId; - private final Http.ResponseStatus status; + private final Http.Status status; private final Headers headers; private final String apiSpecificError; @@ -53,7 +53,7 @@ protected ApiRestException(BaseBuilder builder) { * * @return status */ - public Http.ResponseStatus status() { + public Http.Status status() { return status; } @@ -92,7 +92,7 @@ public Headers headers() { public abstract static class BaseBuilder> { private String message; private String requestId; - private Http.ResponseStatus status; + private Http.Status status; private Headers headers; private String apiSpecificError; private Throwable cause; @@ -125,7 +125,7 @@ public B requestId(String requestId) { * @param status returned status * @return updated builder */ - public B status(Http.ResponseStatus status) { + public B status(Http.Status status) { this.status = status; return me(); } diff --git a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiRestRequest.java b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiRestRequest.java index 3e7b1413898..40262f740f4 100644 --- a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiRestRequest.java +++ b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ApiRestRequest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import java.util.Map; import java.util.Optional; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaType; /** * Common base class for REST requests. diff --git a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ResponseBuilder.java b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ResponseBuilder.java index 07a8764eea4..b9fb247b637 100644 --- a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ResponseBuilder.java +++ b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/ResponseBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,7 +35,7 @@ public interface ResponseBuilder, T, X> exten * @param status HTTP status * @return updated builder */ - B status(Http.ResponseStatus status); + B status(Http.Status status); /** * Configure the HTTP headers returned by the API call. diff --git a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/RestApi.java b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/RestApi.java index bd7583a3788..1acc414b121 100644 --- a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/RestApi.java +++ b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/RestApi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; -import io.helidon.common.http.Http.RequestMethod; +import io.helidon.common.http.Http.Method; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; import io.helidon.config.Config; @@ -160,7 +160,7 @@ default Single delete(String path, * * @return future with metadata if successful. future with error otherwise */ - Single invoke(RequestMethod method, + Single invoke(Method method, String path, ApiRequest request, ApiResponse.Builder responseBuilder); @@ -177,14 +177,14 @@ Single invoke(RequestMethod method, * * @return future with entity and metadata if successful, future with error otherwise */ - Single invokeWithResponse(RequestMethod method, + Single invokeWithResponse(Method method, String path, ApiRequest request, ApiEntityResponse.Builder responseBuilder); /** * The request media type should be provided in request, falls back to - * {@link io.helidon.common.http.MediaType#APPLICATION_OCTET_STREAM}. + * {@link io.helidon.common.media.type.MediaTypes#APPLICATION_OCTET_STREAM}. * * @param method method to invoke * @param path path to invoke @@ -195,7 +195,7 @@ Single invokeWithResponse(RequestMethod method, * * @return future with the response or error */ - Single invokeBytesRequest(Http.RequestMethod method, + Single invokeBytesRequest(Http.Method method, String path, ApiRequest request, Flow.Publisher byteRequest, @@ -205,7 +205,7 @@ Single invokeBytesRequest(Http.RequestMethod method, * Invoke API call that is expected to return bytes as a publisher. * * The accepted media type must be provided in request, falls back to - * {@link io.helidon.common.http.MediaType#APPLICATION_OCTET_STREAM}. + * {@link io.helidon.common.media.type.MediaTypes#APPLICATION_OCTET_STREAM}. * * @param method method to invoke * @param path path to invoke @@ -218,7 +218,7 @@ Single invokeBytesRequest(Http.RequestMethod method, * @return future with the response */ > - Single invokePublisherResponse(RequestMethod method, + Single invokePublisherResponse(Method method, String path, ApiRequest request, ApiOptionalResponse.BuilderBase, R> responseBuilder); @@ -227,10 +227,10 @@ Single invokePublisherResponse(RequestMethod method, * Invoke API call that is expected to return bytes. * This method collects all bytes in memory, so it cannot be used for large data. * See - * {@link #invokePublisherResponse(io.helidon.common.http.Http.RequestMethod, String, ApiRequest, io.helidon.integrations.common.rest.ApiOptionalResponse.BuilderBase)}. + * {@link #invokePublisherResponse(io.helidon.common.http.Http.Method, String, ApiRequest, io.helidon.integrations.common.rest.ApiOptionalResponse.BuilderBase)}. * * The accepted media type must be provided in request, falls back to - * {@link io.helidon.common.http.MediaType#APPLICATION_OCTET_STREAM}. + * {@link io.helidon.common.media.type.MediaTypes#APPLICATION_OCTET_STREAM}. * * @param method method to invoke * @param path path to invoke @@ -243,7 +243,7 @@ Single invokePublisherResponse(RequestMethod method, * @return future with the response */ > - Single invokeBytesResponse(RequestMethod method, + Single invokeBytesResponse(Method method, String path, ApiRequest request, ApiOptionalResponse.BuilderBase responseBuilder); @@ -264,7 +264,7 @@ Single invokeBytesResponse(RequestMethod method, * */ > - Single invokeOptional(RequestMethod method, + Single invokeOptional(Method method, String path, ApiRequest request, ApiOptionalResponse.BuilderBase responseBuilder); diff --git a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/RestApiBase.java b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/RestApiBase.java index 69dd80e15ab..a19bf1b4c13 100644 --- a/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/RestApiBase.java +++ b/integrations/common/rest/src/main/java/io/helidon/integrations/common/rest/RestApiBase.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import io.helidon.common.context.Contexts; import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Collector; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; @@ -73,7 +73,7 @@ protected RestApiBase(RestApi.Builder builder) { @Override public Single - invoke(Http.RequestMethod method, + invoke(Http.Method method, String path, ApiRequest request, ApiResponse.Builder responseBuilder) { @@ -87,7 +87,7 @@ protected RestApiBase(RestApi.Builder builder) { @Override public Single - invokeWithResponse(Http.RequestMethod method, + invokeWithResponse(Http.Method method, String path, ApiRequest request, ApiEntityResponse.Builder responseBuilder) { @@ -102,7 +102,7 @@ protected RestApiBase(RestApi.Builder builder) { } @Override - public Single invokeBytesRequest(Http.RequestMethod method, + public Single invokeBytesRequest(Http.Method method, String path, ApiRequest request, Flow.Publisher byteRequest, @@ -130,7 +130,7 @@ public Single invokeBytesRequest(Http.RequestMethod m @Override public > - Single invokePublisherResponse(Http.RequestMethod method, + Single invokePublisherResponse(Http.Method method, String path, ApiRequest request, ApiOptionalResponse.BuilderBase, R> responseBuilder) { @@ -139,7 +139,7 @@ Single invokePublisherResponse(Http.RequestMethod method, LOGGER.finest(() -> requestId + ": Invoking " + method + " on path " + path + " with publisher response"); - request.responseMediaType(request.responseMediaType().orElse(MediaType.WILDCARD)); + request.responseMediaType(request.responseMediaType().orElse(MediaTypes.WILDCARD)); Supplier> responseSupplier = responseSupplier(method, path, request, requestId); @@ -149,7 +149,7 @@ Single invokePublisherResponse(Http.RequestMethod method, @Override public > - Single invokeBytesResponse(Http.RequestMethod method, + Single invokeBytesResponse(Http.Method method, String path, ApiRequest request, ApiOptionalResponse.BuilderBase responseBuilder) { @@ -158,7 +158,7 @@ Single invokeBytesResponse(Http.RequestMethod method, LOGGER.finest(() -> requestId + ": Invoking " + method + " on path " + path + " with bytes response"); - request.responseMediaType(request.responseMediaType().orElse(MediaType.WILDCARD)); + request.responseMediaType(request.responseMediaType().orElse(MediaTypes.WILDCARD)); Supplier> responseSupplier = responseSupplier(method, path, request, requestId); @@ -168,7 +168,7 @@ Single invokeBytesResponse(Http.RequestMethod method, @Override public > - Single invokeOptional(Http.RequestMethod method, + Single invokeOptional(Http.Method method, String path, ApiRequest request, ApiOptionalResponse.BuilderBase responseBuilder) { @@ -196,7 +196,7 @@ Single invokeOptional(Http.RequestMethod method, * @param requestId request ID to use for this request * @return supplier of response that is used with fault tolerance */ - protected Supplier> responseSupplier(Http.RequestMethod method, + protected Supplier> responseSupplier(Http.Method method, String path, ApiRequest request, String requestId) { @@ -230,7 +230,7 @@ protected Supplier> responseSupplier(Http.RequestMetho protected void addQueryParams(WebClientRequestBuilder requestBuilder, String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId) { request.queryParams() .forEach((name, values) -> { @@ -256,10 +256,10 @@ protected void addQueryParams(WebClientRequestBuilder requestBuilder, protected void addHeaders(WebClientRequestBuilder requestBuilder, String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId) { WebClientRequestHeaders headers = requestBuilder.headers(); - request.headers().forEach(headers::add); + request.headers().forEach((key, value) -> headers.set(Http.Header.create(key), value)); } /** @@ -282,15 +282,15 @@ protected void addHeaders(WebClientRequestBuilder requestBuilder, protected > Single handleBytesResponse(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, ApiOptionalResponse.BuilderBase responseBuilder) { - Http.ResponseStatus status = response.status(); - boolean success = (Http.Status.Family.of(status.code()) == Http.ResponseStatus.Family.SUCCESSFUL) + Http.Status status = response.status(); + boolean success = (Http.Status.Family.of(status.code()) == Http.Status.Family.SUCCESSFUL) || isSuccess(path, request, method, requestId, status); - boolean isEntityExpected = (Http.Status.Family.of(status.code()) == Http.ResponseStatus.Family.SUCCESSFUL) + boolean isEntityExpected = (Http.Status.Family.of(status.code()) == Http.Status.Family.SUCCESSFUL) || isEntityExpected(path, request, method, requestId, status); if (success) { @@ -343,16 +343,16 @@ public byte[] value() { protected > Single handlePublisherResponse(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, ApiOptionalResponse.BuilderBase, R> responseBuilder) { - Http.ResponseStatus status = response.status(); + Http.Status status = response.status(); - boolean success = (Http.Status.Family.of(status.code()) == Http.ResponseStatus.Family.SUCCESSFUL) + boolean success = (Http.Status.Family.of(status.code()) == Http.Status.Family.SUCCESSFUL) || isSuccess(path, request, method, requestId, status); - boolean isEntityExpected = (Http.Status.Family.of(status.code()) == Http.ResponseStatus.Family.SUCCESSFUL) + boolean isEntityExpected = (Http.Status.Family.of(status.code()) == Http.Status.Family.SUCCESSFUL) || isEntityExpected(path, request, method, requestId, status); if (success) { @@ -383,9 +383,9 @@ Single handlePublisherResponse(String path, */ protected boolean isSuccess(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, - Http.ResponseStatus status) { + Http.Status status) { if (status == Http.Status.NOT_FOUND_404) { return true; } @@ -393,7 +393,7 @@ protected boolean isSuccess(String path, return true; } - Http.ResponseStatus.Family family = Http.Status.Family.of(status.code()); + Http.Status.Family family = Http.Status.Family.of(status.code()); switch (family) { // we do have not modified handled, we also follow redirects - so this is an error case REDIRECTION: @@ -409,7 +409,7 @@ protected boolean isSuccess(String path, /** * This method is only called for methods that return an optional entity. * If a method (such as - * {@link RestApi#invokeWithResponse(io.helidon.common.http.Http.RequestMethod, String, ApiRequest, io.helidon.integrations.common.rest.ApiEntityResponse.Builder)}) + * {@link RestApi#invokeWithResponse(io.helidon.common.http.Http.Method, String, ApiRequest, io.helidon.integrations.common.rest.ApiEntityResponse.Builder)}) * receives a status that would not yield an entity (such as 404), it is automatically an error. * Also this method is never called for codes in the success family. * @@ -423,10 +423,10 @@ protected boolean isSuccess(String path, */ protected boolean isEntityExpected(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, - Http.ResponseStatus status) { - Http.ResponseStatus.Family family = Http.Status.Family.of(status.code()); + Http.Status status) { + Http.Status.Family family = Http.Status.Family.of(status.code()); switch (family) { // we do have not modified handled, we also follow redirects - so this is an error case REDIRECTION: @@ -459,16 +459,16 @@ protected boolean isEntityExpected(String path, protected > Single handleOptionalJsonResponse(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, ApiOptionalResponse.BuilderBase responseBuilder) { - Http.ResponseStatus status = response.status(); + Http.Status status = response.status(); - boolean success = (Http.Status.Family.of(status.code()) == Http.ResponseStatus.Family.SUCCESSFUL) + boolean success = (Http.Status.Family.of(status.code()) == Http.Status.Family.SUCCESSFUL) || isSuccess(path, request, method, requestId, status); - boolean isEntityExpected = (Http.Status.Family.of(status.code()) == Http.ResponseStatus.Family.SUCCESSFUL) + boolean isEntityExpected = (Http.Status.Family.of(status.code()) == Http.Status.Family.SUCCESSFUL) || isEntityExpected(path, request, method, requestId, status); if (success) { @@ -509,7 +509,7 @@ Single handleOptionalJsonResponse(String path, */ protected Single emptyResponse(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, ResponseBuilder responseBuilder) { @@ -538,7 +538,7 @@ protected Single emptyResponse(String path, protected T jsonOkResponse(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, JsonObject json, @@ -569,14 +569,14 @@ T jsonOkResponse(String path, protected Single handleJsonResponse(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, ApiEntityResponse.Builder responseBuilder) { - Http.ResponseStatus status = response.status(); + Http.Status status = response.status(); - if (Http.Status.Family.of(status.code()) == Http.ResponseStatus.Family.SUCCESSFUL) { + if (Http.Status.Family.of(status.code()) == Http.Status.Family.SUCCESSFUL) { LOGGER.finest(() -> requestId + ": " + method + " on path " + path + " returned " + status); return response.content() .as(JsonObject.class) @@ -604,13 +604,13 @@ T jsonOkResponse(String path, */ protected Single handleResponse(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, ApiResponse.Builder responseBuilder) { - Http.ResponseStatus status = response.status(); + Http.Status status = response.status(); - boolean success = (Http.Status.Family.of(status.code()) == Http.ResponseStatus.Family.SUCCESSFUL); + boolean success = (Http.Status.Family.of(status.code()) == Http.Status.Family.SUCCESSFUL); if (success) { LOGGER.finest(() -> requestId + ": " + method + " on path " + path + " returned " + status); @@ -637,7 +637,7 @@ protected Single handleResponse(String path, */ protected Single errorResponse(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response) { @@ -686,7 +686,7 @@ protected Single errorResponse(String path, */ protected Throwable readErrorFailedEntity(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, Throwable throwable) { @@ -714,7 +714,7 @@ protected Throwable readErrorFailedEntity(String path, */ protected Throwable readError(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, String entity) { @@ -742,7 +742,7 @@ protected Throwable readError(String path, */ protected Throwable readError(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response) { @@ -771,7 +771,7 @@ protected Throwable readError(String path, */ protected Throwable readError(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, JsonObject errorObject) { @@ -804,7 +804,7 @@ protected Throwable readError(String path, */ protected Single noEntityOkResponse(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, ApiResponse.Builder responseBuilder) { @@ -830,13 +830,13 @@ protected Single noEntityOkResponse(String path, */ protected Supplier> requestJsonPayload(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientRequestBuilder requestBuilder, JsonObject jsonObject) { - requestBuilder.accept(request.responseMediaType().orElse(MediaType.APPLICATION_JSON)); - requestBuilder.contentType(request.requestMediaType().orElse(MediaType.APPLICATION_JSON)); + requestBuilder.accept(request.responseMediaType().orElse(MediaTypes.APPLICATION_JSON)); + requestBuilder.contentType(request.requestMediaType().orElse(MediaTypes.APPLICATION_JSON)); AtomicBoolean updated = new AtomicBoolean(); return () -> { // we should only update request builder once - if a retry is done, it should not be reset @@ -870,12 +870,12 @@ protected Supplier> requestJsonPayload(String path, */ protected Supplier> requestBytesPayload(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientRequestBuilder requestBuilder, Flow.Publisher publisher) { - requestBuilder.accept(request.responseMediaType().orElse(MediaType.APPLICATION_JSON)); - requestBuilder.contentType(request.requestMediaType().orElse(MediaType.APPLICATION_OCTET_STREAM)); + requestBuilder.accept(request.responseMediaType().orElse(MediaTypes.APPLICATION_JSON)); + requestBuilder.contentType(request.requestMediaType().orElse(MediaTypes.APPLICATION_OCTET_STREAM)); AtomicBoolean updated = new AtomicBoolean(); return () -> { @@ -901,7 +901,7 @@ protected Supplier> requestBytesPayload(String path, */ protected Supplier> requestPayload(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientRequestBuilder requestBuilder) { @@ -935,7 +935,7 @@ protected Supplier> requestPayload(String path, protected Single updateRequestBuilder(WebClientRequestBuilder requestBuilder, String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId) { return updateRequestBuilderCommon(requestBuilder, path, request, method, requestId); } @@ -954,7 +954,7 @@ protected Single updateRequestBuilder(WebClientRequestB protected Single updateRequestBuilderBytesPayload(WebClientRequestBuilder requestBuilder, String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId) { return updateRequestBuilderCommon(requestBuilder, path, request, method, requestId); } @@ -974,7 +974,7 @@ protected Single updateRequestBuilderBytesPayload(WebCl protected Single updateRequestBuilder(WebClientRequestBuilder requestBuilder, String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, JsonObject jsonObject) { return updateRequestBuilderCommon(requestBuilder, path, request, method, requestId); @@ -993,7 +993,7 @@ protected Single updateRequestBuilder(WebClientRequestB protected Single updateRequestBuilderCommon(WebClientRequestBuilder requestBuilder, String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId) { return Single.just(requestBuilder); } diff --git a/integrations/micrometer/cdi/src/test/resources/logging.properties b/integrations/micrometer/cdi/src/test/resources/logging.properties index 03baed28ef5..67f06f7215c 100644 --- a/integrations/micrometer/cdi/src/test/resources/logging.properties +++ b/integrations/micrometer/cdi/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler,java.util.logging.FileHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler,java.util.logging.FileHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n @@ -37,7 +37,7 @@ java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$ #java.util.logging.FileHandler.level=FINEST #java.util.logging.FileHandler.formatter=java.util.logging.SimpleFormatter -#io.helidon.common.HelidonConsoleHandler.level=FINEST +#io.helidon.logging.jul.HelidonConsoleHandler.level=FINEST #org.jboss.weld.Bootstrap.level = FINEST #org.jboss.weld.Bean.level=FINEST diff --git a/integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerPrometheusRegistrySupport.java b/integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerPrometheusRegistrySupport.java index 67543c6f6bb..8b4e816debb 100644 --- a/integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerPrometheusRegistrySupport.java +++ b/integrations/micrometer/micrometer/src/main/java/io/helidon/integrations/micrometer/MicrometerPrometheusRegistrySupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import java.util.function.Function; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.ConfigValue; import io.helidon.webserver.Handler; @@ -75,7 +75,7 @@ public Function> requestToHandlerFn(MeterRegist */ return (ServerRequest req) -> { if (req.headers() - .bestAccepted(MediaType.TEXT_PLAIN).isPresent() + .bestAccepted(MediaTypes.TEXT_PLAIN).isPresent() || req.queryParams() .first("type") .orElse("") @@ -104,25 +104,21 @@ static PrometheusHandler create(MeterRegistry registry) { @Override public void accept(ServerRequest req, ServerResponse res) { - res.headers().contentType(MediaType.TEXT_PLAIN); - switch (Http.Method.valueOf(req.method() - .name())) { - case GET: - res.send(registry.scrape()); - break; - case OPTIONS: - StringWriter writer = new StringWriter(); - try { - metadata(writer, registry); - res.send(writer.toString()); - } catch (IOException e) { - res.status(Http.Status.INTERNAL_SERVER_ERROR_500) - .send(e); - } - break; - default: - res.status(Http.Status.NOT_IMPLEMENTED_501) - .send(); + res.headers().contentType(MediaTypes.TEXT_PLAIN); + if (req.method() == Http.Method.GET) { + res.send(registry.scrape()); + } else if (req.method() == Http.Method.OPTIONS) { + StringWriter writer = new StringWriter(); + try { + metadata(writer, registry); + res.send(writer.toString()); + } catch (IOException e) { + res.status(Http.Status.INTERNAL_SERVER_ERROR_500) + .send(e); + } + } else { + res.status(Http.Status.NOT_IMPLEMENTED_501) + .send(); } } } diff --git a/integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerEndpointTests.java b/integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerEndpointTests.java index 2723f9c4599..0941dd1e472 100644 --- a/integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerEndpointTests.java +++ b/integrations/micrometer/micrometer/src/test/java/io/helidon/integrations/micrometer/MicrometerEndpointTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import java.util.function.Supplier; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.ConfigSources; import io.helidon.webclient.WebClient; @@ -39,7 +39,7 @@ public class MicrometerEndpointTests { @Test public void testDefaultEndpoint() throws ExecutionException, InterruptedException { - runTest(MicrometerSupport.DEFAULT_CONTEXT, () -> MicrometerSupport.create()); + runTest(MicrometerSupport.DEFAULT_CONTEXT, MicrometerSupport::create); } @Test @@ -87,7 +87,7 @@ private static void runTest(String contextForRequest, Supplier { // If there is no media type, assume text/plain which means, for us, Prometheus. - if (req.headers().acceptedTypes().contains(MediaType.TEXT_PLAIN) + if (req.headers().bestAccepted(MediaTypes.TEXT_PLAIN).isPresent() || req.queryParams().first("type").orElse("").equals("prometheus")) { return Optional.of(MicrometerPrometheusRegistrySupport.PrometheusHandler.create(registry)); } else { @@ -86,7 +86,7 @@ public void checkViaMediaType() throws ExecutionException, InterruptedException counter1.increment(3); gauge1.set(4); WebClientResponse response = webClient.get() - .accept(MediaType.TEXT_PLAIN) + .accept(MediaTypes.TEXT_PLAIN) .path("/micrometer") .request() .get(); @@ -103,7 +103,7 @@ public void checkViaQueryParam() throws ExecutionException, InterruptedException counter1.increment(3); gauge1.set(4); WebClientResponse response = webClient.get() - .accept(MediaType.builder().type(MediaType.TEXT_PLAIN.type()).subtype("special").build()) + .accept(MediaTypes.create(MediaTypes.TEXT_PLAIN.type(), "special")) .path("/micrometer") .queryParam("type", "prometheus") .request() @@ -117,7 +117,7 @@ public void checkViaQueryParam() throws ExecutionException, InterruptedException @Test public void checkNoMatch() throws ExecutionException, InterruptedException { WebClientResponse response = webClient.get() - .accept(MediaType.builder().type(MediaType.TEXT_PLAIN.type()).subtype("special").build()) + .accept(MediaTypes.create(MediaTypes.TEXT_PLAIN.type(), "special")) .path("/micrometer") .request() .get(); diff --git a/integrations/micronaut/pom.xml b/integrations/micronaut/pom.xml index 9668b884eb9..4a67297d3b4 100644 --- a/integrations/micronaut/pom.xml +++ b/integrations/micronaut/pom.xml @@ -34,6 +34,9 @@ cdi-processor cdi - data + + diff --git a/integrations/pom.xml b/integrations/pom.xml index 29d98c23f04..0b93ee26446 100644 --- a/integrations/pom.xml +++ b/integrations/pom.xml @@ -49,7 +49,6 @@ micronaut neo4j micrometer - oci vault microstream diff --git a/integrations/vault/auths/approle/src/main/java/io/helidon/integrations/vault/auths/approle/AppRoleRestApi.java b/integrations/vault/auths/approle/src/main/java/io/helidon/integrations/vault/auths/approle/AppRoleRestApi.java index 02126318de3..4e81f5d38eb 100644 --- a/integrations/vault/auths/approle/src/main/java/io/helidon/integrations/vault/auths/approle/AppRoleRestApi.java +++ b/integrations/vault/auths/approle/src/main/java/io/helidon/integrations/vault/auths/approle/AppRoleRestApi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -52,7 +52,7 @@ static Builder appRoleBuilder() { protected Single updateRequestBuilderCommon(WebClientRequestBuilder requestBuilder, String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId) { VaultTokenBase currentToken = this.currentToken.get(); diff --git a/integrations/vault/auths/common/src/main/java/io/helidon/integrations/vault/auths/common/VaultRestApi.java b/integrations/vault/auths/common/src/main/java/io/helidon/integrations/vault/auths/common/VaultRestApi.java index 3ba7b3a37e6..53d540e72a7 100644 --- a/integrations/vault/auths/common/src/main/java/io/helidon/integrations/vault/auths/common/VaultRestApi.java +++ b/integrations/vault/auths/common/src/main/java/io/helidon/integrations/vault/auths/common/VaultRestApi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -58,7 +58,7 @@ public static Builder builder() { @Override protected Single emptyResponse(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, ResponseBuilder responseBuilder) { @@ -93,7 +93,7 @@ protected Single emptyResponse(String path, @Override protected Throwable readError(String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId, WebClientResponse response, JsonObject entity) { diff --git a/integrations/vault/auths/k8s/src/main/java/io/helidon/integrations/vault/auths/k8s/K8sRestApi.java b/integrations/vault/auths/k8s/src/main/java/io/helidon/integrations/vault/auths/k8s/K8sRestApi.java index 78209b46fd3..f5cb00be2c5 100644 --- a/integrations/vault/auths/k8s/src/main/java/io/helidon/integrations/vault/auths/k8s/K8sRestApi.java +++ b/integrations/vault/auths/k8s/src/main/java/io/helidon/integrations/vault/auths/k8s/K8sRestApi.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -49,7 +49,7 @@ static Builder k8sBuilder() { protected Single updateRequestBuilderCommon(WebClientRequestBuilder requestBuilder, String path, ApiRequest request, - Http.RequestMethod method, + Http.Method method, String requestId) { VaultTokenBase k8sToken = currentToken.get(); diff --git a/integrations/vault/secrets/database/src/main/java/module-info.java b/integrations/vault/secrets/database/src/main/java/module-info.java index 09195f9b994..373c245ef9c 100644 --- a/integrations/vault/secrets/database/src/main/java/module-info.java +++ b/integrations/vault/secrets/database/src/main/java/module-info.java @@ -23,6 +23,7 @@ requires transitive io.helidon.common.reactive; requires io.helidon.integrations.vault; requires io.helidon.integrations.common.rest; + requires transitive io.helidon.common.reactive; exports io.helidon.integrations.vault.secrets.database; diff --git a/integrations/vault/sys/sys/src/main/java/module-info.java b/integrations/vault/sys/sys/src/main/java/module-info.java index a2e3e23e71c..126f795cfd4 100644 --- a/integrations/vault/sys/sys/src/main/java/module-info.java +++ b/integrations/vault/sys/sys/src/main/java/module-info.java @@ -27,6 +27,7 @@ requires io.helidon.integrations.vault; requires io.helidon.integrations.common.rest; requires io.helidon.integrations.vault.auths.common; + requires transitive io.helidon.common.reactive; exports io.helidon.integrations.vault.sys; diff --git a/integrations/vault/vault/src/main/java/io/helidon/integrations/vault/Vault.java b/integrations/vault/vault/src/main/java/io/helidon/integrations/vault/Vault.java index 3a5f8251245..7cd152db001 100644 --- a/integrations/vault/vault/src/main/java/io/helidon/integrations/vault/Vault.java +++ b/integrations/vault/vault/src/main/java/io/helidon/integrations/vault/Vault.java @@ -41,7 +41,7 @@ public interface Vault { /** * HTTP {@code LIST} method used by several Vault engines. */ - Http.RequestMethod LIST = Http.RequestMethod.create("LIST"); + Http.Method LIST = Http.Method.create("LIST"); /** * Fluent API builder to construct new instances. diff --git a/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonEntity.java b/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonEntity.java index fe38d292941..b08244cbe2d 100644 --- a/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonEntity.java +++ b/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonEntity.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.IoMulti; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.OutputStreamMulti; @@ -157,7 +157,7 @@ public Flow.Publisher write( Single content, GenericType type, MessageBodyWriterContext context) { - context.contentType(MediaType.APPLICATION_OCTET_STREAM); + context.contentType(MediaTypes.APPLICATION_OCTET_STREAM); return content.flatMap(new ByteArrayOutputStreamToChunks()); } diff --git a/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonStructures.java b/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonStructures.java index f9ac1134edf..0fe76e7735d 100644 --- a/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonStructures.java +++ b/jersey/connector/src/main/java/io/helidon/jersey/connector/HelidonStructures.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,8 +27,8 @@ import javax.net.ssl.SSLContext; import io.helidon.common.http.Headers; +import io.helidon.common.http.HeadersWritable; import io.helidon.common.http.Http; -import io.helidon.common.http.ReadOnlyParameters; import io.helidon.config.Config; import io.helidon.media.common.DefaultMediaSupport; import io.helidon.media.common.MessageBodyReader; @@ -51,7 +51,9 @@ private HelidonStructures() { } static Headers createHeaders(Map> data) { - return new ReadOnlyHeaders(data); + HeadersWritable headers = HeadersWritable.create(); + data.forEach((key, value) -> headers.set(Http.Header.create(key), value)); + return headers; } static MessageBodyReader createInputStreamBodyReader() { @@ -87,7 +89,7 @@ static Optional createSSL(SSLContext context) { } static boolean hasEntity(WebClientResponse webClientResponse) { - final ReadOnlyParameters headers = webClientResponse.content().readerContext().headers(); + final Headers headers = webClientResponse.content().readerContext().headers(); final Optional contentLenth = headers.first(Http.Header.CONTENT_LENGTH); final Optional encoding = headers.first(Http.Header.TRANSFER_ENCODING); @@ -95,12 +97,6 @@ static boolean hasEntity(WebClientResponse webClientResponse) { || (encoding.isPresent() && encoding.get().equals(HttpHeaderValues.CHUNKED.toString()))); } - private static class ReadOnlyHeaders extends ReadOnlyParameters implements Headers { - ReadOnlyHeaders(Map> data) { - super(data); - } - } - private static class ProxyBuilder { private static Optional createProxy(Configuration config) { final Object proxyUri = config.getProperty(ClientProperties.PROXY_URI); From 33c867123bfce01e998380fbd0da8d68c90e90e3 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:54:39 +0200 Subject: [PATCH 23/54] Examples refactored to new common. --- .../src/main/resources/logging.properties | 4 +- .../io/helidon/examples/cors/MainTest.java | 65 ++++++++++--------- .../src/main/resources/logging.properties | 6 +- .../src/main/resources/logging.properties | 6 +- .../dbclient/pokemons/PokemonService.java | 6 +- .../src/main/resources/logging.properties | 6 +- .../src/main/resources/logging.properties | 4 +- .../io/helidon/service/employee/MainTest.java | 6 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../security/outbound/SecureServer.java | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../mp/src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- examples/integrations/pom.xml | 3 + .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- examples/jbatch/pom.xml | 9 +++ .../src/main/resources/logging.properties | 2 +- examples/media/multipart/pom.xml | 9 +++ .../examples/media/multipart/FileService.java | 10 +-- .../examples/media/multipart/Main.java | 5 +- .../media/multipart/FileServiceTest.java | 21 +++--- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../mp/src/main/resources/logging.properties | 4 +- .../se/src/main/resources/logging.properties | 4 +- .../kpi/src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 2 +- .../src/main/resources/logging.properties | 4 +- .../microprofile/examples/cors/TestCORS.java | 57 ++++++++-------- .../src/test/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 6 +- .../src/main/resources/logging.properties | 6 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../lra/src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/test/resources/logging.properties | 4 +- .../tls/src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../io/helidon/examples/openapi/MainTest.java | 6 +- .../src/main/resources/logging.properties | 4 +- .../basic/BasicExampleBuilderMain.java | 6 +- .../basic/BasicExampleConfigMain.java | 6 +- .../src/main/resources/logging.properties | 4 +- .../webserver/basic/BasicExampleTest.java | 4 +- .../examples/google/GoogleBuilderMain.java | 6 +- .../examples/google/GoogleConfigMain.java | 6 +- .../src/main/resources/logging.properties | 4 +- .../examples/idcs/IdcsBuilderMain.java | 6 +- .../security/examples/idcs/IdcsMain.java | 6 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../outbound/OutboundOverrideJwtExample.java | 5 +- .../src/main/resources/application.yaml | 34 +++++----- .../src/main/resources/logging.properties | 4 +- .../digest/DigestExampleBuilderMain.java | 16 ++--- .../digest/DigestExampleConfigMain.java | 6 +- .../src/main/resources/logging.properties | 4 +- .../SignatureExampleBuilderMain.java | 6 +- .../SignatureExampleConfigMain.java | 6 +- .../signatures/SignatureExampleUtil.java | 6 +- .../src/main/resources/logging.properties | 4 +- .../demo/todos/backend/BackendTests.java | 12 ++-- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../webclient/standalone/ClientMain.java | 2 +- .../webserver/examples/basics/Main.java | 18 ++--- .../webserver/examples/basics/NameReader.java | 8 +-- .../webserver/examples/basics/MainTest.java | 25 +++---- .../examples/comments/CommentsService.java | 6 +- .../webserver/examples/comments/Main.java | 6 +- .../comments/CommentsServiceTest.java | 6 +- .../webserver/examples/comments/MainTest.java | 6 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../src/main/resources/logging.properties | 4 +- .../examples/staticcontent/Main.java | 5 +- .../examples/streaming/StreamingService.java | 4 +- examples/webserver/threadpool/pom.xml | 9 +++ .../src/main/resources/logging.properties | 4 +- .../tls/src/main/resources/logging.properties | 4 +- .../examples/tutorial/CommentService.java | 57 ++++++++++++---- .../webserver/examples/tutorial/Main.java | 6 +- .../examples/tutorial/UpperXFilter.java | 7 +- .../examples/tutorial/CommentServiceTest.java | 8 +-- .../tutorial/user/UserFilterTest.java | 5 +- .../src/main/resources/logging.properties | 4 +- 117 files changed, 448 insertions(+), 364 deletions(-) diff --git a/examples/cors/src/main/resources/logging.properties b/examples/cors/src/main/resources/logging.properties index bb63a07aa78..b971da0120f 100644 --- a/examples/cors/src/main/resources/logging.properties +++ b/examples/cors/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/cors/src/test/java/io/helidon/examples/cors/MainTest.java b/examples/cors/src/test/java/io/helidon/examples/cors/MainTest.java index 8a205956462..2ca6a3f6562 100644 --- a/examples/cors/src/test/java/io/helidon/examples/cors/MainTest.java +++ b/examples/cors/src/test/java/io/helidon/examples/cors/MainTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +21,14 @@ import java.util.concurrent.TimeUnit; import io.helidon.common.http.Headers; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; +import io.helidon.webclient.WebClientRequestHeaders; import io.helidon.webclient.WebClientResponse; +import io.helidon.webclient.WebClientResponseHeaders; import io.helidon.webserver.WebServer; import io.helidon.webserver.cors.CrossOriginConfig; @@ -39,6 +41,11 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_METHODS; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN; +import static io.helidon.common.http.Http.Header.ACCESS_CONTROL_REQUEST_METHOD; +import static io.helidon.common.http.Http.Header.HOST; +import static io.helidon.common.http.Http.Header.ORIGIN; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertTrue; @@ -113,16 +120,16 @@ public void testHelloWorld() { @Test void testAnonymousGreetWithCors() { WebClientRequestBuilder builder = webClient.get(); - Headers headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); + WebClientRequestHeaders headers = builder.headers(); + headers.set(ORIGIN, "http://foo.com"); + headers.set(HOST, "here.com"); WebClientResponse r = getResponse("/greet", builder); assertEquals(200, r.status().code(), "HTTP response"); String payload = fromPayload(r).getMessage(); assertTrue(payload.contains("Hola World"), "HTTP response payload was " + payload); - headers = r.headers(); - Optional allowOrigin = headers.value(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + Headers responseHeaders = r.headers(); + Optional allowOrigin = responseHeaders.value(ACCESS_CONTROL_ALLOW_ORIGIN); assertTrue(allowOrigin.isPresent(), "Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN + " is absent"); assertEquals(allowOrigin.get(), "*"); @@ -135,21 +142,21 @@ void testGreetingChangeWithCors() { // Send the pre-flight request and check the response. WebClientRequestBuilder builder = webClient.options(); - Headers headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); - headers.add("Access-Control-Request-Method", "PUT"); + WebClientRequestHeaders headers = builder.headers(); + headers.set(ORIGIN, "http://foo.com"); + headers.set(HOST, "here.com"); + headers.set(ACCESS_CONTROL_REQUEST_METHOD, "PUT"); WebClientResponse r = builder.path("/greet/greeting") .submit() .await(); - Headers preflightResponseHeaders = r.headers(); - List allowMethods = preflightResponseHeaders.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS); + Headers responseHeaders = r.headers(); + List allowMethods = responseHeaders.values(ACCESS_CONTROL_ALLOW_METHODS); assertFalse(allowMethods.isEmpty(), "pre-flight response does not include " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS); assertTrue(allowMethods.contains("PUT")); - List allowOrigins = preflightResponseHeaders.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + List allowOrigins = responseHeaders.values(ACCESS_CONTROL_ALLOW_ORIGIN); assertFalse(allowOrigins.isEmpty(), "pre-flight response does not include " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); assertTrue(allowOrigins.contains("http://foo.com"), "Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN @@ -159,14 +166,14 @@ void testGreetingChangeWithCors() { builder = webClient.put(); headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); - headers.addAll(preflightResponseHeaders); + headers.set(ORIGIN, "http://foo.com"); + headers.set(HOST, "here.com"); + headers.addAll(responseHeaders); r = putResponse("/greet/greeting", new GreetingMessage("Cheers"), builder); assertEquals(204, r.status().code(), "HTTP response3"); - headers = r.headers(); - allowOrigins = headers.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + responseHeaders = r.headers(); + allowOrigins = responseHeaders.values(ACCESS_CONTROL_ALLOW_ORIGIN); assertFalse(allowOrigins.isEmpty(), "Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN + " has no value(s)"); assertTrue(allowOrigins.contains("http://foo.com"), "Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN @@ -177,15 +184,15 @@ void testGreetingChangeWithCors() { @Test void testNamedGreetWithCors() { WebClientRequestBuilder builder = webClient.get(); - Headers headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); + WebClientRequestHeaders headers = builder.headers(); + headers.set(ORIGIN, "http://foo.com"); + headers.set(HOST, "here.com"); WebClientResponse r = getResponse("/greet/Maria", builder); assertEquals(200, r.status().code(), "HTTP response"); assertTrue(fromPayload(r).getMessage().contains("Cheers Maria")); - headers = r.headers(); - Optional allowOrigin = headers.value(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + WebClientResponseHeaders responseHeaders = r.headers(); + Optional allowOrigin = responseHeaders.value(ACCESS_CONTROL_ALLOW_ORIGIN); assertTrue(allowOrigin.isPresent(), "Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN + " is absent"); assertEquals(allowOrigin.get(), "*"); @@ -195,9 +202,9 @@ void testNamedGreetWithCors() { @Test void testGreetingChangeWithCorsAndOtherOrigin() { WebClientRequestBuilder builder = webClient.put(); - Headers headers = builder.headers(); - headers.add("Origin", "http://other.com"); - headers.add("Host", "here.com"); + WebClientRequestHeaders headers = builder.headers(); + headers.set(ORIGIN, "http://other.com"); + headers.set(HOST, "here.com"); WebClientResponse r = putResponse("/greet/greeting", new GreetingMessage("Ahoy"), builder); // Result depends on whether we are using overrides or not. @@ -211,7 +218,7 @@ private static WebClientResponse getResponse(String path) { private static WebClientResponse getResponse(String path, WebClientRequestBuilder builder) { return builder - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path(path) .submit() .await(); @@ -223,7 +230,7 @@ private static WebClientResponse putResponse(String path, GreetingMessage payloa private static WebClientResponse putResponse(String path, GreetingMessage payload, WebClientRequestBuilder builder) { return builder - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path(path) .submit(payload.forRest()) .await(); diff --git a/examples/dbclient/jdbc/src/main/resources/logging.properties b/examples/dbclient/jdbc/src/main/resources/logging.properties index a3b94c39c5f..3b6512d0017 100644 --- a/examples/dbclient/jdbc/src/main/resources/logging.properties +++ b/examples/dbclient/jdbc/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2020 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,14 +18,14 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # Global default logging level. Can be overriden by specific handlers and loggers .level=INFO # Helidon Web Server has a custom log formatter that extends SimpleFormatter. # It replaces "!thread!" with the current thread name -io.helidon.common.HelidonConsoleHandler.level=ALL +io.helidon.logging.jul.HelidonConsoleHandler.level=ALL java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #Component specific log levels diff --git a/examples/dbclient/mongodb/src/main/resources/logging.properties b/examples/dbclient/mongodb/src/main/resources/logging.properties index a3b94c39c5f..3b6512d0017 100644 --- a/examples/dbclient/mongodb/src/main/resources/logging.properties +++ b/examples/dbclient/mongodb/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2020 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,14 +18,14 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # Global default logging level. Can be overriden by specific handlers and loggers .level=INFO # Helidon Web Server has a custom log formatter that extends SimpleFormatter. # It replaces "!thread!" with the current thread name -io.helidon.common.HelidonConsoleHandler.level=ALL +io.helidon.logging.jul.HelidonConsoleHandler.level=ALL java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #Component specific log levels diff --git a/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonService.java b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonService.java index 1703ae4a783..55e8a84805b 100644 --- a/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonService.java +++ b/examples/dbclient/pokemons/src/main/java/io/helidon/examples/dbclient/pokemons/PokemonService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import java.util.logging.Logger; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.dbclient.DbClient; import io.helidon.dbclient.DbRow; import io.helidon.webserver.Handler; @@ -73,7 +73,7 @@ public void update(Routing.Rules rules) { * @param response the server response */ private void index(ServerRequest request, ServerResponse response) { - response.headers().contentType(MediaType.TEXT_PLAIN); + response.headers().contentType(MediaTypes.TEXT_PLAIN); response.send("Pokemon JDBC Example:\n" + " GET /type - List all pokemon types\n" + " GET /pokemon - List all pokemons\n" diff --git a/examples/dbclient/pokemons/src/main/resources/logging.properties b/examples/dbclient/pokemons/src/main/resources/logging.properties index a3b94c39c5f..3b6512d0017 100644 --- a/examples/dbclient/pokemons/src/main/resources/logging.properties +++ b/examples/dbclient/pokemons/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2020 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,14 +18,14 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # Global default logging level. Can be overriden by specific handlers and loggers .level=INFO # Helidon Web Server has a custom log formatter that extends SimpleFormatter. # It replaces "!thread!" with the current thread name -io.helidon.common.HelidonConsoleHandler.level=ALL +io.helidon.logging.jul.HelidonConsoleHandler.level=ALL java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #Component specific log levels diff --git a/examples/employee-app/src/main/resources/logging.properties b/examples/employee-app/src/main/resources/logging.properties index f9dfd25b9da..f9bcbc4fc4b 100644 --- a/examples/employee-app/src/main/resources/logging.properties +++ b/examples/employee-app/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/employee-app/src/test/java/io/helidon/service/employee/MainTest.java b/examples/employee-app/src/test/java/io/helidon/service/employee/MainTest.java index 26f7a5fc82f..1b87aaa691d 100644 --- a/examples/employee-app/src/test/java/io/helidon/service/employee/MainTest.java +++ b/examples/employee-app/src/test/java/io/helidon/service/employee/MainTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import java.util.concurrent.TimeUnit; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.webclient.WebClient; import io.helidon.webserver.WebServer; @@ -39,7 +39,7 @@ public static void startTheServer() { webClient = WebClient.builder() .baseUri("http://localhost:" + webServer.port()) - .addHeader(Http.Header.ACCEPT, MediaType.APPLICATION_JSON.toString()) + .addHeader(Http.Header.ACCEPT, MediaTypes.APPLICATION_JSON.text()) .build(); } diff --git a/examples/grpc/basics/src/main/resources/logging.properties b/examples/grpc/basics/src/main/resources/logging.properties index f9dfd25b9da..f9bcbc4fc4b 100644 --- a/examples/grpc/basics/src/main/resources/logging.properties +++ b/examples/grpc/basics/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/grpc/client-standalone/src/main/resources/logging.properties b/examples/grpc/client-standalone/src/main/resources/logging.properties index fe4a22c9607..4ef909e29f2 100644 --- a/examples/grpc/client-standalone/src/main/resources/logging.properties +++ b/examples/grpc/client-standalone/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n .level=INFO diff --git a/examples/grpc/metrics/src/main/resources/logging.properties b/examples/grpc/metrics/src/main/resources/logging.properties index f9dfd25b9da..f9bcbc4fc4b 100644 --- a/examples/grpc/metrics/src/main/resources/logging.properties +++ b/examples/grpc/metrics/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/grpc/microprofile/basic-client/src/main/resources/logging.properties b/examples/grpc/microprofile/basic-client/src/main/resources/logging.properties index 5941e844d4a..b5a1aabfdbf 100644 --- a/examples/grpc/microprofile/basic-client/src/main/resources/logging.properties +++ b/examples/grpc/microprofile/basic-client/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=INFO io.helidon.microprofile.config.level=FINEST diff --git a/examples/grpc/microprofile/basic-server-implicit/src/main/resources/logging.properties b/examples/grpc/microprofile/basic-server-implicit/src/main/resources/logging.properties index 4b50b0d4f83..b5a1aabfdbf 100644 --- a/examples/grpc/microprofile/basic-server-implicit/src/main/resources/logging.properties +++ b/examples/grpc/microprofile/basic-server-implicit/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=INFO io.helidon.microprofile.config.level=FINEST diff --git a/examples/grpc/microprofile/metrics/src/main/resources/logging.properties b/examples/grpc/microprofile/metrics/src/main/resources/logging.properties index db670931807..4e68ae86668 100644 --- a/examples/grpc/microprofile/metrics/src/main/resources/logging.properties +++ b/examples/grpc/microprofile/metrics/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=INFO io.helidon.microprofile.config.level=FINEST diff --git a/examples/grpc/opentracing/src/main/resources/logging.properties b/examples/grpc/opentracing/src/main/resources/logging.properties index f9dfd25b9da..f9bcbc4fc4b 100644 --- a/examples/grpc/opentracing/src/main/resources/logging.properties +++ b/examples/grpc/opentracing/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/grpc/security-abac/src/main/resources/logging.properties b/examples/grpc/security-abac/src/main/resources/logging.properties index 94c6fdd77e2..934e3c96e8e 100644 --- a/examples/grpc/security-abac/src/main/resources/logging.properties +++ b/examples/grpc/security-abac/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,6 +14,6 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler .level=INFO AUDIT.level=FINEST diff --git a/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/SecureServer.java b/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/SecureServer.java index 74193666df5..1cf9412d389 100644 --- a/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/SecureServer.java +++ b/examples/grpc/security-outbound/src/main/java/io/helidon/grpc/examples/security/outbound/SecureServer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -247,7 +247,7 @@ private void handleResponse(WebClientResponse response, StreamObserver observer) } private void completeWithError(WebClientResponse response, StreamObserver observer) { - Http.ResponseStatus status = response.status(); + Http.Status status = response.status(); if (status == Http.Status.UNAUTHORIZED_401 || status == Http.Status.FORBIDDEN_403){ diff --git a/examples/grpc/security-outbound/src/main/resources/logging.properties b/examples/grpc/security-outbound/src/main/resources/logging.properties index c970479a0ae..ed6d1e879f5 100644 --- a/examples/grpc/security-outbound/src/main/resources/logging.properties +++ b/examples/grpc/security-outbound/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/grpc/security/src/main/resources/logging.properties b/examples/grpc/security/src/main/resources/logging.properties index c970479a0ae..ed6d1e879f5 100644 --- a/examples/grpc/security/src/main/resources/logging.properties +++ b/examples/grpc/security/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/micrometer/mp/src/main/resources/logging.properties b/examples/integrations/micrometer/mp/src/main/resources/logging.properties index 5a64db181bb..37c5d7591c3 100644 --- a/examples/integrations/micrometer/mp/src/main/resources/logging.properties +++ b/examples/integrations/micrometer/mp/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/micronaut/data/src/main/resources/logging.properties b/examples/integrations/micronaut/data/src/main/resources/logging.properties index 06e9fa7e63c..42482e19bd9 100644 --- a/examples/integrations/micronaut/data/src/main/resources/logging.properties +++ b/examples/integrations/micronaut/data/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/neo4j/neo4j-mp/src/main/resources/logging.properties b/examples/integrations/neo4j/neo4j-mp/src/main/resources/logging.properties index b5cf2f64627..b3e1f27ca75 100644 --- a/examples/integrations/neo4j/neo4j-mp/src/main/resources/logging.properties +++ b/examples/integrations/neo4j/neo4j-mp/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/neo4j/neo4j-se/src/main/resources/logging.properties b/examples/integrations/neo4j/neo4j-se/src/main/resources/logging.properties index aced7e48602..bfe40fbca31 100644 --- a/examples/integrations/neo4j/neo4j-se/src/main/resources/logging.properties +++ b/examples/integrations/neo4j/neo4j-se/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/oci/atp-cdi/src/main/resources/logging.properties b/examples/integrations/oci/atp-cdi/src/main/resources/logging.properties index d69a37e024f..600832651de 100644 --- a/examples/integrations/oci/atp-cdi/src/main/resources/logging.properties +++ b/examples/integrations/oci/atp-cdi/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/oci/atp-reactive/src/main/resources/logging.properties b/examples/integrations/oci/atp-reactive/src/main/resources/logging.properties index d69a37e024f..600832651de 100644 --- a/examples/integrations/oci/atp-reactive/src/main/resources/logging.properties +++ b/examples/integrations/oci/atp-reactive/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/oci/metrics-reactive/src/main/resources/logging.properties b/examples/integrations/oci/metrics-reactive/src/main/resources/logging.properties index d0317654f47..f8802f90626 100644 --- a/examples/integrations/oci/metrics-reactive/src/main/resources/logging.properties +++ b/examples/integrations/oci/metrics-reactive/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/oci/objectstorage-cdi/src/main/resources/logging.properties b/examples/integrations/oci/objectstorage-cdi/src/main/resources/logging.properties index d69a37e024f..600832651de 100644 --- a/examples/integrations/oci/objectstorage-cdi/src/main/resources/logging.properties +++ b/examples/integrations/oci/objectstorage-cdi/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/oci/objectstorage-reactive/src/main/resources/logging.properties b/examples/integrations/oci/objectstorage-reactive/src/main/resources/logging.properties index d69a37e024f..600832651de 100644 --- a/examples/integrations/oci/objectstorage-reactive/src/main/resources/logging.properties +++ b/examples/integrations/oci/objectstorage-reactive/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/oci/vault-cdi/src/main/resources/logging.properties b/examples/integrations/oci/vault-cdi/src/main/resources/logging.properties index d0317654f47..f8802f90626 100644 --- a/examples/integrations/oci/vault-cdi/src/main/resources/logging.properties +++ b/examples/integrations/oci/vault-cdi/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/oci/vault-reactive/src/main/resources/logging.properties b/examples/integrations/oci/vault-reactive/src/main/resources/logging.properties index d0317654f47..f8802f90626 100644 --- a/examples/integrations/oci/vault-reactive/src/main/resources/logging.properties +++ b/examples/integrations/oci/vault-reactive/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/pom.xml b/examples/integrations/pom.xml index 7543662a855..f668c40c693 100644 --- a/examples/integrations/pom.xml +++ b/examples/integrations/pom.xml @@ -35,7 +35,10 @@ micronaut neo4j micrometer + vault microstream diff --git a/examples/integrations/vault/hcp-cdi/src/main/resources/logging.properties b/examples/integrations/vault/hcp-cdi/src/main/resources/logging.properties index d0317654f47..f8802f90626 100644 --- a/examples/integrations/vault/hcp-cdi/src/main/resources/logging.properties +++ b/examples/integrations/vault/hcp-cdi/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/integrations/vault/hcp-reactive/src/main/resources/logging.properties b/examples/integrations/vault/hcp-reactive/src/main/resources/logging.properties index d0317654f47..f8802f90626 100644 --- a/examples/integrations/vault/hcp-reactive/src/main/resources/logging.properties +++ b/examples/integrations/vault/hcp-reactive/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/jbatch/pom.xml b/examples/jbatch/pom.xml index 9790b97f02e..6a6a63a5351 100644 --- a/examples/jbatch/pom.xml +++ b/examples/jbatch/pom.xml @@ -110,6 +110,15 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + org.apache.maven.plugins maven-dependency-plugin diff --git a/examples/jbatch/src/main/resources/logging.properties b/examples/jbatch/src/main/resources/logging.properties index 930fbd49781..b5e2c644b3d 100644 --- a/examples/jbatch/src/main/resources/logging.properties +++ b/examples/jbatch/src/main/resources/logging.properties @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/media/multipart/pom.xml b/examples/media/multipart/pom.xml index de1e52fe1a1..6d35dd9cb3b 100644 --- a/examples/media/multipart/pom.xml +++ b/examples/media/multipart/pom.xml @@ -88,6 +88,15 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + diff --git a/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/FileService.java b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/FileService.java index 8b38a604907..913487d6953 100644 --- a/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/FileService.java +++ b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/FileService.java @@ -21,11 +21,11 @@ import java.util.concurrent.ExecutorService; import io.helidon.common.configurable.ThreadPoolSupplier; +import io.helidon.common.http.ContentDisposition; import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.IoMulti; -import io.helidon.media.multipart.ContentDisposition; import io.helidon.media.multipart.ReadableBodyPart; import io.helidon.webserver.ResponseHeaders; import io.helidon.webserver.Routing; @@ -70,8 +70,8 @@ private void list(ServerRequest req, ServerResponse res) { private void download(ServerRequest req, ServerResponse res) { Path filePath = storage.lookup(req.path().param("fname")); ResponseHeaders headers = res.headers(); - headers.contentType(MediaType.APPLICATION_OCTET_STREAM); - headers.put(Http.Header.CONTENT_DISPOSITION, ContentDisposition.builder() + headers.contentType(MediaTypes.APPLICATION_OCTET_STREAM); + headers.set(Http.Header.CONTENT_DISPOSITION, ContentDisposition.builder() .filename(filePath.getFileName().toString()) .build() .toString()); @@ -95,7 +95,7 @@ private void upload(ServerRequest req, ServerResponse res) { .onError(res::send) .onComplete(() -> { res.status(Http.Status.MOVED_PERMANENTLY_301); - res.headers().put(Http.Header.LOCATION, "/ui"); + res.headers().set(Http.Header.LOCATION, "/ui"); res.send(); }).ignoreElement(); } diff --git a/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java index dd04076dc9e..9d960c7a89f 100644 --- a/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java +++ b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ * This application provides a simple file upload service with a UI to exercise multipart. */ public final class Main { + private static final Http.HeaderValue REDIRECT_LOCATION = Http.HeaderValue.createCached(Http.Header.LOCATION, "/ui"); private Main() { } @@ -40,7 +41,7 @@ static Routing createRouting() { return Routing.builder() .any("/", (req, res) -> { res.status(Http.Status.MOVED_PERMANENTLY_301); - res.headers().put(Http.Header.LOCATION, "/ui"); + res.headers().set(REDIRECT_LOCATION); res.send(); }) .register("/ui", StaticContentSupport.builder("WEB") diff --git a/examples/media/multipart/src/test/java/io/helidon/examples/media/multipart/FileServiceTest.java b/examples/media/multipart/src/test/java/io/helidon/examples/media/multipart/FileServiceTest.java index a6b62d8bf8e..4509033436b 100644 --- a/examples/media/multipart/src/test/java/io/helidon/examples/media/multipart/FileServiceTest.java +++ b/examples/media/multipart/src/test/java/io/helidon/examples/media/multipart/FileServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,8 @@ import java.util.List; import java.util.concurrent.TimeUnit; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.Http; +import io.helidon.common.media.type.MediaTypes; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.media.multipart.FileFormParams; import io.helidon.media.multipart.MultiPartSupport; @@ -77,10 +78,10 @@ public static void stopServer() { @Test @Order(1) public void testUpload() throws IOException { - Path file = Files.write( Files.createTempFile(null, null), "bar\n".getBytes(StandardCharsets.UTF_8)); + Path file = Files.writeString(Files.createTempFile(null, null), "bar\n"); WebClientResponse response = webClient .post() - .contentType(MediaType.MULTIPART_FORM_DATA) + .contentType(MediaTypes.MULTIPART_FORM_DATA) .submit(FileFormParams.builder() .addFile("file[]", "foo.txt", file) .build()) @@ -91,12 +92,12 @@ public void testUpload() throws IOException { @Test @Order(2) public void testStreamUpload() throws IOException { - Path file = Files.write( Files.createTempFile(null, null), "stream bar\n".getBytes(StandardCharsets.UTF_8)); - Path file2 = Files.write( Files.createTempFile(null, null), "stream foo\n".getBytes(StandardCharsets.UTF_8)); + Path file = Files.writeString(Files.createTempFile(null, null), "stream bar\n"); + Path file2 = Files.writeString(Files.createTempFile(null, null), "stream foo\n"); WebClientResponse response = webClient .post() .queryParam("stream", "true") - .contentType(MediaType.MULTIPART_FORM_DATA) + .contentType(MediaTypes.MULTIPART_FORM_DATA) .submit(FileFormParams.builder() .addFile("file[]", "streamed-foo.txt", file) .addFile("otherPart", "streamed-foo2.txt", file2) @@ -110,7 +111,7 @@ public void testStreamUpload() throws IOException { public void testList() { WebClientResponse response = webClient .get() - .contentType(MediaType.APPLICATION_JSON) + .contentType(MediaTypes.APPLICATION_JSON) .request() .await(); assertThat(response.status().code(), Matchers.is(200)); @@ -126,11 +127,11 @@ public void testDownload() { WebClientResponse response = webClient .get() .path("foo.txt") - .accept(MediaType.APPLICATION_OCTET_STREAM) + .accept(MediaTypes.APPLICATION_OCTET_STREAM) .request() .await(); assertThat(response.status().code(), is(200)); - assertThat(response.headers().first("Content-Disposition").orElse(null), + assertThat(response.headers().first(Http.Header.CONTENT_DISPOSITION).orElse(null), containsString("filename=\"foo.txt\"")); byte[] bytes = response.content().as(byte[].class).await(); assertThat(new String(bytes, StandardCharsets.UTF_8), Matchers.is("bar\n")); diff --git a/examples/messaging/jms-websocket-mp/src/main/resources/logging.properties b/examples/messaging/jms-websocket-mp/src/main/resources/logging.properties index fc2a665a625..d593130c85f 100644 --- a/examples/messaging/jms-websocket-mp/src/main/resources/logging.properties +++ b/examples/messaging/jms-websocket-mp/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/messaging/jms-websocket-se/src/main/resources/logging.properties b/examples/messaging/jms-websocket-se/src/main/resources/logging.properties index fc2a665a625..d593130c85f 100644 --- a/examples/messaging/jms-websocket-se/src/main/resources/logging.properties +++ b/examples/messaging/jms-websocket-se/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/messaging/kafka-websocket-mp/src/main/resources/logging.properties b/examples/messaging/kafka-websocket-mp/src/main/resources/logging.properties index c81e423cb17..d28553fe70d 100644 --- a/examples/messaging/kafka-websocket-mp/src/main/resources/logging.properties +++ b/examples/messaging/kafka-websocket-mp/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/messaging/kafka-websocket-se/src/main/resources/logging.properties b/examples/messaging/kafka-websocket-se/src/main/resources/logging.properties index fc2a665a625..d593130c85f 100644 --- a/examples/messaging/kafka-websocket-se/src/main/resources/logging.properties +++ b/examples/messaging/kafka-websocket-se/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/messaging/oracle-aq-websocket-mp/src/main/resources/logging.properties b/examples/messaging/oracle-aq-websocket-mp/src/main/resources/logging.properties index fc2a665a625..d593130c85f 100644 --- a/examples/messaging/oracle-aq-websocket-mp/src/main/resources/logging.properties +++ b/examples/messaging/oracle-aq-websocket-mp/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/metrics/exemplar/src/main/resources/logging.properties b/examples/metrics/exemplar/src/main/resources/logging.properties index aced7e48602..bfe40fbca31 100644 --- a/examples/metrics/exemplar/src/main/resources/logging.properties +++ b/examples/metrics/exemplar/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/metrics/filtering/mp/src/main/resources/logging.properties b/examples/metrics/filtering/mp/src/main/resources/logging.properties index 8fc59d05c12..2ad5305a205 100644 --- a/examples/metrics/filtering/mp/src/main/resources/logging.properties +++ b/examples/metrics/filtering/mp/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/metrics/filtering/se/src/main/resources/logging.properties b/examples/metrics/filtering/se/src/main/resources/logging.properties index aced7e48602..bfe40fbca31 100644 --- a/examples/metrics/filtering/se/src/main/resources/logging.properties +++ b/examples/metrics/filtering/se/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/metrics/kpi/src/main/resources/logging.properties b/examples/metrics/kpi/src/main/resources/logging.properties index aced7e48602..bfe40fbca31 100644 --- a/examples/metrics/kpi/src/main/resources/logging.properties +++ b/examples/metrics/kpi/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/microprofile/bean-validation/src/main/resources/logging.properties b/examples/microprofile/bean-validation/src/main/resources/logging.properties index 930fbd49781..b5e2c644b3d 100644 --- a/examples/microprofile/bean-validation/src/main/resources/logging.properties +++ b/examples/microprofile/bean-validation/src/main/resources/logging.properties @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/microprofile/cors/src/main/resources/logging.properties b/examples/microprofile/cors/src/main/resources/logging.properties index 38c3c8089f6..da327e1adda 100644 --- a/examples/microprofile/cors/src/main/resources/logging.properties +++ b/examples/microprofile/cors/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/microprofile/cors/src/test/java/io/helidon/microprofile/examples/cors/TestCORS.java b/examples/microprofile/cors/src/test/java/io/helidon/microprofile/examples/cors/TestCORS.java index c85b782d1ed..2dfc26624a3 100644 --- a/examples/microprofile/cors/src/test/java/io/helidon/microprofile/examples/cors/TestCORS.java +++ b/examples/microprofile/cors/src/test/java/io/helidon/microprofile/examples/cors/TestCORS.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,13 +20,16 @@ import java.util.Optional; import io.helidon.common.http.Headers; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.Http.Header; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.microprofile.server.Server; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; +import io.helidon.webclient.WebClientRequestHeaders; import io.helidon.webclient.WebClientResponse; +import io.helidon.webclient.WebClientResponseHeaders; import io.helidon.webserver.cors.CrossOriginConfig; import jakarta.json.Json; @@ -115,16 +118,16 @@ public void testHelloWorld() { @Test void testAnonymousGreetWithCors() { WebClientRequestBuilder builder = client.get(); - Headers headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); + WebClientRequestHeaders headers = builder.headers(); + headers.add(Header.ORIGIN, "http://foo.com"); + headers.add(Header.HOST, "here.com"); WebClientResponse r = getResponse("/greet", builder); assertThat("HTTP response", r.status().code(), is(200)); String payload = fromPayload(r); assertThat("HTTP response payload", payload, is("Hola World!")); - headers = r.headers(); - Optional allowOrigin = headers.value(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + Headers responseHeaders = r.headers(); + Optional allowOrigin = responseHeaders.value(Header.ACCESS_CONTROL_ALLOW_ORIGIN); assertThat("Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN + " is present", allowOrigin.isPresent(), is(true)); assertThat("CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin.get(), is("*")); @@ -137,10 +140,10 @@ void testGreetingChangeWithCors() { // Send the pre-flight request and check the response. WebClientRequestBuilder builder = client.method("OPTIONS"); - Headers headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); - headers.add("Access-Control-Request-Method", "PUT"); + WebClientRequestHeaders headers = builder.headers(); + headers.add(Header.ORIGIN, "http://foo.com"); + headers.add(Header.HOST, "here.com"); + headers.add(Header.ACCESS_CONTROL_REQUEST_METHOD, "PUT"); WebClientResponse r = builder.path("/greet/greeting") .submit() @@ -148,11 +151,11 @@ void testGreetingChangeWithCors() { assertThat("pre-flight status", r.status().code(), is(200)); Headers preflightResponseHeaders = r.headers(); - List allowMethods = preflightResponseHeaders.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS); + List allowMethods = preflightResponseHeaders.values(Header.ACCESS_CONTROL_ALLOW_METHODS); assertThat("pre-flight response check for " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS, allowMethods, is(not(empty()))); assertThat("Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS, allowMethods, contains("PUT")); - List allowOrigins = preflightResponseHeaders.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + List allowOrigins = preflightResponseHeaders.values(Header.ACCESS_CONTROL_ALLOW_ORIGIN); assertThat("pre-flight response check for " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigins, is(not(empty()))); assertThat( "Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigins, contains("http://foo.com")); @@ -161,14 +164,14 @@ void testGreetingChangeWithCors() { builder = client.put(); headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); + headers.add(Header.ORIGIN, "http://foo.com"); + headers.add(Header.HOST, "here.com"); headers.addAll(preflightResponseHeaders); r = putResponse("/greet/greeting", "Cheers", builder); assertThat("HTTP response3", r.status().code(), is(204)); - headers = r.headers(); - allowOrigins = headers.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + WebClientResponseHeaders responseHeaders = r.headers(); + allowOrigins = responseHeaders.values(Header.ACCESS_CONTROL_ALLOW_ORIGIN); assertThat("Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigins, is(not(empty()))); assertThat( "Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigins, contains("http://foo.com")); @@ -178,15 +181,15 @@ void testGreetingChangeWithCors() { @Test void testNamedGreetWithCors() { WebClientRequestBuilder builder = client.get(); - Headers headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); + WebClientRequestHeaders headers = builder.headers(); + headers.add(Header.ORIGIN, "http://foo.com"); + headers.add(Header.HOST, "here.com"); WebClientResponse r = getResponse("/greet/Maria", builder); assertThat("HTTP response", r.status().code(), is(200)); assertThat(fromPayload(r), containsString("Cheers Maria")); - headers = r.headers(); - Optional allowOrigin = headers.value(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + WebClientResponseHeaders responseHeaders = r.headers(); + Optional allowOrigin = responseHeaders.value(Header.ACCESS_CONTROL_ALLOW_ORIGIN); assertThat("Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN + " presence check", allowOrigin.isPresent(), is(true)); assertThat(allowOrigin.get(), is("*")); @@ -196,9 +199,9 @@ void testNamedGreetWithCors() { @Test void testGreetingChangeWithCorsAndOtherOrigin() { WebClientRequestBuilder builder = client.put(); - Headers headers = builder.headers(); - headers.add("Origin", "http://other.com"); - headers.add("Host", "here.com"); + WebClientRequestHeaders headers = builder.headers(); + headers.set(Header.ORIGIN, "http://other.com"); + headers.set(Header.HOST, "here.com"); WebClientResponse r = putResponse("/greet/greeting", "Ahoy", builder); // Result depends on whether we are using overrides or not. @@ -213,7 +216,7 @@ private static WebClientResponse getResponse(String path) { private static WebClientResponse getResponse(String path, WebClientRequestBuilder builder) { return builder - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path(path) .submit() .await(); @@ -238,7 +241,7 @@ private static WebClientResponse putResponse(String path, String message) { private static WebClientResponse putResponse(String path, String message, WebClientRequestBuilder builder) { return builder - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path(path) .submit(toPayload(message)) .await(); diff --git a/examples/microprofile/cors/src/test/resources/logging.properties b/examples/microprofile/cors/src/test/resources/logging.properties index c08c7458dbf..d7cf0b9d9e1 100644 --- a/examples/microprofile/cors/src/test/resources/logging.properties +++ b/examples/microprofile/cors/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/microprofile/graphql/src/main/resources/logging.properties b/examples/microprofile/graphql/src/main/resources/logging.properties index d777fc779e0..c2b6684a4a1 100644 --- a/examples/microprofile/graphql/src/main/resources/logging.properties +++ b/examples/microprofile/graphql/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,9 +14,9 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler -io.helidon.common.HelidonConsoleHandler.level=ALL +io.helidon.logging.jul.HelidonConsoleHandler.level=ALL java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details diff --git a/examples/microprofile/hello-world-explicit/src/main/resources/logging.properties b/examples/microprofile/hello-world-explicit/src/main/resources/logging.properties index 0615cfef72c..54e59fec680 100644 --- a/examples/microprofile/hello-world-explicit/src/main/resources/logging.properties +++ b/examples/microprofile/hello-world-explicit/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2020 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,9 +14,9 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler -io.helidon.common.HelidonConsoleHandler.level=ALL +io.helidon.logging.jul.HelidonConsoleHandler.level=ALL java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details diff --git a/examples/microprofile/hello-world-implicit/src/main/resources/logging.properties b/examples/microprofile/hello-world-implicit/src/main/resources/logging.properties index 7b0371c8484..c1abdd4a00f 100644 --- a/examples/microprofile/hello-world-implicit/src/main/resources/logging.properties +++ b/examples/microprofile/hello-world-implicit/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=INFO io.helidon.microprofile.config.level=FINEST diff --git a/examples/microprofile/idcs/src/main/resources/logging.properties b/examples/microprofile/idcs/src/main/resources/logging.properties index 51d8d54c6e6..7e09243e77e 100644 --- a/examples/microprofile/idcs/src/main/resources/logging.properties +++ b/examples/microprofile/idcs/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=INFO AUDIT.level=FINEST diff --git a/examples/microprofile/lra/src/main/resources/logging.properties b/examples/microprofile/lra/src/main/resources/logging.properties index dbdea1d127e..98447119f24 100644 --- a/examples/microprofile/lra/src/main/resources/logging.properties +++ b/examples/microprofile/lra/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,6 +14,6 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=INFO diff --git a/examples/microprofile/messaging-sse/src/main/resources/logging.properties b/examples/microprofile/messaging-sse/src/main/resources/logging.properties index 927be950e5d..c5729141913 100644 --- a/examples/microprofile/messaging-sse/src/main/resources/logging.properties +++ b/examples/microprofile/messaging-sse/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=INFO io.helidon.microprofile.config.level=FINEST diff --git a/examples/microprofile/multipart/src/main/resources/logging.properties b/examples/microprofile/multipart/src/main/resources/logging.properties index 5a64db181bb..37c5d7591c3 100644 --- a/examples/microprofile/multipart/src/main/resources/logging.properties +++ b/examples/microprofile/multipart/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/microprofile/openapi-basic/src/main/resources/logging.properties b/examples/microprofile/openapi-basic/src/main/resources/logging.properties index 4029baf371f..a540d5bc6ab 100644 --- a/examples/microprofile/openapi-basic/src/main/resources/logging.properties +++ b/examples/microprofile/openapi-basic/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/microprofile/security/src/main/resources/logging.properties b/examples/microprofile/security/src/main/resources/logging.properties index 73bb7b5f638..c1abdd4a00f 100644 --- a/examples/microprofile/security/src/main/resources/logging.properties +++ b/examples/microprofile/security/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2020 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=INFO io.helidon.microprofile.config.level=FINEST diff --git a/examples/microprofile/static-content/src/main/resources/logging.properties b/examples/microprofile/static-content/src/main/resources/logging.properties index 7ea32155049..a42ad7a5dde 100644 --- a/examples/microprofile/static-content/src/main/resources/logging.properties +++ b/examples/microprofile/static-content/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2020 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # #All attributes details -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS.%1$tL] %4$s %3$s : %5$s%6$s%n #All log level details .level=INFO diff --git a/examples/microprofile/static-content/src/test/resources/logging.properties b/examples/microprofile/static-content/src/test/resources/logging.properties index 229aba33fe2..a9746102e97 100644 --- a/examples/microprofile/static-content/src/test/resources/logging.properties +++ b/examples/microprofile/static-content/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2020 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler .level=INFO java.util.logging.FileHandler.pattern=%h/java%u.log java.util.logging.FileHandler.limit=50000 diff --git a/examples/microprofile/tls/src/main/resources/logging.properties b/examples/microprofile/tls/src/main/resources/logging.properties index a71c681ab58..84a691d1515 100644 --- a/examples/microprofile/tls/src/main/resources/logging.properties +++ b/examples/microprofile/tls/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/microprofile/websocket/src/main/resources/logging.properties b/examples/microprofile/websocket/src/main/resources/logging.properties index 24de00b0293..ddbc8d6521d 100644 --- a/examples/microprofile/websocket/src/main/resources/logging.properties +++ b/examples/microprofile/websocket/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=INFO io.helidon.microprofile.server.level=INFO diff --git a/examples/openapi/src/main/resources/logging.properties b/examples/openapi/src/main/resources/logging.properties index 20473988762..8759a9f181b 100644 --- a/examples/openapi/src/main/resources/logging.properties +++ b/examples/openapi/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/openapi/src/test/java/io/helidon/examples/openapi/MainTest.java b/examples/openapi/src/test/java/io/helidon/examples/openapi/MainTest.java index a9f2049743c..ea4ad32b887 100644 --- a/examples/openapi/src/test/java/io/helidon/examples/openapi/MainTest.java +++ b/examples/openapi/src/test/java/io/helidon/examples/openapi/MainTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import java.util.Collections; import java.util.concurrent.TimeUnit; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.examples.openapi.internal.SimpleAPIModelReader; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; @@ -120,7 +120,7 @@ public void testOpenAPI() { * change the following path also. */ JsonObject jsonObject = webClient.get() - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path("/openapi") .request(JsonObject.class) .await(); diff --git a/examples/security/attribute-based-access-control/src/main/resources/logging.properties b/examples/security/attribute-based-access-control/src/main/resources/logging.properties index 3f6b76d5ef0..28f6591733f 100644 --- a/examples/security/attribute-based-access-control/src/main/resources/logging.properties +++ b/examples/security/attribute-based-access-control/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,6 +14,6 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler .level=INFO AUDIT.level=FINEST diff --git a/examples/security/basic-auth-with-static-content/src/main/java/io/helidon/security/examples/webserver/basic/BasicExampleBuilderMain.java b/examples/security/basic-auth-with-static-content/src/main/java/io/helidon/security/examples/webserver/basic/BasicExampleBuilderMain.java index af04e3a0041..f2a0c801b03 100644 --- a/examples/security/basic-auth-with-static-content/src/main/java/io/helidon/security/examples/webserver/basic/BasicExampleBuilderMain.java +++ b/examples/security/basic-auth-with-static-content/src/main/java/io/helidon/security/examples/webserver/basic/BasicExampleBuilderMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,7 +24,7 @@ import java.util.concurrent.TimeUnit; import io.helidon.common.LogConfig; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.security.Security; import io.helidon.security.SecurityContext; import io.helidon.security.integration.webserver.WebSecurity; @@ -78,7 +78,7 @@ static WebServer startServer() { .audit()) .get("/{*}", (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Hello, you are: \n" + securityContext .map(ctx -> ctx.user().orElse(SecurityContext.ANONYMOUS).toString()) .orElse("Security context is null")); diff --git a/examples/security/basic-auth-with-static-content/src/main/java/io/helidon/security/examples/webserver/basic/BasicExampleConfigMain.java b/examples/security/basic-auth-with-static-content/src/main/java/io/helidon/security/examples/webserver/basic/BasicExampleConfigMain.java index bf3c3f2b1a8..b34266c85a9 100644 --- a/examples/security/basic-auth-with-static-content/src/main/java/io/helidon/security/examples/webserver/basic/BasicExampleConfigMain.java +++ b/examples/security/basic-auth-with-static-content/src/main/java/io/helidon/security/examples/webserver/basic/BasicExampleConfigMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import java.util.concurrent.TimeUnit; import io.helidon.common.LogConfig; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.config.Config; import io.helidon.security.SecurityContext; import io.helidon.security.integration.webserver.WebSecurity; @@ -54,7 +54,7 @@ static WebServer startServer() { .register("/static", StaticContentSupport.create("/WEB")) .get("/{*}", (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Hello, you are: \n" + securityContext .map(ctx -> ctx.user().orElse(SecurityContext.ANONYMOUS).toString()) .orElse("Security context is null")); diff --git a/examples/security/basic-auth-with-static-content/src/main/resources/logging.properties b/examples/security/basic-auth-with-static-content/src/main/resources/logging.properties index 7cd24e811fc..ed01d6773d2 100644 --- a/examples/security/basic-auth-with-static-content/src/main/resources/logging.properties +++ b/examples/security/basic-auth-with-static-content/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2020 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details .level=INFO diff --git a/examples/security/basic-auth-with-static-content/src/test/java/io/helidon/security/examples/webserver/basic/BasicExampleTest.java b/examples/security/basic-auth-with-static-content/src/test/java/io/helidon/security/examples/webserver/basic/BasicExampleTest.java index 8d1220e51d2..cc9925a38fc 100644 --- a/examples/security/basic-auth-with-static-content/src/test/java/io/helidon/security/examples/webserver/basic/BasicExampleTest.java +++ b/examples/security/basic-auth-with-static-content/src/test/java/io/helidon/security/examples/webserver/basic/BasicExampleTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -142,7 +142,7 @@ private void testNotAuthorized(String uri) { WebClientResponse response = client.get().uri(uri).request().await(5, TimeUnit.SECONDS); assertThat(response.status(), is(Http.Status.UNAUTHORIZED_401)); - String header = response.headers().first("WWW-Authenticate").get(); + String header = response.headers().get(Http.Header.WWW_AUTHENTICATE).value(); assertThat(header.toLowerCase(), containsString("basic")); assertThat(header, containsString("helidon")); } diff --git a/examples/security/google-login/src/main/java/io/helidon/security/examples/google/GoogleBuilderMain.java b/examples/security/google-login/src/main/java/io/helidon/security/examples/google/GoogleBuilderMain.java index f06e30e2813..18d06abd418 100644 --- a/examples/security/google-login/src/main/java/io/helidon/security/examples/google/GoogleBuilderMain.java +++ b/examples/security/google-login/src/main/java/io/helidon/security/examples/google/GoogleBuilderMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import java.util.Optional; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.security.Security; import io.helidon.security.SecurityContext; import io.helidon.security.Subject; @@ -63,7 +63,7 @@ static int start(int port) { WebSecurity.authenticate(), (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Response from builder based service, you are: \n" + securityContext .flatMap(SecurityContext::user) .map(Subject::toString) diff --git a/examples/security/google-login/src/main/java/io/helidon/security/examples/google/GoogleConfigMain.java b/examples/security/google-login/src/main/java/io/helidon/security/examples/google/GoogleConfigMain.java index 44241a83634..a754d9fa108 100644 --- a/examples/security/google-login/src/main/java/io/helidon/security/examples/google/GoogleConfigMain.java +++ b/examples/security/google-login/src/main/java/io/helidon/security/examples/google/GoogleConfigMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import java.util.Optional; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.config.Config; import io.helidon.security.SecurityContext; import io.helidon.security.Subject; @@ -71,7 +71,7 @@ static int start(int port) { // web server does not (yet) have possibility to configure routes in config files, so explicit... .get("/rest/profile", (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Response from config based service, you are: \n" + securityContext .flatMap(SecurityContext::user) .map(Subject::toString) diff --git a/examples/security/google-login/src/main/resources/logging.properties b/examples/security/google-login/src/main/resources/logging.properties index 3f6b76d5ef0..28f6591733f 100644 --- a/examples/security/google-login/src/main/resources/logging.properties +++ b/examples/security/google-login/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,6 +14,6 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler .level=INFO AUDIT.level=FINEST diff --git a/examples/security/idcs-login/src/main/java/io/helidon/security/examples/idcs/IdcsBuilderMain.java b/examples/security/idcs-login/src/main/java/io/helidon/security/examples/idcs/IdcsBuilderMain.java index 8df4cdab19b..ede5ff4bac8 100644 --- a/examples/security/idcs-login/src/main/java/io/helidon/security/examples/idcs/IdcsBuilderMain.java +++ b/examples/security/idcs-login/src/main/java/io/helidon/security/examples/idcs/IdcsBuilderMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import java.util.Optional; import io.helidon.common.LogConfig; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.config.Config; import io.helidon.security.Security; import io.helidon.security.SecurityContext; @@ -87,7 +87,7 @@ public static void main(String[] args) throws IOException { // web server does not (yet) have possibility to configure routes in config files, so explicit... .get("/rest/profile", (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Response from builder based service, you are: \n" + securityContext .flatMap(SecurityContext::user) .map(Subject::toString) diff --git a/examples/security/idcs-login/src/main/java/io/helidon/security/examples/idcs/IdcsMain.java b/examples/security/idcs-login/src/main/java/io/helidon/security/examples/idcs/IdcsMain.java index 73df24332ad..ec89d71ddd4 100644 --- a/examples/security/idcs-login/src/main/java/io/helidon/security/examples/idcs/IdcsMain.java +++ b/examples/security/idcs-login/src/main/java/io/helidon/security/examples/idcs/IdcsMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import io.helidon.common.LogConfig; import io.helidon.common.context.Contexts; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.config.Config; import io.helidon.security.Security; import io.helidon.security.SecurityContext; @@ -68,7 +68,7 @@ public static void main(String[] args) { // web server does not (yet) have possibility to configure routes in config files, so explicit... .get("/rest/profile", (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Response from config based service, you are: \n" + securityContext .flatMap(SecurityContext::user) .map(Subject::toString) diff --git a/examples/security/idcs-login/src/main/resources/logging.properties b/examples/security/idcs-login/src/main/resources/logging.properties index 53ec2f9338a..78202ff9364 100644 --- a/examples/security/idcs-login/src/main/resources/logging.properties +++ b/examples/security/idcs-login/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/security/jersey/src/main/resources/logging.properties b/examples/security/jersey/src/main/resources/logging.properties index 3f6b76d5ef0..28f6591733f 100644 --- a/examples/security/jersey/src/main/resources/logging.properties +++ b/examples/security/jersey/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,6 +14,6 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler .level=INFO AUDIT.level=FINEST diff --git a/examples/security/nohttp-programmatic/src/main/resources/logging.properties b/examples/security/nohttp-programmatic/src/main/resources/logging.properties index 3f6b76d5ef0..28f6591733f 100644 --- a/examples/security/nohttp-programmatic/src/main/resources/logging.properties +++ b/examples/security/nohttp-programmatic/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,6 +14,6 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler .level=INFO AUDIT.level=FINEST diff --git a/examples/security/outbound-override/src/main/java/io/helidon/security/examples/outbound/OutboundOverrideJwtExample.java b/examples/security/outbound-override/src/main/java/io/helidon/security/examples/outbound/OutboundOverrideJwtExample.java index c7f3a970471..cb461ed9fd6 100644 --- a/examples/security/outbound-override/src/main/java/io/helidon/security/examples/outbound/OutboundOverrideJwtExample.java +++ b/examples/security/outbound-override/src/main/java/io/helidon/security/examples/outbound/OutboundOverrideJwtExample.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,6 +17,7 @@ import java.util.concurrent.CompletionStage; +import io.helidon.common.http.Http; import io.helidon.config.Config; import io.helidon.security.Principal; import io.helidon.security.SecurityContext; @@ -79,7 +80,7 @@ static CompletionStage startServingService(int port) { .register(WebSecurity.create(config.get("security"))) .get("/hello", (req, res) -> { // This is the token. It should be bearer - req.headers().first("Authorization") + req.headers().first(Http.Header.AUTHORIZATION) .ifPresent(System.out::println); res.send(req.context().get(SecurityContext.class).flatMap(SecurityContext::user).map( Subject::principal).map(Principal::getName).orElse("Anonymous")); diff --git a/examples/security/vaults/src/main/resources/application.yaml b/examples/security/vaults/src/main/resources/application.yaml index ec6dad51935..63e95c48c80 100644 --- a/examples/security/vaults/src/main/resources/application.yaml +++ b/examples/security/vaults/src/main/resources/application.yaml @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -38,7 +38,7 @@ security: - vault-transit: address: "${vault.address}" token: "${vault.token}" - - oci-vault: + # - oci-vault: # We need either the cryptographic endpoint (recommended), or management-endpoint configured # Also ~/.oci/config must be correctly set up, otherwise all information is required here # The crypto endpoint may be configured per digest/encryption, as we may use more than one @@ -51,10 +51,10 @@ security: config: path: "app/secret" key: "username" - - name: "password" - provider: "oci-vault" - config: - ocid: "${oci.properties.secret-ocid}" +# - name: "password" +# provider: "oci-vault" +# config: +# ocid: "${oci.properties.secret-ocid}" - name: "token" provider: "config-vault" config: @@ -71,22 +71,22 @@ security: config: type: "hmac" key-name: "encryption-key" - - name: "sig-1" - provider: "oci-vault" - config: - cryptographic-endpoint: "${oci.properties.cryptographic-endpoint}" - key-ocid: "${oci.properties.vault-rsa-key-ocid}" - algorithm: "SHA_256_RSA_PKCS_PSS" +# - name: "sig-1" +# provider: "oci-vault" +# config: +# cryptographic-endpoint: "${oci.properties.cryptographic-endpoint}" +# key-ocid: "${oci.properties.vault-rsa-key-ocid}" +# algorithm: "SHA_256_RSA_PKCS_PSS" encryption: # encryption and decryption - name: "crypto-3" provider: "vault-transit" config: key-name: "encryption-key" - - name: "crypto-1" - provider: "oci-vault" - config: - cryptographic-endpoint: "${oci.properties.cryptographic-endpoint}" - key-ocid: "${oci.properties.vault-key-ocid}" +# - name: "crypto-1" +# provider: "oci-vault" +# config: +# cryptographic-endpoint: "${oci.properties.cryptographic-endpoint}" +# key-ocid: "${oci.properties.vault-key-ocid}" - name: "crypto-2" provider: "config-vault" diff --git a/examples/security/vaults/src/main/resources/logging.properties b/examples/security/vaults/src/main/resources/logging.properties index d0317654f47..f8802f90626 100644 --- a/examples/security/vaults/src/main/resources/logging.properties +++ b/examples/security/vaults/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/security/webserver-digest-auth/src/main/java/io/helidon/security/examples/webserver/digest/DigestExampleBuilderMain.java b/examples/security/webserver-digest-auth/src/main/java/io/helidon/security/examples/webserver/digest/DigestExampleBuilderMain.java index 641201c0420..20e876b3765 100644 --- a/examples/security/webserver-digest-auth/src/main/java/io/helidon/security/examples/webserver/digest/DigestExampleBuilderMain.java +++ b/examples/security/webserver-digest-auth/src/main/java/io/helidon/security/examples/webserver/digest/DigestExampleBuilderMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import java.util.Set; import io.helidon.common.LogConfig; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.security.Security; import io.helidon.security.SecurityContext; import io.helidon.security.integration.webserver.WebSecurity; @@ -43,14 +43,14 @@ public final class DigestExampleBuilderMain { // used from unit tests private static WebServer server; // simple approach to user storage - for real world, use data store... - private static Map users = new HashMap<>(); + private static final Map USERS = new HashMap<>(); private static final char[] HEX_ARRAY = "0123456789abcdef".toCharArray(); static { - users.put("jack", new MyUser("jack", "password".toCharArray(), Set.of("user", "admin"))); - users.put("jill", new MyUser("jill", "password".toCharArray(), Set.of("user"))); - users.put("john", new MyUser("john", "password".toCharArray(), Set.of())); + USERS.put("jack", new MyUser("jack", "password".toCharArray(), Set.of("user", "admin"))); + USERS.put("jill", new MyUser("jill", "password".toCharArray(), Set.of("user"))); + USERS.put("john", new MyUser("john", "password".toCharArray(), Set.of())); } private DigestExampleBuilderMain() { @@ -79,7 +79,7 @@ public static void main(String[] args) { .audit()) .get("/{*}", (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Hello, you are: \n" + securityContext .map(ctx -> ctx.user().orElse(SecurityContext.ANONYMOUS).toString()) .orElse("Security context is null")); @@ -103,7 +103,7 @@ private static WebSecurity buildWebSecurity() { } private static SecureUserStore buildUserStore() { - return login -> Optional.ofNullable(users.get(login)); + return login -> Optional.ofNullable(USERS.get(login)); } static WebServer getServer() { diff --git a/examples/security/webserver-digest-auth/src/main/java/io/helidon/security/examples/webserver/digest/DigestExampleConfigMain.java b/examples/security/webserver-digest-auth/src/main/java/io/helidon/security/examples/webserver/digest/DigestExampleConfigMain.java index 3e78391635d..2c3e01b0cb3 100644 --- a/examples/security/webserver-digest-auth/src/main/java/io/helidon/security/examples/webserver/digest/DigestExampleConfigMain.java +++ b/examples/security/webserver-digest-auth/src/main/java/io/helidon/security/examples/webserver/digest/DigestExampleConfigMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import java.util.Optional; import io.helidon.common.LogConfig; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.config.Config; import io.helidon.security.SecurityContext; import io.helidon.security.integration.webserver.WebSecurity; @@ -55,7 +55,7 @@ public static void main(String[] args) { // web server does not (yet) have possibility to configure routes in config files, so explicit... .get("/{*}", (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Hello, you are: \n" + securityContext .map(ctx -> ctx.user().orElse(SecurityContext.ANONYMOUS).toString()) .orElse("Security context is null")); diff --git a/examples/security/webserver-digest-auth/src/main/resources/logging.properties b/examples/security/webserver-digest-auth/src/main/resources/logging.properties index 809e33bb018..a418a00a6d8 100644 --- a/examples/security/webserver-digest-auth/src/main/resources/logging.properties +++ b/examples/security/webserver-digest-auth/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details .level=WARNING diff --git a/examples/security/webserver-signatures/src/main/java/io/helidon/security/examples/signatures/SignatureExampleBuilderMain.java b/examples/security/webserver-signatures/src/main/java/io/helidon/security/examples/signatures/SignatureExampleBuilderMain.java index 6c424190482..44395d82e4f 100644 --- a/examples/security/webserver-signatures/src/main/java/io/helidon/security/examples/signatures/SignatureExampleBuilderMain.java +++ b/examples/security/webserver-signatures/src/main/java/io/helidon/security/examples/signatures/SignatureExampleBuilderMain.java @@ -1,6 +1,6 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,7 +27,7 @@ import java.util.Optional; import io.helidon.common.configurable.Resource; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.pki.KeyConfig; import io.helidon.security.CompositeProviderFlag; import io.helidon.security.CompositeProviderSelectionPolicy; @@ -135,7 +135,7 @@ private static Routing routing2() { // web server does not (yet) have possibility to configure routes in config files, so explicit... .get("/{*}", (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Response from service2, you are: \n" + securityContext .flatMap(SecurityContext::user) .map(Subject::toString) diff --git a/examples/security/webserver-signatures/src/main/java/io/helidon/security/examples/signatures/SignatureExampleConfigMain.java b/examples/security/webserver-signatures/src/main/java/io/helidon/security/examples/signatures/SignatureExampleConfigMain.java index c25b749c631..faf2387ef07 100644 --- a/examples/security/webserver-signatures/src/main/java/io/helidon/security/examples/signatures/SignatureExampleConfigMain.java +++ b/examples/security/webserver-signatures/src/main/java/io/helidon/security/examples/signatures/SignatureExampleConfigMain.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import java.util.Optional; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.config.Config; import io.helidon.config.ConfigSources; import io.helidon.security.SecurityContext; @@ -78,7 +78,7 @@ private static Routing routing2() { // web server does not (yet) have possibility to configure routes in config files, so explicit... .get("/{*}", (req, res) -> { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Response from service2, you are: \n" + securityContext .flatMap(SecurityContext::user) .map(Subject::toString) diff --git a/examples/security/webserver-signatures/src/main/java/io/helidon/security/examples/signatures/SignatureExampleUtil.java b/examples/security/webserver-signatures/src/main/java/io/helidon/security/examples/signatures/SignatureExampleUtil.java index 18b5d68ca05..f9217a1ac66 100644 --- a/examples/security/webserver-signatures/src/main/java/io/helidon/security/examples/signatures/SignatureExampleUtil.java +++ b/examples/security/webserver-signatures/src/main/java/io/helidon/security/examples/signatures/SignatureExampleUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import java.util.concurrent.TimeUnit; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.security.SecurityContext; import io.helidon.webclient.WebClient; import io.helidon.webclient.security.WebClientSecurity; @@ -79,7 +79,7 @@ public static WebServer startServer(Routing routing, int port) { static void processService1Request(ServerRequest req, ServerResponse res, String path, int svc2port) { Optional securityContext = req.context().get(SecurityContext.class); - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); securityContext.ifPresentOrElse(context -> { CLIENT.get() diff --git a/examples/todo-app/backend/src/main/resources/logging.properties b/examples/todo-app/backend/src/main/resources/logging.properties index a77b4c191f6..5adb5fab096 100644 --- a/examples/todo-app/backend/src/main/resources/logging.properties +++ b/examples/todo-app/backend/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, 2021 Oracle and/or its affiliates. +# Copyright (c) 2017, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # #All attributes details -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details diff --git a/examples/todo-app/backend/src/test/java/io/helidon/demo/todos/backend/BackendTests.java b/examples/todo-app/backend/src/test/java/io/helidon/demo/todos/backend/BackendTests.java index 0860a41e8cd..51d0d2a03fc 100644 --- a/examples/todo-app/backend/src/test/java/io/helidon/demo/todos/backend/BackendTests.java +++ b/examples/todo-app/backend/src/test/java/io/helidon/demo/todos/backend/BackendTests.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -112,7 +112,7 @@ void testTodoScenario() { JsonObject returnedTodo = webTarget .path("/api/backend") .request(MediaType.APPLICATION_JSON_TYPE) - .header(Http.Header.AUTHORIZATION, basicAuth) + .header(Http.Header.AUTHORIZATION.defaultCase(), basicAuth) .post(Entity.json(todo), JsonObject.class); assertEquals("john", returnedTodo.getString("user")); @@ -121,7 +121,7 @@ void testTodoScenario() { // Get the todo created earlier JsonObject fromServer = webTarget.path("/api/backend/" + returnedTodo.getString("id")) .request(MediaType.APPLICATION_JSON_TYPE) - .header(Http.Header.AUTHORIZATION, basicAuth) + .header(Http.Header.AUTHORIZATION.defaultCase(), basicAuth) .get(JsonObject.class); assertEquals(returnedTodo, fromServer); @@ -134,7 +134,7 @@ void testTodoScenario() { fromServer = webTarget.path("/api/backend/" + returnedTodo.getString("id")) .request(MediaType.APPLICATION_JSON_TYPE) - .header(Http.Header.AUTHORIZATION, basicAuth) + .header(Http.Header.AUTHORIZATION.defaultCase(), basicAuth) .put(Entity.json(updatedTodo), JsonObject.class); assertEquals(updatedTodo.getString("title"), fromServer.getString("title")); @@ -142,7 +142,7 @@ void testTodoScenario() { // Delete the todo created earlier fromServer = webTarget.path("/api/backend/" + returnedTodo.getString("id")) .request(MediaType.APPLICATION_JSON_TYPE) - .header(Http.Header.AUTHORIZATION, basicAuth) + .header(Http.Header.AUTHORIZATION.defaultCase(), basicAuth) .delete(JsonObject.class); assertEquals(returnedTodo.getString("id"), fromServer.getString("id")); @@ -150,7 +150,7 @@ void testTodoScenario() { // Get list of todos JsonArray jsonValues = webTarget.path("/api/backend") .request(MediaType.APPLICATION_JSON_TYPE) - .header(Http.Header.AUTHORIZATION, basicAuth) + .header(Http.Header.AUTHORIZATION.defaultCase(), basicAuth) .get(JsonArray.class); assertEquals(0, jsonValues.size(), "There should be no todos on server"); diff --git a/examples/todo-app/frontend/src/main/resources/logging.properties b/examples/todo-app/frontend/src/main/resources/logging.properties index a77b4c191f6..5adb5fab096 100644 --- a/examples/todo-app/frontend/src/main/resources/logging.properties +++ b/examples/todo-app/frontend/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, 2021 Oracle and/or its affiliates. +# Copyright (c) 2017, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # #All attributes details -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details diff --git a/examples/translator-app/backend/src/main/resources/logging.properties b/examples/translator-app/backend/src/main/resources/logging.properties index b83e3834510..7a9ecf9507f 100644 --- a/examples/translator-app/backend/src/main/resources/logging.properties +++ b/examples/translator-app/backend/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, 2021 Oracle and/or its affiliates. +# Copyright (c) 2017, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ #All attributes details -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details diff --git a/examples/translator-app/frontend/src/main/resources/logging.properties b/examples/translator-app/frontend/src/main/resources/logging.properties index b83e3834510..7a9ecf9507f 100644 --- a/examples/translator-app/frontend/src/main/resources/logging.properties +++ b/examples/translator-app/frontend/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, 2021 Oracle and/or its affiliates. +# Copyright (c) 2017, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ #All attributes details -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details diff --git a/examples/webclient/standalone/src/main/java/io/helidon/examples/webclient/standalone/ClientMain.java b/examples/webclient/standalone/src/main/java/io/helidon/examples/webclient/standalone/ClientMain.java index 72283b6e7df..5caa3cf566e 100644 --- a/examples/webclient/standalone/src/main/java/io/helidon/examples/webclient/standalone/ClientMain.java +++ b/examples/webclient/standalone/src/main/java/io/helidon/examples/webclient/standalone/ClientMain.java @@ -102,7 +102,7 @@ public static void main(String[] args) { .await(); } - static Single performPutMethod(WebClient webClient) { + static Single performPutMethod(WebClient webClient) { System.out.println("Put request execution."); return webClient.put() .path("/greeting") diff --git a/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/Main.java b/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/Main.java index 335404ff241..931ab768f4d 100644 --- a/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/Main.java +++ b/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,8 +23,8 @@ import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; -import io.helidon.common.http.Parameters; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.media.common.MediaContext; import io.helidon.media.common.MessageBodyReader; import io.helidon.media.jsonp.JsonpSupport; @@ -55,6 +55,8 @@ public class Main { private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); + public static final Http.HeaderName BAR_HEADER = Http.Header.create("bar"); + public static final Http.HeaderName FOO_HEADER = Http.Header.create("foo"); // ---------------- EXAMPLES @@ -153,7 +155,7 @@ public void routingAsFilter() { *

* {@link java.util.Optional Optional} API is heavily used to represent parameters optionality. *

- * WebServer {@link Parameters Parameters} API is used to represent fact, that headers and + * WebServer {@link io.helidon.common.parameters.Parameters Parameters} API is used to represent fact, that headers and * query parameters can contain multiple values. */ public void parametersAndHeaders() { @@ -162,7 +164,7 @@ public void parametersAndHeaders() { StringBuilder sb = new StringBuilder(); // Request headers req.headers() - .first("foo") + .first(FOO_HEADER) .ifPresent(v -> sb.append("foo: ").append(v).append("\n")); // Request parameters req.queryParams() @@ -171,7 +173,7 @@ public void parametersAndHeaders() { // Path parameters sb.append("id: ").append(req.path().param("id")); // Response headers - res.headers().contentType(MediaType.TEXT_PLAIN); + res.headers().contentType(MediaTypes.TEXT_PLAIN); // Response entity (payload) res.send(sb.toString()); }) @@ -186,8 +188,8 @@ public void parametersAndHeaders() { public void advancedRouting() { Routing routing = Routing.builder() .get("/foo", RequestPredicate.create() - .accepts(MediaType.TEXT_PLAIN) - .containsHeader("bar") + .accepts(HttpMediaType.TEXT_PLAIN) + .containsHeader(BAR_HEADER) .thenApply((req, res) -> res.send())) .build(); startServer(routing); diff --git a/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/NameReader.java b/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/NameReader.java index 183f66b584a..db346eead57 100644 --- a/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/NameReader.java +++ b/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/NameReader.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Single; import io.helidon.media.common.ContentReaders; import io.helidon.media.common.MessageBodyReader; @@ -30,7 +30,7 @@ */ public class NameReader implements MessageBodyReader { - private static final MediaType TYPE = MediaType.parse("application/name"); + static final HttpMediaType APP_NAME = HttpMediaType.create("application/name"); private NameReader() { } @@ -48,7 +48,7 @@ public Single read(Flow.Publisher publisher, Gene @Override public PredicateResult accept(GenericType type, MessageBodyReaderContext context) { return context.contentType() - .filter(TYPE::equals) + .filter(APP_NAME::equals) .map(it -> PredicateResult.supports(Name.class, type)) .orElse(PredicateResult.NOT_SUPPORTED); } diff --git a/examples/webserver/basics/src/test/java/io/helidon/webserver/examples/basics/MainTest.java b/examples/webserver/basics/src/test/java/io/helidon/webserver/examples/basics/MainTest.java index 07537075eec..82cdac8e25a 100644 --- a/examples/webserver/basics/src/test/java/io/helidon/webserver/examples/basics/MainTest.java +++ b/examples/webserver/basics/src/test/java/io/helidon/webserver/examples/basics/MainTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,8 @@ import java.util.function.Consumer; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.media.common.MediaContext; import io.helidon.webserver.Routing; import io.helidon.webserver.testsupport.MediaPublisher; @@ -34,6 +35,8 @@ public class MainTest { + private static final Http.HeaderName FOO_HEADER = Http.Header.create("foo"); + @Test public void firstRouting() throws Exception { // POST @@ -59,7 +62,7 @@ public void routingAsFilter() throws Exception { public void parametersAndHeaders() throws Exception { TestResponse response = createClient(Main::parametersAndHeaders).path("/context/aaa") .queryParameter("bar", "bbb") - .header("foo", "ccc") + .header(FOO_HEADER, "ccc") .get(); assertEquals(200, response.status().code()); String s = response.asString().get(); @@ -85,12 +88,12 @@ public void readContentEntity() throws Exception { // foo TestResponse response = createClient(Main::readContentEntity).path("/foo") - .post(MediaPublisher.create(MediaType.TEXT_PLAIN, "aaa")); + .post(MediaPublisher.create(HttpMediaType.TEXT_PLAIN, "aaa")); assertEquals(200, response.status().code()); assertEquals("aaa", response.asString().get()); // bar response = createClient(Main::readContentEntity).path("/bar") - .post(MediaPublisher.create(MediaType.TEXT_PLAIN, "aaa")); + .post(MediaPublisher.create(HttpMediaType.TEXT_PLAIN, "aaa")); assertEquals(200, response.status().code()); assertEquals("aaa", response.asString().get()); } @@ -99,13 +102,13 @@ public void readContentEntity() throws Exception { public void mediaReader() throws Exception { TestResponse response = createClient(Main::mediaReader) .path("/create-record") - .post(MediaPublisher.create(MediaType.parse("application/name"), "John Smith")); + .post(MediaPublisher.create(NameReader.APP_NAME, "John Smith")); assertEquals(201, response.status().code()); assertEquals("John Smith", response.asString().get()); // Unsupported Content-Type response = createClient(Main::mediaReader) .path("/create-record") - .post(MediaPublisher.create(MediaType.TEXT_PLAIN, "John Smith")); + .post(MediaPublisher.create(HttpMediaType.TEXT_PLAIN, "John Smith")); assertEquals(500, response.status().code()); } @@ -118,7 +121,7 @@ public void supports() throws Exception { // Static content response = createClient(Main::supports).path("/index.html").get(); assertEquals(200, response.status().code()); - assertEquals(MediaType.TEXT_HTML.toString(), response.headers().first(Http.Header.CONTENT_TYPE).orElse(null)); + assertEquals(MediaTypes.TEXT_HTML.text(), response.headers().first(Http.Header.CONTENT_TYPE).orElse(null)); // JSON response = createClient(Main::supports).path("/hello/Europe").get(); assertEquals(200, response.status().code()); @@ -130,18 +133,18 @@ public void errorHandling() throws Exception { // Valid TestResponse response = createClient(Main::errorHandling) .path("/compute") - .post(MediaPublisher.create(MediaType.TEXT_PLAIN, "2")); + .post(MediaPublisher.create(HttpMediaType.TEXT_PLAIN, "2")); assertEquals(200, response.status().code()); assertEquals("100 / 2 = 50", response.asString().get()); // Zero response = createClient(Main::errorHandling) .path("/compute") - .post(MediaPublisher.create(MediaType.TEXT_PLAIN, "0")); + .post(MediaPublisher.create(HttpMediaType.TEXT_PLAIN, "0")); assertEquals(412, response.status().code()); // NaN response = createClient(Main::errorHandling) .path("/compute") - .post(MediaPublisher.create(MediaType.TEXT_PLAIN, "aaa")); + .post(MediaPublisher.create(HttpMediaType.TEXT_PLAIN, "aaa")); assertEquals(400, response.status().code()); } diff --git a/examples/webserver/comment-aas/src/main/java/io/helidon/webserver/examples/comments/CommentsService.java b/examples/webserver/comment-aas/src/main/java/io/helidon/webserver/examples/comments/CommentsService.java index d2320c8033f..6463664fc50 100644 --- a/examples/webserver/comment-aas/src/main/java/io/helidon/webserver/examples/comments/CommentsService.java +++ b/examples/webserver/comment-aas/src/main/java/io/helidon/webserver/examples/comments/CommentsService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,7 +22,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.stream.Collectors; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerRequest; import io.helidon.webserver.ServerResponse; @@ -45,7 +45,7 @@ public void update(Routing.Rules routingRules) { private void handleListComments(ServerRequest req, ServerResponse resp) { String topic = req.path().param("topic"); - resp.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + resp.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); resp.send(listComments(topic)); } diff --git a/examples/webserver/comment-aas/src/main/java/io/helidon/webserver/examples/comments/Main.java b/examples/webserver/comment-aas/src/main/java/io/helidon/webserver/examples/comments/Main.java index 47782a56e79..ed8db1b82fc 100644 --- a/examples/webserver/comment-aas/src/main/java/io/helidon/webserver/examples/comments/Main.java +++ b/examples/webserver/comment-aas/src/main/java/io/helidon/webserver/examples/comments/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -35,6 +35,8 @@ */ public final class Main { + private static final Http.HeaderName USER_IDENTITY_HEADER = Http.Header.create("user-identity"); + private Main() { } @@ -66,7 +68,7 @@ static Routing createRouting(boolean acceptAnonymousUsers) { return Routing.builder() // Filter that translates user identity header into the contextual "user" information .any((req, res) -> { - String user = req.headers().first("user-identity") + String user = req.headers().first(USER_IDENTITY_HEADER) .or(() -> acceptAnonymousUsers ? Optional.of("anonymous") : Optional.empty()) .orElseThrow(() -> new HttpException("Anonymous access is forbidden!", Http.Status.FORBIDDEN_403)); diff --git a/examples/webserver/comment-aas/src/test/java/io/helidon/webserver/examples/comments/CommentsServiceTest.java b/examples/webserver/comment-aas/src/test/java/io/helidon/webserver/examples/comments/CommentsServiceTest.java index d6d4913adc1..d5c3eed6b57 100644 --- a/examples/webserver/comment-aas/src/test/java/io/helidon/webserver/examples/comments/CommentsServiceTest.java +++ b/examples/webserver/comment-aas/src/test/java/io/helidon/webserver/examples/comments/CommentsServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import java.nio.charset.StandardCharsets; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.webserver.Routing; import io.helidon.webserver.testsupport.MediaPublisher; import io.helidon.webserver.testsupport.TestClient; @@ -66,7 +66,7 @@ public void testRouting() throws Exception { response = TestClient.create(routing) .path("one") - .post(MediaPublisher.create(MediaType.TEXT_PLAIN, "aaa")); + .post(MediaPublisher.create(HttpMediaType.TEXT_PLAIN, "aaa")); assertEquals(Http.Status.OK_200, response.status()); response = TestClient.create(routing) diff --git a/examples/webserver/comment-aas/src/test/java/io/helidon/webserver/examples/comments/MainTest.java b/examples/webserver/comment-aas/src/test/java/io/helidon/webserver/examples/comments/MainTest.java index ebbc27d0d08..110b84bbae4 100644 --- a/examples/webserver/comment-aas/src/test/java/io/helidon/webserver/examples/comments/MainTest.java +++ b/examples/webserver/comment-aas/src/test/java/io/helidon/webserver/examples/comments/MainTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ package io.helidon.webserver.examples.comments; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.webserver.testsupport.MediaPublisher; import io.helidon.webserver.testsupport.TestClient; import io.helidon.webserver.testsupport.TestResponse; @@ -35,7 +35,7 @@ public class MainTest { public void argot() throws Exception { TestResponse response = TestClient.create(Main.createRouting(true)) .path("/comments/one") - .post(MediaPublisher.create(MediaType.TEXT_PLAIN, "Spring framework is the BEST!")); + .post(MediaPublisher.create(HttpMediaType.TEXT_PLAIN, "Spring framework is the BEST!")); assertEquals(Http.Status.NOT_ACCEPTABLE_406, response.status()); } diff --git a/examples/webserver/fault-tolerance/src/main/resources/logging.properties b/examples/webserver/fault-tolerance/src/main/resources/logging.properties index 59efab32c1b..5330b56bd27 100644 --- a/examples/webserver/fault-tolerance/src/main/resources/logging.properties +++ b/examples/webserver/fault-tolerance/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=INFO io.helidon.microprofile.server.level=INFO diff --git a/examples/webserver/jersey/src/main/resources/logging.properties b/examples/webserver/jersey/src/main/resources/logging.properties index c71d4b6eb0f..254c7ead135 100644 --- a/examples/webserver/jersey/src/main/resources/logging.properties +++ b/examples/webserver/jersey/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, 2021 Oracle and/or its affiliates. +# Copyright (c) 2017, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ #All attributes details -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details diff --git a/examples/webserver/multiport/src/main/resources/logging.properties b/examples/webserver/multiport/src/main/resources/logging.properties index aced7e48602..bfe40fbca31 100644 --- a/examples/webserver/multiport/src/main/resources/logging.properties +++ b/examples/webserver/multiport/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/webserver/opentracing/src/main/resources/logging.properties b/examples/webserver/opentracing/src/main/resources/logging.properties index 4833373add0..1126efa5ece 100644 --- a/examples/webserver/opentracing/src/main/resources/logging.properties +++ b/examples/webserver/opentracing/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2017, 2021 Oracle and/or its affiliates. +# Copyright (c) 2017, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ #All attributes details -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details diff --git a/examples/webserver/static-content/src/main/java/io/helidon/webserver/examples/staticcontent/Main.java b/examples/webserver/static-content/src/main/java/io/helidon/webserver/examples/staticcontent/Main.java index 62eebc9d8db..54f18670fea 100644 --- a/examples/webserver/static-content/src/main/java/io/helidon/webserver/examples/staticcontent/Main.java +++ b/examples/webserver/static-content/src/main/java/io/helidon/webserver/examples/staticcontent/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -27,6 +27,7 @@ * on the WEB page. */ public class Main { + private static final Http.HeaderValue UI_REDIRECT = Http.HeaderValue.createCached(Http.Header.LOCATION, "/ui"); private Main() { } @@ -41,7 +42,7 @@ static Routing createRouting() { .any("/", (req, res) -> { // showing the capability to run on any path, and redirecting from root res.status(Http.Status.MOVED_PERMANENTLY_301); - res.headers().put(Http.Header.LOCATION, "/ui"); + res.headers().set(UI_REDIRECT); res.send(); }) .register("/ui", new CounterService()) diff --git a/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/StreamingService.java b/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/StreamingService.java index 718e47fa9db..bcdeecf53eb 100644 --- a/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/StreamingService.java +++ b/examples/webserver/streaming/src/main/java/io/helidon/webserver/examples/streaming/StreamingService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. + * Copyright (c) 2018, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -60,7 +60,7 @@ private void upload(ServerRequest request, ServerResponse response) { private void download(ServerRequest request, ServerResponse response) { LOGGER.info("Entering download ..." + Thread.currentThread()); long length = filePath.toFile().length(); - response.headers().add("Content-Length", String.valueOf(length)); + response.headers().contentLength(length); response.send(new ServerFileReader(filePath)); LOGGER.info("Exiting download ..."); } diff --git a/examples/webserver/threadpool/pom.xml b/examples/webserver/threadpool/pom.xml index 3e528c07e89..fd3e29e2612 100644 --- a/examples/webserver/threadpool/pom.xml +++ b/examples/webserver/threadpool/pom.xml @@ -87,6 +87,15 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + diff --git a/examples/webserver/threadpool/src/main/resources/logging.properties b/examples/webserver/threadpool/src/main/resources/logging.properties index e5b54190393..f5fcf23ae91 100644 --- a/examples/webserver/threadpool/src/main/resources/logging.properties +++ b/examples/webserver/threadpool/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/webserver/tls/src/main/resources/logging.properties b/examples/webserver/tls/src/main/resources/logging.properties index 24de00b0293..ddbc8d6521d 100644 --- a/examples/webserver/tls/src/main/resources/logging.properties +++ b/examples/webserver/tls/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=INFO io.helidon.microprofile.server.level=INFO diff --git a/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/CommentService.java b/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/CommentService.java index c72dd7664af..ef22d9782a1 100644 --- a/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/CommentService.java +++ b/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/CommentService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -24,9 +24,15 @@ import java.util.concurrent.Flow; import java.util.stream.Collectors; +import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.Http; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; +import io.helidon.common.reactive.Single; import io.helidon.media.common.ContentWriters; +import io.helidon.media.common.MessageBodyWriter; +import io.helidon.media.common.MessageBodyWriterContext; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerRequest; import io.helidon.webserver.ServerResponse; @@ -47,24 +53,16 @@ public class CommentService implements Service { public void update(Routing.Rules routingRules) { routingRules.get((req, res) -> { // Register a publisher for comment - res.registerWriter(List.class, MediaType.TEXT_PLAIN.withCharset("UTF-8"), this::publish); + res.registerWriter(new CommentWriter()); req.next(); }) .get("/{" + ROOM_PATH_ID + "}", this::getComments) .post("/{" + ROOM_PATH_ID + "}", this::addComment); } - Flow.Publisher publish(List comments) { - String str = comments.stream() - .map(Comment::toString) - .collect(Collectors.joining("\n")); - return ContentWriters.charSequenceWriter(StandardCharsets.UTF_8) - .apply(str + "\n"); - } - private void getComments(ServerRequest req, ServerResponse resp) { String roomId = req.path().param(ROOM_PATH_ID); - //resp.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + //resp.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); List comments = getComments(roomId); resp.send(comments); } @@ -161,4 +159,39 @@ public int hashCode() { return result; } } + + private static final class CommentWriter implements MessageBodyWriter> { + private static final GenericType> SUPPORTED_TYPE = new GenericType>() {}; + private static final Http.HeaderValue CONTENT_TYPE_UTF_8 = Http.HeaderValue.createCached(Http.Header.CONTENT_TYPE, + HttpMediaType.PLAINTEXT_UTF_8.text()); + + @Override + public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { + if(type.equals(SUPPORTED_TYPE)) { + if (context.headers().isAccepted(MediaTypes.TEXT_PLAIN)) { + return PredicateResult.SUPPORTED; + } else { + return PredicateResult.COMPATIBLE; + } + } + return PredicateResult.NOT_SUPPORTED; + } + + @Override + public Flow.Publisher write(Single> single, + GenericType> type, + MessageBodyWriterContext context) { + context.headers().setIfAbsent(CONTENT_TYPE_UTF_8); + return single.flatMap(this::publish); + } + + Flow.Publisher publish(List comments) { + String str = comments.stream() + .map(Comment::toString) + .collect(Collectors.joining("\n")); + return ContentWriters.charSequenceWriter(StandardCharsets.UTF_8) + .apply(str + "\n"); + } + + } } diff --git a/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/Main.java b/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/Main.java index 66c92affb14..00fa1aee8b4 100644 --- a/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/Main.java +++ b/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/Main.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package io.helidon.webserver.examples.tutorial; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.webserver.Routing; import io.helidon.webserver.WebServer; import io.helidon.webserver.examples.tutorial.user.UserFilter; @@ -42,7 +42,7 @@ static Routing createRouting() { }) .register("/article", new CommentService()) .post("/mgmt/shutdown", (req, res) -> { - res.headers().contentType(MediaType.TEXT_PLAIN.withCharset("UTF-8")); + res.headers().contentType(HttpMediaType.PLAINTEXT_UTF_8); res.send("Shutting down TUTORIAL server. Good bye!\n"); // Use reactive API nature to stop the server AFTER the response was sent. res.whenSent().thenRun(() -> req.webServer().shutdown()); diff --git a/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/UpperXFilter.java b/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/UpperXFilter.java index 73b332ddef9..c2703aab626 100644 --- a/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/UpperXFilter.java +++ b/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/UpperXFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,18 +20,17 @@ import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.util.concurrent.Flow.Publisher; -import java.util.function.Function; import io.helidon.common.http.DataChunk; import io.helidon.common.reactive.Multi; - +import io.helidon.media.common.MessageBodyFilter; /** * All 'x' must be upper case. *

* This is a naive implementation. */ -public final class UpperXFilter implements Function, Publisher> { +public final class UpperXFilter implements MessageBodyFilter { private static final Charset CHARSET = StandardCharsets.US_ASCII; private static final byte LOWER_X = "x".getBytes(CHARSET)[0]; diff --git a/examples/webserver/tutorial/src/test/java/io/helidon/webserver/examples/tutorial/CommentServiceTest.java b/examples/webserver/tutorial/src/test/java/io/helidon/webserver/examples/tutorial/CommentServiceTest.java index 724a05a7009..0700a91b76a 100644 --- a/examples/webserver/tutorial/src/test/java/io/helidon/webserver/examples/tutorial/CommentServiceTest.java +++ b/examples/webserver/tutorial/src/test/java/io/helidon/webserver/examples/tutorial/CommentServiceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import java.nio.charset.StandardCharsets; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.webserver.Routing; import io.helidon.webserver.testsupport.MediaPublisher; import io.helidon.webserver.testsupport.TestClient; @@ -64,7 +64,7 @@ public void testRouting() throws Exception { // Add first comment response = TestClient.create(routing) .path("one") - .post(MediaPublisher.create(MediaType.TEXT_PLAIN, "aaa")); + .post(MediaPublisher.create(HttpMediaType.TEXT_PLAIN, "aaa")); assertEquals(Http.Status.OK_200, response.status()); response = TestClient.create(routing) .path("one") @@ -76,7 +76,7 @@ public void testRouting() throws Exception { // Add second comment response = TestClient.create(routing) .path("one") - .post(MediaPublisher.create(MediaType.TEXT_PLAIN, "bbb")); + .post(MediaPublisher.create(HttpMediaType.TEXT_PLAIN, "bbb")); assertEquals(Http.Status.OK_200, response.status()); response = TestClient.create(routing) .path("one") diff --git a/examples/webserver/tutorial/src/test/java/io/helidon/webserver/examples/tutorial/user/UserFilterTest.java b/examples/webserver/tutorial/src/test/java/io/helidon/webserver/examples/tutorial/user/UserFilterTest.java index 815d2d64551..86b7f60c23d 100644 --- a/examples/webserver/tutorial/src/test/java/io/helidon/webserver/examples/tutorial/user/UserFilterTest.java +++ b/examples/webserver/tutorial/src/test/java/io/helidon/webserver/examples/tutorial/user/UserFilterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ import java.util.concurrent.atomic.AtomicReference; +import io.helidon.common.http.Http; import io.helidon.webserver.Routing; import io.helidon.webserver.testsupport.TestClient; import io.helidon.webserver.testsupport.TestResponse; @@ -49,7 +50,7 @@ public void filter() throws Exception { assertEquals(User.ANONYMOUS, userReference.get()); response = TestClient.create(routing) .path("/") - .header("Cookie", "Unauthenticated-User-Alias=Foo") + .header(Http.Header.COOKIE, "Unauthenticated-User-Alias=Foo") .get(); assertEquals("Foo", userReference.get().getAlias()); } diff --git a/examples/webserver/websocket/src/main/resources/logging.properties b/examples/webserver/websocket/src/main/resources/logging.properties index 24de00b0293..ddbc8d6521d 100644 --- a/examples/webserver/websocket/src/main/resources/logging.properties +++ b/examples/webserver/websocket/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=INFO io.helidon.microprofile.server.level=INFO From 19f4eabb39dd11f64734dd331caaafa44e61af8b Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:55:35 +0200 Subject: [PATCH 24/54] Tests and TCK refactored to new common. --- .../src/test/resources/logging.properties | 4 ++-- .../lra/tck/CoordinatorDeployer.java | 4 ++-- .../src/main/resources/logging.properties | 4 ++-- tests/apps/bookstore/bookstore-se/pom.xml | 9 +++++++++ .../src/main/resources/logging.properties | 4 ++-- .../io/helidon/tests/bookstore/MainTest.java | 16 ++++++++-------- .../src/main/resources/logging.properties | 2 +- .../src/test/resources/logging.properties | 4 ++-- .../src/test/resources/logging.properties | 4 ++-- .../src/test/resources/logging.properties | 4 ++-- .../src/test/resources/logging.properties | 4 ++-- .../src/main/resources/logging.properties | 6 +++--- .../src/main/resources/logging.properties | 2 +- .../functional/requestscopecdi/SecretTest.java | 6 +++--- .../src/test/resources/logging.properties | 4 ++-- .../functional/requestscope/TenantContext.java | 6 ++++-- .../src/test/resources/logging.properties | 4 ++-- .../dbclient/appl/tools/ExitService.java | 4 ++-- .../appl/src/main/resources/logging.properties | 4 ++-- .../appl/src/test/resources/logging.properties | 4 ++-- .../jms/src/test/resources/logging.properties | 4 ++-- .../appl/src/main/resources/logging.properties | 4 ++-- .../appl/src/test/resources/logging.properties | 4 ++-- .../src/test/resources/logging.properties | 2 +- .../src/main/resources/logging.properties | 2 +- .../tests/integration/gh3246/Gh3246Test.java | 2 +- .../src/test/resources/logging.properties | 4 ++-- .../native-image/mp-1/logging.properties | 6 +++--- .../mp-1/src/main/resources/logging.properties | 4 ++-- .../mp-2/src/main/resources/logging.properties | 4 ++-- .../mp-3/src/main/resources/logging.properties | 4 ++-- .../nativeimage/se1/WebClientService.java | 4 ++-- .../se-1/src/main/resources/logging.properties | 4 ++-- .../src/main/resources/logging.properties | 4 ++-- .../src/main/resources/logging.properties | 4 ++-- .../src/main/resources/logging.properties | 4 ++-- .../src/main/resources/logging.properties | 4 ++-- .../src/main/resources/logging.properties | 2 +- .../tools/example/LifeCycleService.java | 4 ++-- .../src/main/resources/logging.properties | 4 ++-- .../src/test/resources/logging.properties | 4 ++-- .../mp/src/main/resources/logging.properties | 4 ++-- .../se/src/main/resources/logging.properties | 4 ++-- .../integration/webclient/GreetService.java | 6 +++--- .../tests/integration/webclient/FormTest.java | 18 +++++++++--------- .../integration/webclient/HeaderTest.java | 4 ++-- .../integration/webclient/RequestTest.java | 4 ++-- .../integration/webclient/UriPartTest.java | 4 ++-- 48 files changed, 116 insertions(+), 105 deletions(-) diff --git a/microprofile/tests/tck/tck-config/src/test/resources/logging.properties b/microprofile/tests/tck/tck-config/src/test/resources/logging.properties index 877a19fb363..cd700592fb6 100644 --- a/microprofile/tests/tck/tck-config/src/test/resources/logging.properties +++ b/microprofile/tests/tck/tck-config/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2020 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -14,7 +14,7 @@ # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=[%1$tc] %4$s: %2$s - %5$s %6$s%n .level=WARNING io.helidon.microprofile.config.level=INFO diff --git a/microprofile/tests/tck/tck-lra/src/test/java/io/helidon/microprofile/lra/tck/CoordinatorDeployer.java b/microprofile/tests/tck/tck-lra/src/test/java/io/helidon/microprofile/lra/tck/CoordinatorDeployer.java index 948f367f65c..36fe8f0449a 100644 --- a/microprofile/tests/tck/tck-lra/src/test/java/io/helidon/microprofile/lra/tck/CoordinatorDeployer.java +++ b/microprofile/tests/tck/tck-lra/src/test/java/io/helidon/microprofile/lra/tck/CoordinatorDeployer.java @@ -21,7 +21,7 @@ import java.util.concurrent.atomic.AtomicInteger; import java.util.logging.Logger; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Single; import io.helidon.config.ConfigSources; import io.helidon.config.mp.MpConfigSources; @@ -55,7 +55,7 @@ public void beforeStart(@Observes BeforeStart event, Container container) throws containerConfig.addConfigBuilderConsumer(configBuilder -> { var is = CoordinatorService.class.getResourceAsStream("/application.yaml"); - configBuilder.withSources(MpConfigSources.create(ConfigSources.create(is, MediaType.APPLICATION_X_YAML.toString())), + configBuilder.withSources(MpConfigSources.create(ConfigSources.create(is, MediaTypes.APPLICATION_X_YAML)), MpConfigSources.create(Map.of( // Force client to use random port first time with 0 // reuse port second time(TckRecoveryTests does redeploy) diff --git a/tests/apps/bookstore/bookstore-mp/src/main/resources/logging.properties b/tests/apps/bookstore/bookstore-mp/src/main/resources/logging.properties index 4029baf371f..a540d5bc6ab 100644 --- a/tests/apps/bookstore/bookstore-mp/src/main/resources/logging.properties +++ b/tests/apps/bookstore/bookstore-mp/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/apps/bookstore/bookstore-se/pom.xml b/tests/apps/bookstore/bookstore-se/pom.xml index 6242e60801d..f7d7a00f7c4 100644 --- a/tests/apps/bookstore/bookstore-se/pom.xml +++ b/tests/apps/bookstore/bookstore-se/pom.xml @@ -99,6 +99,15 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + diff --git a/tests/apps/bookstore/bookstore-se/src/main/resources/logging.properties b/tests/apps/bookstore/bookstore-se/src/main/resources/logging.properties index f9dfd25b9da..f9bcbc4fc4b 100644 --- a/tests/apps/bookstore/bookstore-se/src/main/resources/logging.properties +++ b/tests/apps/bookstore/bookstore-se/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java b/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java index c888d06947d..0426b10d8da 100644 --- a/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java +++ b/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,7 +31,7 @@ import java.util.logging.Logger; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientResponse; @@ -371,7 +371,7 @@ private void runMetricsAndHealthTest(String edition, String jsonLibrary, boolean // Get Prometheus style metrics webClient.get() - .accept(MediaType.WILDCARD) + .accept(MediaTypes.WILDCARD) .path("/metrics") .request(String.class) // Make sure we got prometheus metrics @@ -381,7 +381,7 @@ private void runMetricsAndHealthTest(String edition, String jsonLibrary, boolean // Get JSON encoded metrics webClient.get() - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path("/metrics") .request(JsonObject.class) // Makes sure we got JSON metrics @@ -393,7 +393,7 @@ private void runMetricsAndHealthTest(String edition, String jsonLibrary, boolean // Get JSON encoded metrics/base webClient.get() - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path("/metrics/base") .request(JsonObject.class) // Makes sure we got JSON metrics @@ -405,7 +405,7 @@ private void runMetricsAndHealthTest(String edition, String jsonLibrary, boolean // Get JSON encoded health check webClient.get() - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path("/health") .request(JsonObject.class) .thenAccept(it -> { @@ -435,7 +435,7 @@ void routing(String edition) throws Exception { .build(); webClient.get() - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .skipUriEncoding() .path("/boo%6bs") .request() @@ -451,7 +451,7 @@ void routing(String edition) throws Exception { .get(); webClient.get() - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path("/badurl") .request() .thenAccept(it -> assertThat("Checking encode URL response", it.status(), is(Http.Status.NOT_FOUND_404))) diff --git a/tests/functional/config-profiles/src/main/resources/logging.properties b/tests/functional/config-profiles/src/main/resources/logging.properties index cd238eb6615..ff741e62ce6 100644 --- a/tests/functional/config-profiles/src/main/resources/logging.properties +++ b/tests/functional/config-profiles/src/main/resources/logging.properties @@ -3,7 +3,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/functional/context-propagation/src/test/resources/logging.properties b/tests/functional/context-propagation/src/test/resources/logging.properties index 10bbbfb7851..320f59c661c 100644 --- a/tests/functional/context-propagation/src/test/resources/logging.properties +++ b/tests/functional/context-propagation/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/functional/jax-rs-multiple-apps/src/test/resources/logging.properties b/tests/functional/jax-rs-multiple-apps/src/test/resources/logging.properties index b38f5227356..d4d2bf10e99 100644 --- a/tests/functional/jax-rs-multiple-apps/src/test/resources/logging.properties +++ b/tests/functional/jax-rs-multiple-apps/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/functional/jax-rs-subresource/src/test/resources/logging.properties b/tests/functional/jax-rs-subresource/src/test/resources/logging.properties index 10bbbfb7851..320f59c661c 100644 --- a/tests/functional/jax-rs-subresource/src/test/resources/logging.properties +++ b/tests/functional/jax-rs-subresource/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2019, 2021 Oracle and/or its affiliates. +# Copyright (c) 2019, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/functional/mp-compression/src/test/resources/logging.properties b/tests/functional/mp-compression/src/test/resources/logging.properties index 388084ada8e..7c9c1f137c2 100644 --- a/tests/functional/mp-compression/src/test/resources/logging.properties +++ b/tests/functional/mp-compression/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/functional/mp-synthetic-app/src/main/resources/logging.properties b/tests/functional/mp-synthetic-app/src/main/resources/logging.properties index b2f1ba7d8fe..e0b7f52f4b3 100644 --- a/tests/functional/mp-synthetic-app/src/main/resources/logging.properties +++ b/tests/functional/mp-synthetic-app/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -13,7 +13,7 @@ # See the License for the specific language governing permissions and # limitations under the License. # -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread # java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n # Global logging level. Can be overridden by specific loggers @@ -22,7 +22,7 @@ io.helidon.level=INFO # Helidon Web Server has a custom log formatter that extends SimpleFormatter. # It replaces "!thread!" with the current thread name #java.util.logging.ConsoleHandler.level=ALL -io.helidon.common.HelidonConsoleHandler.level=ALL +io.helidon.logging.jul.HelidonConsoleHandler.level=ALL java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n AUDIT.level=FINEST io.helidon.webserver.level=WARNING diff --git a/tests/functional/param-converter-provider/src/main/resources/logging.properties b/tests/functional/param-converter-provider/src/main/resources/logging.properties index 351c4e65b87..98bde23f08f 100644 --- a/tests/functional/param-converter-provider/src/main/resources/logging.properties +++ b/tests/functional/param-converter-provider/src/main/resources/logging.properties @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/functional/request-scope-cdi/src/test/java/io/helidon/tests/functional/requestscopecdi/SecretTest.java b/tests/functional/request-scope-cdi/src/test/java/io/helidon/tests/functional/requestscopecdi/SecretTest.java index 7c9abb43d23..f3753c18ade 100644 --- a/tests/functional/request-scope-cdi/src/test/java/io/helidon/tests/functional/requestscopecdi/SecretTest.java +++ b/tests/functional/request-scope-cdi/src/test/java/io/helidon/tests/functional/requestscopecdi/SecretTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package io.helidon.tests.functional.requestscopecdi; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.microprofile.tests.junit5.HelidonTest; import io.netty.handler.codec.http.HttpResponseStatus; @@ -40,7 +40,7 @@ class SecretTest { public void testSecrets() { Response r = baseTarget.path("greet") .request() - .accept(MediaType.APPLICATION_JSON.toString()) + .accept(MediaTypes.APPLICATION_JSON.text()) .get(); assertThat(r.getStatus(), is(HttpResponseStatus.OK.code())); JsonObject o = r.readEntity(JsonObject.class); diff --git a/tests/functional/request-scope-cdi/src/test/resources/logging.properties b/tests/functional/request-scope-cdi/src/test/resources/logging.properties index 97e3945355b..2c626bfea8d 100644 --- a/tests/functional/request-scope-cdi/src/test/resources/logging.properties +++ b/tests/functional/request-scope-cdi/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/functional/request-scope/src/main/java/io/helidon/tests/functional/requestscope/TenantContext.java b/tests/functional/request-scope/src/main/java/io/helidon/tests/functional/requestscope/TenantContext.java index 7273e49970d..396e6746f51 100644 --- a/tests/functional/request-scope/src/main/java/io/helidon/tests/functional/requestscope/TenantContext.java +++ b/tests/functional/request-scope/src/main/java/io/helidon/tests/functional/requestscope/TenantContext.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package io.helidon.tests.functional.requestscope; +import io.helidon.common.http.Http; import io.helidon.webserver.ServerRequest; import jakarta.annotation.PostConstruct; @@ -23,6 +24,7 @@ @RequestScoped public class TenantContext { + private static final Http.HeaderName TENANT_ID = Http.Header.create("x-tenant-id"); @Context private ServerRequest request; @@ -38,6 +40,6 @@ public String getTenantId() { */ @PostConstruct public void init() { - tenantId = request.headers().value("x-tenant-id").orElse(null); + tenantId = request.headers().value(TENANT_ID).orElse(null); } } diff --git a/tests/functional/request-scope/src/test/resources/logging.properties b/tests/functional/request-scope/src/test/resources/logging.properties index e688e921bae..a913db304b4 100644 --- a/tests/functional/request-scope/src/test/resources/logging.properties +++ b/tests/functional/request-scope/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/integration/dbclient/appl/src/main/java/io/helidon/tests/integration/dbclient/appl/tools/ExitService.java b/tests/integration/dbclient/appl/src/main/java/io/helidon/tests/integration/dbclient/appl/tools/ExitService.java index 2628c07d3b3..0bd20c124ad 100644 --- a/tests/integration/dbclient/appl/src/main/java/io/helidon/tests/integration/dbclient/appl/tools/ExitService.java +++ b/tests/integration/dbclient/appl/src/main/java/io/helidon/tests/integration/dbclient/appl/tools/ExitService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import java.util.logging.Logger; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.tests.integration.dbclient.appl.InitService; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerRequest; diff --git a/tests/integration/dbclient/appl/src/main/resources/logging.properties b/tests/integration/dbclient/appl/src/main/resources/logging.properties index ff075c651c8..005ede706e0 100644 --- a/tests/integration/dbclient/appl/src/main/resources/logging.properties +++ b/tests/integration/dbclient/appl/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n # Global logging level. Can be overridden by specific loggers diff --git a/tests/integration/dbclient/appl/src/test/resources/logging.properties b/tests/integration/dbclient/appl/src/test/resources/logging.properties index 9f94ddf8773..dea5a80aa9d 100644 --- a/tests/integration/dbclient/appl/src/test/resources/logging.properties +++ b/tests/integration/dbclient/appl/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n # Global logging level. Can be overridden by specific loggers diff --git a/tests/integration/jms/src/test/resources/logging.properties b/tests/integration/jms/src/test/resources/logging.properties index 793e77550ef..19bfd4a627e 100644 --- a/tests/integration/jms/src/test/resources/logging.properties +++ b/tests/integration/jms/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # #All attributes details -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details diff --git a/tests/integration/jpa/appl/src/main/resources/logging.properties b/tests/integration/jpa/appl/src/main/resources/logging.properties index 5795e7cecfd..e014fcc8f3b 100644 --- a/tests/integration/jpa/appl/src/main/resources/logging.properties +++ b/tests/integration/jpa/appl/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n # Global logging level. Can be overridden by specific loggers diff --git a/tests/integration/jpa/appl/src/test/resources/logging.properties b/tests/integration/jpa/appl/src/test/resources/logging.properties index 524f3e5831e..ddb13af28c3 100644 --- a/tests/integration/jpa/appl/src/test/resources/logging.properties +++ b/tests/integration/jpa/appl/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/integration/kafka/src/test/resources/logging.properties b/tests/integration/kafka/src/test/resources/logging.properties index 72944511151..aefa3d3e1ed 100644 --- a/tests/integration/kafka/src/test/resources/logging.properties +++ b/tests/integration/kafka/src/test/resources/logging.properties @@ -15,7 +15,7 @@ # #All attributes details -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n #All log level details diff --git a/tests/integration/mp-bean-validation/src/main/resources/logging.properties b/tests/integration/mp-bean-validation/src/main/resources/logging.properties index 930fbd49781..b5e2c644b3d 100644 --- a/tests/integration/mp-bean-validation/src/main/resources/logging.properties +++ b/tests/integration/mp-bean-validation/src/main/resources/logging.properties @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/integration/mp-gh-3246/src/test/java/io/helidon/tests/integration/gh3246/Gh3246Test.java b/tests/integration/mp-gh-3246/src/test/java/io/helidon/tests/integration/gh3246/Gh3246Test.java index de23f43cd9c..e4315d8ce7e 100644 --- a/tests/integration/mp-gh-3246/src/test/java/io/helidon/tests/integration/gh3246/Gh3246Test.java +++ b/tests/integration/mp-gh-3246/src/test/java/io/helidon/tests/integration/gh3246/Gh3246Test.java @@ -86,7 +86,7 @@ void testSecuredCallout() { String response = webTarget.path("/test/secured") .queryParam("port", port) .request() - .header(Http.Header.AUTHORIZATION, "Bearer " + tokenContent) + .header(Http.Header.AUTHORIZATION.defaultCase(), "Bearer " + tokenContent) .get(String.class); assertThat(response, is("hello")); diff --git a/tests/integration/mp-grpc/src/test/resources/logging.properties b/tests/integration/mp-grpc/src/test/resources/logging.properties index fc2a665a625..d593130c85f 100644 --- a/tests/integration/mp-grpc/src/test/resources/logging.properties +++ b/tests/integration/mp-grpc/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/integration/native-image/mp-1/logging.properties b/tests/integration/native-image/mp-1/logging.properties index 8efee87e0d5..ecfcebbe4df 100644 --- a/tests/integration/native-image/mp-1/logging.properties +++ b/tests/integration/native-image/mp-1/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2020 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread # java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n @@ -31,7 +31,7 @@ io.helidon.level=INFO # Helidon Web Server has a custom log formatter that extends SimpleFormatter. # It replaces "!thread!" with the current thread name #java.util.logging.ConsoleHandler.level=ALL -io.helidon.common.HelidonConsoleHandler.level=ALL +io.helidon.logging.jul.HelidonConsoleHandler.level=ALL java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n AUDIT.level=FINEST io.helidon.webserver.level=WARNING diff --git a/tests/integration/native-image/mp-1/src/main/resources/logging.properties b/tests/integration/native-image/mp-1/src/main/resources/logging.properties index 917e8ff7a89..5dcaa1c1296 100644 --- a/tests/integration/native-image/mp-1/src/main/resources/logging.properties +++ b/tests/integration/native-image/mp-1/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/integration/native-image/mp-2/src/main/resources/logging.properties b/tests/integration/native-image/mp-2/src/main/resources/logging.properties index 10aaaf88feb..674f6e56187 100644 --- a/tests/integration/native-image/mp-2/src/main/resources/logging.properties +++ b/tests/integration/native-image/mp-2/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ # Example Logging Configuration File # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n # Global logging level. Can be overridden by specific loggers diff --git a/tests/integration/native-image/mp-3/src/main/resources/logging.properties b/tests/integration/native-image/mp-3/src/main/resources/logging.properties index 6deebe47fd5..96a1f6f23f3 100644 --- a/tests/integration/native-image/mp-3/src/main/resources/logging.properties +++ b/tests/integration/native-image/mp-3/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/WebClientService.java b/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/WebClientService.java index 3033646a3ae..d45c01744d0 100644 --- a/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/WebClientService.java +++ b/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/WebClientService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -29,7 +29,7 @@ import io.helidon.common.context.Context; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Single; import io.helidon.config.Config; import io.helidon.media.jsonb.JsonbSupport; diff --git a/tests/integration/native-image/se-1/src/main/resources/logging.properties b/tests/integration/native-image/se-1/src/main/resources/logging.properties index ff100daadc8..71643af046c 100644 --- a/tests/integration/native-image/se-1/src/main/resources/logging.properties +++ b/tests/integration/native-image/se-1/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/integration/security/gh1487/src/main/resources/logging.properties b/tests/integration/security/gh1487/src/main/resources/logging.properties index 9d84702edcc..b4dbaa07d92 100644 --- a/tests/integration/security/gh1487/src/main/resources/logging.properties +++ b/tests/integration/security/gh1487/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # Global logging level. Can be overridden by specific loggers .level=WARNING diff --git a/tests/integration/security/gh2297/src/main/resources/logging.properties b/tests/integration/security/gh2297/src/main/resources/logging.properties index d9065fed13b..47bda3aa5e8 100644 --- a/tests/integration/security/gh2297/src/main/resources/logging.properties +++ b/tests/integration/security/gh2297/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/integration/security/gh2772/src/main/resources/logging.properties b/tests/integration/security/gh2772/src/main/resources/logging.properties index 8c04fec0503..0cb1430847e 100644 --- a/tests/integration/security/gh2772/src/main/resources/logging.properties +++ b/tests/integration/security/gh2772/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/integration/security/path-params/src/main/resources/logging.properties b/tests/integration/security/path-params/src/main/resources/logging.properties index 97346889169..09e48dca804 100644 --- a/tests/integration/security/path-params/src/main/resources/logging.properties +++ b/tests/integration/security/path-params/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/integration/security/security-response-mapper/src/main/resources/logging.properties b/tests/integration/security/security-response-mapper/src/main/resources/logging.properties index 998f7fd29e2..dffe5053f85 100644 --- a/tests/integration/security/security-response-mapper/src/main/resources/logging.properties +++ b/tests/integration/security/security-response-mapper/src/main/resources/logging.properties @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # Global logging level. Can be overridden by specific loggers .level=WARNING diff --git a/tests/integration/tools/example/src/main/java/io/helidon/tests/integration/tools/example/LifeCycleService.java b/tests/integration/tools/example/src/main/java/io/helidon/tests/integration/tools/example/LifeCycleService.java index 5fd27526d47..27997e4e397 100644 --- a/tests/integration/tools/example/src/main/java/io/helidon/tests/integration/tools/example/LifeCycleService.java +++ b/tests/integration/tools/example/src/main/java/io/helidon/tests/integration/tools/example/LifeCycleService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import jakarta.json.Json; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.dbclient.DbClient; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerRequest; diff --git a/tests/integration/tools/example/src/main/resources/logging.properties b/tests/integration/tools/example/src/main/resources/logging.properties index 865ba5a9488..7ae84fa0a89 100644 --- a/tests/integration/tools/example/src/main/resources/logging.properties +++ b/tests/integration/tools/example/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n # Global logging level. Can be overridden by specific loggers diff --git a/tests/integration/tools/example/src/test/resources/logging.properties b/tests/integration/tools/example/src/test/resources/logging.properties index 865ba5a9488..7ae84fa0a89 100644 --- a/tests/integration/tools/example/src/test/resources/logging.properties +++ b/tests/integration/tools/example/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n # Global logging level. Can be overridden by specific loggers diff --git a/tests/integration/vault/mp/src/main/resources/logging.properties b/tests/integration/vault/mp/src/main/resources/logging.properties index d0317654f47..f8802f90626 100644 --- a/tests/integration/vault/mp/src/main/resources/logging.properties +++ b/tests/integration/vault/mp/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/integration/vault/se/src/main/resources/logging.properties b/tests/integration/vault/se/src/main/resources/logging.properties index 4c6501df2d6..24a189ad914 100644 --- a/tests/integration/vault/se/src/main/resources/logging.properties +++ b/tests/integration/vault/se/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020, 2021 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -15,7 +15,7 @@ # # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/tests/integration/webclient/src/main/java/io/helidon/tests/integration/webclient/GreetService.java b/tests/integration/webclient/src/main/java/io/helidon/tests/integration/webclient/GreetService.java index 375902d0f62..3925f8b810e 100644 --- a/tests/integration/webclient/src/main/java/io/helidon/tests/integration/webclient/GreetService.java +++ b/tests/integration/webclient/src/main/java/io/helidon/tests/integration/webclient/GreetService.java @@ -29,8 +29,8 @@ import io.helidon.common.context.Context; import io.helidon.common.context.Contexts; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.FormParams; import io.helidon.common.http.Http; +import io.helidon.common.parameters.Parameters; import io.helidon.common.reactive.Multi; import io.helidon.config.Config; import io.helidon.security.SecurityContext; @@ -188,13 +188,13 @@ private void redirectInfinite(ServerRequest serverRequest, ServerResponse respon } private void form(ServerRequest req, ServerResponse res) { - req.content().as(FormParams.class) + req.content().as(Parameters.class) .thenApply(form -> "Hi " + form.first("name").orElse("unknown")) .thenAccept(res::send); } private void formContent(ServerRequest req, ServerResponse res) { - req.content().as(FormParams.class) + req.content().as(Parameters.class) .thenAccept(res::send); } diff --git a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/FormTest.java b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/FormTest.java index 4a87f691f23..0edc3bff29d 100644 --- a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/FormTest.java +++ b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/FormTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,8 +15,8 @@ */ package io.helidon.tests.integration.webclient; -import io.helidon.common.http.FormParams; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; +import io.helidon.common.parameters.Parameters; import org.junit.jupiter.api.Test; @@ -30,7 +30,7 @@ */ public class FormTest extends TestParent { - private static final FormParams TEST_FORM = FormParams.builder() + private static final Parameters TEST_FORM = Parameters.builder("webclient-form") .add("name", "David Tester") .build(); @@ -39,7 +39,7 @@ public class FormTest extends TestParent { private static final String NO_VALUE = "noValue"; private static final String SPECIAL_VALUE = "some &@#/ special value"; - private static final FormParams ADVANCED_TEST_FORM = FormParams.builder() + private static final Parameters ADVANCED_TEST_FORM = Parameters.builder("webclient-form") .add(SPECIAL, SPECIAL_VALUE) .add(MULTIPLE, "value1", "value2") .add(NO_VALUE) @@ -58,7 +58,7 @@ public void testHelloWorld() { public void testSpecificContentType() { webClient.post() .path("/form") - .contentType(MediaType.TEXT_PLAIN) + .contentType(MediaTypes.TEXT_PLAIN) .submit(TEST_FORM, String.class) .thenAccept(resp -> assertThat(resp, is("Hi David Tester"))) .await(); @@ -68,7 +68,7 @@ public void testSpecificContentType() { public void testSpecificContentTypeIncorrect() { Exception ex = assertThrows(IllegalStateException.class, () -> webClient.post() .path("/form") - .contentType(MediaType.APPLICATION_ATOM_XML) + .contentType(MediaTypes.APPLICATION_ATOM_XML) .submit(TEST_FORM).await()); assertThat(ex.getCause().getMessage(), @@ -77,9 +77,9 @@ public void testSpecificContentTypeIncorrect() { @Test public void testFormContent() { - FormParams received = webClient.post() + Parameters received = webClient.post() .path("/form/content") - .submit(ADVANCED_TEST_FORM, FormParams.class) + .submit(ADVANCED_TEST_FORM, Parameters.class) .await(); assertThat(received.all(SPECIAL), is(ADVANCED_TEST_FORM.all(SPECIAL))); diff --git a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/HeaderTest.java b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/HeaderTest.java index 63048044361..660e913ce8c 100644 --- a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/HeaderTest.java +++ b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/HeaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -114,7 +114,7 @@ public Single request(WebClientServiceRequest request) @Override public Single response(WebClientRequestBuilder.ClientRequest request, WebClientServiceResponse response) { - List userAgent = request.headers().all(Http.Header.USER_AGENT); + List userAgent = request.headers().all(Http.Header.USER_AGENT, List::of); assertThat(userAgent, hasSize(1)); assertThat(userAgent, contains(user)); return Single.just(response); diff --git a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/RequestTest.java b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/RequestTest.java index dc880105c4d..38e3bc810ff 100644 --- a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/RequestTest.java +++ b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/RequestTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -40,7 +40,7 @@ /** * Tests of basic requests. */ -public class RequestTest extends TestParent { +class RequestTest extends TestParent { private static final JsonBuilderFactory JSON_BUILDER = Json.createBuilderFactory(Collections.emptyMap()); private static final JsonObject JSON_NEW_GREETING; diff --git a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/UriPartTest.java b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/UriPartTest.java index 574f1ccedf0..0fffec28db1 100644 --- a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/UriPartTest.java +++ b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/UriPartTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -131,7 +131,7 @@ public void testQueryNotDoubleEncoded() { @Test public void testPathNotDecoded() { WebClient webClient = createNewClient(request -> { - assertThat(request.path().toRawString(), is("/greet/path%26")); + assertThat(request.path().rawPath(), is("/greet/path%26")); return Single.just(request); }); assertThrows(CompletionException.class, () -> webClient.get() From d3aa5d9f1fbbb9a509c2c88df7359a9407f08b79 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:56:03 +0200 Subject: [PATCH 25/54] SE Media refactored to new common. --- .../common/MessageBodyReadableContent.java | 2 +- .../common/MessageBodyWriterContext.java | 65 ---- .../media/jsonb/JsonbBodyStreamWriter.java | 2 +- .../helidon/media/jsonb/JsonbBodyWriter.java | 2 +- .../media/jsonb/JsonbEsBodyStreamWriter.java | 17 +- .../media/jsonb/JsonbNdBodyStreamWriter.java | 13 +- .../media/jsonb/TestJsonBindingSupport.java | 9 +- .../media/jsonp/JsonpBodyStreamWriter.java | 2 +- .../helidon/media/jsonp/JsonpBodyWriter.java | 2 +- .../media/jsonp/JsonpEsBodyStreamWriter.java | 14 +- .../media/jsonp/JsonpNdBodyStreamWriter.java | 14 +- .../helidon/media/jsonp/JsonSupportTest.java | 39 +- .../media/jsonp/JsonpStreamWriterTest.java | 6 +- .../io/helidon/media/multipart/BodyPart.java | 4 +- .../multipart/BodyPartBodyStreamReader.java | 4 +- .../multipart/BodyPartBodyStreamWriter.java | 4 +- .../media/multipart/BodyPartHeaders.java | 16 +- .../media/multipart/ContentDisposition.java | 352 ------------------ .../media/multipart/FileFormParams.java | 7 +- .../media/multipart/MultiPartBodyWriter.java | 16 +- .../media/multipart/MultiPartDecoder.java | 7 +- .../multipart/ReadableBodyPartHeaders.java | 113 ++++-- .../media/multipart/VirtualBuffer.java | 4 +- .../media/multipart/WriteableBodyPart.java | 16 +- .../multipart/WriteableBodyPartHeaders.java | 164 +++++--- .../media/multipart/BodyPartHeadersTest.java | 38 +- .../helidon/media/multipart/BodyPartTest.java | 3 +- .../multipart/ContentDispositionTest.java | 15 +- .../media/multipart/MimeParserTest.java | 2 - .../media/multipart/MultiPartDecoderTest.java | 29 +- .../media/multipart/MultiPartEncoderTest.java | 2 +- .../io/helidon/media/multipart/Utils.java | 35 ++ .../media/multipart/VirtualBufferTest.java | 3 +- 33 files changed, 373 insertions(+), 648 deletions(-) delete mode 100644 media/multipart/src/main/java/io/helidon/media/multipart/ContentDisposition.java create mode 100644 media/multipart/src/test/java/io/helidon/media/multipart/Utils.java diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyReadableContent.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyReadableContent.java index 5c648937f89..dd91c05c987 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyReadableContent.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyReadableContent.java @@ -29,7 +29,7 @@ */ @SuppressWarnings("deprecation") public final class MessageBodyReadableContent - implements MessageBodyReaders, MessageBodyFilters, MessageBodyContent { + implements MessageBodyReaders, MessageBodyFilters, MessageBodyContent, Multi { private final Publisher publisher; private final MessageBodyReaderContext context; diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java index 8156af11181..10e1600faee 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java @@ -216,71 +216,6 @@ public MessageBodyWriterContext registerWriter(MessageBodyStreamWriter writer swriters.registerFirst(writer); return this; } - /** - * Registers a writer function with a given type. - * - * @param entity type - * @param type class representing the type supported by this writer - * @param function writer function - * @return this {@code MessageBodyWriteableContent} instance - * @deprecated since 2.0.0, use {@link #registerWriter(MessageBodyWriter) } instead - */ - @Deprecated - public MessageBodyWriterContext registerWriter(Class type, Function> function) { - writers.registerFirst(new WriterAdapter<>(function, type, null)); - return this; - } - - /** - * Registers a writer function with a given type and media type. - * - * @param entity type - * @param type class representing the type supported by this writer - * @param contentType the media type - * @param function writer function - * @return this {@code MessageBodyWriteableContent} instance - * @deprecated since 2.0.0, use {@link #registerWriter(MessageBodyWriter) } instead - */ - @Deprecated - public MessageBodyWriterContext registerWriter(Class type, HttpMediaType contentType, - Function> function) { - - writers.registerFirst(new WriterAdapter<>(function, type, contentType)); - return this; - } - - /** - * Registers a writer function with a given predicate. - * - * @param entity type - * @param accept the object predicate - * @param function writer function - * @return this {@code MessageBodyWriteableContent} instance - * @deprecated since 2.0.0 use {@link #registerWriter(MessageBodyWriter) } instead - */ - @Deprecated - public MessageBodyWriterContext registerWriter(Predicate accept, Function> function) { - writers.registerFirst(new WriterAdapter<>(function, accept, null)); - return this; - } - - /** - * Registers a writer function with a given predicate and media type. - * - * @param entity type - * @param accept the object predicate - * @param contentType the media type - * @param function writer function - * @return this {@code MessageBodyWriteableContent} instance - * @deprecated since 2.0.0, use {@link #registerWriter(MessageBodyWriter) } instead - */ - @Deprecated - public MessageBodyWriterContext registerWriter(Predicate accept, HttpMediaType contentType, - Function> function) { - - writers.registerFirst(new WriterAdapter<>(function, accept, contentType)); - return this; - } /** * Convert a given input publisher into HTTP payload by selecting a diff --git a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyStreamWriter.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyStreamWriter.java index ac5cf780a81..678dd42f64e 100644 --- a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyStreamWriter.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyStreamWriter.java @@ -60,7 +60,7 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont @Override public Multi write(Flow.Publisher publisher, GenericType type, MessageBodyWriterContext context) { - MediaType contentType = context.findAccepted(MediaType.JSON_PREDICATE, MediaType.APPLICATION_JSON); + HttpMediaType contentType = context.findAccepted(HttpMediaType.JSON_PREDICATE, HttpMediaType.APPLICATION_JSON); context.contentType(contentType); AtomicBoolean first = new AtomicBoolean(true); diff --git a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyWriter.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyWriter.java index e6e4dfc816d..189e482a33c 100644 --- a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyWriter.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbBodyWriter.java @@ -56,7 +56,7 @@ public PredicateResult accept(GenericType type, public Publisher write(Single content, GenericType type, MessageBodyWriterContext context) { - MediaType contentType = context.findAccepted(MediaType.JSON_PREDICATE, MediaType.APPLICATION_JSON); + HttpMediaType contentType = context.findAccepted(HttpMediaType.JSON_PREDICATE, HttpMediaType.APPLICATION_JSON); context.contentType(contentType); return content.flatMap(new ObjectToChunks(jsonb, context.charset())); } diff --git a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbEsBodyStreamWriter.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbEsBodyStreamWriter.java index 992c574b28e..8bc86e1eea4 100644 --- a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbEsBodyStreamWriter.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbEsBodyStreamWriter.java @@ -23,6 +23,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Multi; import io.helidon.media.common.MessageBodyStreamWriter; import io.helidon.media.common.MessageBodyWriterContext; @@ -31,12 +32,13 @@ /** * Message body stream writer supporting object binding with JSON-B. - * This writer is for {@link MediaType#TEXT_EVENT_STREAM} with no element-type parameter or element-type="application/json". + * This writer is for {@link io.helidon.common.media.type.MediaTypes#TEXT_EVENT_STREAM} with no element-type parameter + * or element-type="application/json". */ class JsonbEsBodyStreamWriter implements MessageBodyStreamWriter { - private static final MediaType TEXT_EVENT_STREAM_JSON = MediaType - .parse("text/event-stream;element-type=\"application/json\""); + private static final HttpMediaType TEXT_EVENT_STREAM_JSON = + HttpMediaType.create("text/event-stream;element-type=\"application/json\""); private static final byte[] DATA = "data: ".getBytes(StandardCharsets.UTF_8); private static final byte[] NL = "\n\n".getBytes(StandardCharsets.UTF_8); @@ -57,14 +59,15 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont } return context.contentType() .or(() -> findMediaType(context)) - .filter(mediaType -> mediaType.equals(TEXT_EVENT_STREAM_JSON) || mediaType.equals(MediaType.TEXT_EVENT_STREAM)) + .filter(mediaType -> mediaType.equals(TEXT_EVENT_STREAM_JSON) + || mediaType.mediaType().equals(MediaTypes.TEXT_EVENT_STREAM)) .map(it -> PredicateResult.COMPATIBLE) .orElse(PredicateResult.NOT_SUPPORTED); } @Override public Multi write(Flow.Publisher publisher, GenericType type, MessageBodyWriterContext context) { - MediaType contentType = context.contentType() + HttpMediaType contentType = context.contentType() .or(() -> findMediaType(context)) .orElse(TEXT_EVENT_STREAM_JSON); context.contentType(contentType); @@ -76,9 +79,9 @@ public Multi write(Flow.Publisher publisher, GenericType type, ); } - private Optional findMediaType(MessageBodyWriterContext context) { + private Optional findMediaType(MessageBodyWriterContext context) { try { - return Optional.of(context.findAccepted(MediaType.JSON_EVENT_STREAM_PREDICATE, TEXT_EVENT_STREAM_JSON)); + return Optional.of(context.findAccepted(HttpMediaType.JSON_EVENT_STREAM_PREDICATE, TEXT_EVENT_STREAM_JSON)); } catch (IllegalStateException ignore) { //Not supported. Ignore exception. return Optional.empty(); diff --git a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbNdBodyStreamWriter.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbNdBodyStreamWriter.java index a8528d041d9..2fb2d5b2b23 100644 --- a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbNdBodyStreamWriter.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbNdBodyStreamWriter.java @@ -24,6 +24,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyStreamWriter; @@ -33,11 +34,12 @@ /** * Message body stream writer supporting object binding with JSON-B. - * This writer is for {@link MediaType#APPLICATION_X_NDJSON} media type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_NDJSON} media type. */ class JsonbNdBodyStreamWriter implements MessageBodyStreamWriter { private static final byte[] NL = "\n".getBytes(StandardCharsets.UTF_8); + private static final HttpMediaType X_NDJSON = HttpMediaType.create(MediaTypes.APPLICATION_X_NDJSON); private final Jsonb jsonb; @@ -56,15 +58,14 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont } return context.contentType() .or(() -> findMediaType(context)) - .filter(mediaType -> mediaType.equals(MediaType.APPLICATION_X_NDJSON)) + .filter(mediaType -> mediaType.equals(X_NDJSON)) .map(it -> PredicateResult.COMPATIBLE) .orElse(PredicateResult.NOT_SUPPORTED); } @Override public Multi write(Flow.Publisher publisher, GenericType type, MessageBodyWriterContext context) { - MediaType contentType = MediaType.APPLICATION_X_NDJSON; - context.contentType(contentType); + context.contentType(MediaTypes.APPLICATION_X_NDJSON); AtomicBoolean first = new AtomicBoolean(true); @@ -80,9 +81,9 @@ public Multi write(Flow.Publisher publisher, GenericType type, }); } - private Optional findMediaType(MessageBodyWriterContext context) { + private Optional findMediaType(MessageBodyWriterContext context) { try { - return Optional.of(context.findAccepted(MediaType.APPLICATION_X_NDJSON)); + return Optional.of(context.findAccepted(X_NDJSON)); } catch (IllegalStateException ignore) { //Not supported. Ignore exception. return Optional.empty(); diff --git a/media/jsonb/src/test/java/io/helidon/media/jsonb/TestJsonBindingSupport.java b/media/jsonb/src/test/java/io/helidon/media/jsonb/TestJsonBindingSupport.java index 84e46a81803..facf8541545 100644 --- a/media/jsonb/src/test/java/io/helidon/media/jsonb/TestJsonBindingSupport.java +++ b/media/jsonb/src/test/java/io/helidon/media/jsonb/TestJsonBindingSupport.java @@ -45,10 +45,10 @@ public void pingPong() throws Exception { final String personJson = "{\"name\":\"Frank\"}"; final TestResponse response = TestClient.create(routing, JsonbSupport.create()) .path("/foo") - .post(MediaPublisher.create(MediaType.APPLICATION_JSON.withCharset("UTF-8"), personJson)); + .post(MediaPublisher.create(HttpMediaType.JSON_UTF_8, personJson)); assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), - is(MediaType.APPLICATION_JSON.toString())); + is(HttpMediaType.APPLICATION_JSON.text())); final String json = response.asString().get(10, TimeUnit.SECONDS); assertThat(json, is(personJson)); } @@ -66,10 +66,9 @@ public void genericType() throws Exception { final String personsJson = "[{\"name\":\"Frank\"},{\"name\":\"John\"}]"; final TestResponse response = TestClient.create(routing, JsonbSupport.create()) .path("/foo") - .post(MediaPublisher.create(MediaType.APPLICATION_JSON.withCharset("UTF-8"), - personsJson)); + .post(MediaPublisher.create(HttpMediaType.JSON_UTF_8, personsJson)); assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), - is(MediaType.APPLICATION_JSON.toString())); + is(HttpMediaType.APPLICATION_JSON.text())); final String json = response.asString().get(10, TimeUnit.SECONDS); assertThat(json, is(personsJson)); } diff --git a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyStreamWriter.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyStreamWriter.java index f3e60dc7a49..367492cdc7b 100644 --- a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyStreamWriter.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyStreamWriter.java @@ -54,7 +54,7 @@ public Multi write(Publisher publisher, GenericType type, MessageBodyWriterContext context) { - MediaType contentType = context.findAccepted(MediaType.JSON_PREDICATE, MediaType.APPLICATION_JSON); + HttpMediaType contentType = context.findAccepted(HttpMediaType.JSON_PREDICATE, HttpMediaType.APPLICATION_JSON); context.contentType(contentType); // we do not have join operator diff --git a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyWriter.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyWriter.java index af80302fb9d..f18f810662a 100644 --- a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyWriter.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpBodyWriter.java @@ -52,7 +52,7 @@ public Publisher write(Single content, GenericType type, MessageBodyWriterContext context) { - MediaType contentType = context.findAccepted(MediaType.JSON_PREDICATE, MediaType.APPLICATION_JSON); + HttpMediaType contentType = context.findAccepted(HttpMediaType.JSON_PREDICATE, HttpMediaType.APPLICATION_JSON); context.contentType(contentType); return content.map(new JsonStructureToChunks(jsonWriterFactory, context.charset())); } diff --git a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpEsBodyStreamWriter.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpEsBodyStreamWriter.java index c63e7ca3c51..fba2b5a771c 100644 --- a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpEsBodyStreamWriter.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpEsBodyStreamWriter.java @@ -23,6 +23,8 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Multi; import io.helidon.media.common.MessageBodyStreamWriter; import io.helidon.media.common.MessageBodyWriterContext; @@ -33,12 +35,12 @@ /** * Message body writer for {@link jakarta.json.JsonStructure} sub-classes (JSON-P). - * This writer is for {@link MediaType#TEXT_EVENT_STREAM} with no element-type parameter or element-type="application/json". + * This writer is for {@link MediaTypes#TEXT_EVENT_STREAM} with no element-type parameter or element-type="application/json". */ class JsonpEsBodyStreamWriter implements MessageBodyStreamWriter { - private static final MediaType TEXT_EVENT_STREAM_JSON = MediaType - .parse("text/event-stream;element-type=\"application/json\""); + private static final HttpMediaType TEXT_EVENT_STREAM_JSON = HttpMediaType + .create("text/event-stream;element-type=\"application/json\""); private static final byte[] DATA = "data: ".getBytes(StandardCharsets.UTF_8); private static final byte[] NL = "\n\n".getBytes(StandardCharsets.UTF_8); @@ -55,7 +57,7 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont } return context.contentType() .or(() -> findMediaType(context)) - .filter(mediaType -> mediaType.equals(TEXT_EVENT_STREAM_JSON) || mediaType.equals(MediaType.TEXT_EVENT_STREAM)) + .filter(mediaType -> mediaType.equals(TEXT_EVENT_STREAM_JSON) || mediaType.equals(MediaTypes.TEXT_EVENT_STREAM)) .map(it -> PredicateResult.COMPATIBLE) .orElse(PredicateResult.NOT_SUPPORTED); } @@ -83,9 +85,9 @@ public Multi write(Flow.Publisher publisher, DataChunk.create(NL))); } - private Optional findMediaType(MessageBodyWriterContext context) { + private Optional findMediaType(MessageBodyWriterContext context) { try { - return Optional.of(context.findAccepted(MediaType.JSON_EVENT_STREAM_PREDICATE, TEXT_EVENT_STREAM_JSON)); + return Optional.of(context.findAccepted(HttpMediaType.JSON_EVENT_STREAM_PREDICATE, TEXT_EVENT_STREAM_JSON)); } catch (IllegalStateException ignore) { //Not supported. Ignore exception. return Optional.empty(); diff --git a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpNdBodyStreamWriter.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpNdBodyStreamWriter.java index 7ce36bc6ec5..a89546d8bd9 100644 --- a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpNdBodyStreamWriter.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpNdBodyStreamWriter.java @@ -24,6 +24,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyStreamWriter; @@ -35,11 +36,12 @@ /** * Message body writer for {@link JsonStructure} sub-classes (JSON-P). - * This writer is for {@link MediaType#APPLICATION_X_NDJSON} media type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_NDJSON} media type. */ class JsonpNdBodyStreamWriter implements MessageBodyStreamWriter { private static final byte[] NL = "\n".getBytes(StandardCharsets.UTF_8); + public static final HttpMediaType MEDIA_TYPE = HttpMediaType.create(MediaTypes.APPLICATION_X_NDJSON); private final JsonWriterFactory jsonWriterFactory; @@ -54,7 +56,7 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont } return context.contentType() .or(() -> findMediaType(context)) - .filter(mediaType -> mediaType.equals(MediaType.APPLICATION_X_NDJSON)) + .filter(mediaType -> mediaType.equals(MEDIA_TYPE)) .map(it -> PredicateResult.COMPATIBLE) .orElse(PredicateResult.NOT_SUPPORTED); } @@ -64,9 +66,9 @@ public Multi write(Flow.Publisher publisher, GenericType type, MessageBodyWriterContext context) { - MediaType contentType = context.contentType() + HttpMediaType contentType = context.contentType() .or(() -> findMediaType(context)) - .orElse(MediaType.APPLICATION_X_NDJSON); + .orElse(MEDIA_TYPE); context.contentType(contentType); @@ -88,9 +90,9 @@ public Multi write(Flow.Publisher publisher, }); } - private Optional findMediaType(MessageBodyWriterContext context) { + private Optional findMediaType(MessageBodyWriterContext context) { try { - return Optional.of(context.findAccepted(MediaType.APPLICATION_X_NDJSON)); + return Optional.of(context.findAccepted(MEDIA_TYPE)); } catch (IllegalStateException ignore) { //Not supported. Ignore exception. return Optional.empty(); diff --git a/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonSupportTest.java b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonSupportTest.java index 1ba883e6848..a2f2cfdf0c6 100644 --- a/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonSupportTest.java +++ b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonSupportTest.java @@ -30,6 +30,7 @@ import jakarta.json.JsonObject; import org.junit.jupiter.api.Test; +import static io.helidon.common.http.Http.Header.ACCEPT; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.sameInstance; @@ -62,11 +63,11 @@ public void pingPong() throws Exception { TestResponse response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") .post(MediaPublisher.create( - MediaType.APPLICATION_JSON.withCharset("UTF-8"), + HttpMediaType.APPLICATION_JSON.withCharset("UTF-8"), json.toString())); assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), - is(equalTo(MediaType.APPLICATION_JSON.toString()))); + is(equalTo(HttpMediaType.APPLICATION_JSON.toString()))); byte[] bytes = response.asBytes().toCompletableFuture() .get(10, TimeUnit.SECONDS); @@ -84,11 +85,11 @@ public void pingPongNoCharset() throws Exception { JsonObject json = createJson(); TestResponse response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") - .post(MediaPublisher.create(MediaType.APPLICATION_JSON, + .post(MediaPublisher.create(HttpMediaType.APPLICATION_JSON, json.toString())); assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), - is(equalTo(MediaType.APPLICATION_JSON.toString()))); + is(equalTo(HttpMediaType.APPLICATION_JSON.text()))); byte[] bytes = response.asBytes().toCompletableFuture() .get(10, TimeUnit.SECONDS); JsonObject json2 = Json.createReader(new ByteArrayInputStream(bytes)) @@ -105,7 +106,7 @@ public void invalidJson() throws Exception { TestResponse response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") .post(MediaPublisher.create( - MediaType.APPLICATION_JSON.withCharset("UTF-8"), + HttpMediaType.APPLICATION_JSON.withCharset("UTF-8"), "{ ... invalid ... }")); assertThat(response.status(), is(equalTo(Http.Status.INTERNAL_SERVER_ERROR_500))); @@ -123,7 +124,7 @@ public void explicitJsonSupportRegistrationMissingJsonProperty() TestResponse response = TestClient.create(routing) .path("/foo") .post(MediaPublisher.create( - MediaType.APPLICATION_JSON.withCharset("UTF-8"), + HttpMediaType.APPLICATION_JSON.withCharset("UTF-8"), json.toString())); assertThat(response.status(), is(equalTo(Http.Status.INTERNAL_SERVER_ERROR_500))); @@ -140,42 +141,42 @@ public void acceptHeaders() throws Exception { // Has accept TestResponse response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") - .header("Accept", "text/plain; q=.8, application/json; q=.1") + .header(ACCEPT, "text/plain; q=.8, application/json; q=.1") .post(MediaPublisher.create( - MediaType.APPLICATION_JSON.withCharset("UTF-8"), + HttpMediaType.APPLICATION_JSON.withCharset("UTF-8"), json.toString())); assertThat(response.status(), is(equalTo(Http.Status.OK_200))); assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), - is(equalTo(MediaType.APPLICATION_JSON.toString()))); + is(equalTo(HttpMediaType.APPLICATION_JSON.text()))); // Has accept with +json response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") - .header("Accept", "text/plain; q=.8, application/specific+json; q=.1") + .header(ACCEPT, "text/plain; q=.8, application/specific+json; q=.1") .post(MediaPublisher.create( - MediaType.APPLICATION_JSON.withCharset("UTF-8"), + HttpMediaType.APPLICATION_JSON.withCharset("UTF-8"), json.toString())); assertThat(response.status(), is(equalTo(Http.Status.OK_200))); assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), - is(equalTo(MediaType.parse("application/specific+json").toString()))); + is(equalTo(HttpMediaType.create("application/specific+json").toString()))); // With start response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") - .header("Accept", "text/plain; q=.8, application/*; q=.1") + .header(ACCEPT, "text/plain; q=.8, application/*; q=.1") .post(MediaPublisher.create( - MediaType.APPLICATION_JSON.withCharset("UTF-8"), + HttpMediaType.APPLICATION_JSON.withCharset("UTF-8"), json.toString())); assertThat(response.status(), is(equalTo(Http.Status.OK_200))); assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), - is(equalTo(MediaType.APPLICATION_JSON.toString()))); + is(equalTo(HttpMediaType.APPLICATION_JSON.text()))); // With JSONP standard application/javascript response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") - .header("Accept", "application/javascript") + .header(ACCEPT, "application/javascript") .post(MediaPublisher.create( - MediaType.APPLICATION_JSON.withCharset("UTF-8"), + HttpMediaType.APPLICATION_JSON.withCharset("UTF-8"), json.toString())); assertThat(response.status(), is(equalTo(Http.Status.INTERNAL_SERVER_ERROR_500))); @@ -183,9 +184,9 @@ public void acceptHeaders() throws Exception { // Without start response = TestClient.create(routing, JsonpSupport.create()) .path("/foo") - .header("Accept", "text/plain; q=.8, application/specific; q=.1") + .header(ACCEPT, "text/plain; q=.8, application/specific; q=.1") .post(MediaPublisher.create( - MediaType.APPLICATION_JSON.withCharset("UTF-8"), + HttpMediaType.APPLICATION_JSON.withCharset("UTF-8"), json.toString())); assertThat(response.status(), is(equalTo(Http.Status.INTERNAL_SERVER_ERROR_500))); diff --git a/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonpStreamWriterTest.java b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonpStreamWriterTest.java index a993c7b8356..47e1b0bf442 100644 --- a/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonpStreamWriterTest.java +++ b/media/jsonp/src/test/java/io/helidon/media/jsonp/JsonpStreamWriterTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,7 +23,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.HashParameters; +import io.helidon.common.http.HeadersWritable; import io.helidon.common.reactive.Multi; import io.helidon.media.common.MessageBodyOperator; import io.helidon.media.common.MessageBodyWriterContext; @@ -49,7 +49,7 @@ public class JsonpStreamWriterTest { private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Map.of()); private static final JsonReaderFactory JSON_PARSER = Json.createReaderFactory(Map.of()); - private static final MessageBodyWriterContext CONTEXT = MessageBodyWriterContext.create(HashParameters.create()); + private static final MessageBodyWriterContext CONTEXT = MessageBodyWriterContext.create(HeadersWritable.create()); private static final JsonpBodyStreamWriter WRITER = (JsonpBodyStreamWriter) JsonpSupport.streamWriter(); private static final GenericType JSON_OBJECT = GenericType.create(JsonObject.class); private static final GenericType JSON_ARRAY = GenericType.create(JsonArray.class); diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPart.java b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPart.java index d9a071bbeb2..1dbf58153e9 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPart.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPart.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -44,7 +44,7 @@ public interface BodyPart { * header, or {@code null} if not present. */ default String name() { - return headers().contentDisposition().name().orElse(null); + return headers().contentDisposition().contentName().orElse(null); } /** diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java index e0d9e42e227..de2875e5f84 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamReader.java @@ -42,12 +42,12 @@ public Publisher read(Publisher publi MessageBodyReaderContext context) { String boundary = null; - MediaType contentType = context.contentType().orElse(null); + HttpMediaType contentType = context.contentType().orElse(null); if (contentType != null) { boundary = contentType.parameters().get("boundary"); } if (boundary == null) { - throw new IllegalStateException("boudary header is missing"); + throw new IllegalStateException("boundary header is missing"); } MultiPartDecoder decoder = MultiPartDecoder.create(boundary, context); publisher.subscribe(decoder); diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java index 9bd8eb5d10a..14e155faada 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartBodyStreamWriter.java @@ -19,7 +19,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.media.common.MessageBodyStreamWriter; import io.helidon.media.common.MessageBodyWriterContext; @@ -44,7 +44,7 @@ public Publisher write(Publisher content GenericType type, MessageBodyWriterContext context) { - context.contentType(MediaType.MULTIPART_FORM_DATA); + context.contentType(MediaTypes.MULTIPART_FORM_DATA); MultiPartEncoder encoder = MultiPartEncoder.create(boundary, context); content.subscribe(encoder); return encoder; diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartHeaders.java b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartHeaders.java index 20fec79e207..9ac2d9e2dab 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartHeaders.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/BodyPartHeaders.java @@ -15,8 +15,10 @@ */ package io.helidon.media.multipart; +import io.helidon.common.http.ContentDisposition; import io.helidon.common.http.Headers; import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; /** * Body part headers. @@ -28,9 +30,9 @@ public interface BodyPartHeaders extends Headers { * is not present, the default value is retrieved using * {@link #defaultContentType()}. * - * @return MediaType, never {@code null} + * @return HttpMediaType, never {@code null} */ - MediaType contentType(); + HttpMediaType partContentType(); /** * Get the {@code Content-Disposition} header. @@ -41,17 +43,17 @@ public interface BodyPartHeaders extends Headers { /** * Returns the default {@code Content-Type} header value: - * {@link MediaType#APPLICATION_OCTET_STREAM} if the + * {@link MediaTypes#APPLICATION_OCTET_STREAM} if the * {@code Content-Disposition} header is present with a non empty value, - * otherwise {@link MediaType#TEXT_PLAIN}. + * otherwise {@link MediaTypes#TEXT_PLAIN}. * * @see * RFC-7578 * @return MediaType, never {@code null} */ - default MediaType defaultContentType() { + default HttpMediaType defaultContentType() { return contentDisposition().filename() - .map(fname -> MediaType.APPLICATION_OCTET_STREAM) - .orElse(MediaType.TEXT_PLAIN); + .map(fname -> HttpMediaType.create(MediaTypes.APPLICATION_OCTET_STREAM)) + .orElse(HttpMediaType.TEXT_PLAIN); } } diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/ContentDisposition.java b/media/multipart/src/main/java/io/helidon/media/multipart/ContentDisposition.java deleted file mode 100644 index a79dca840c9..00000000000 --- a/media/multipart/src/main/java/io/helidon/media/multipart/ContentDisposition.java +++ /dev/null @@ -1,352 +0,0 @@ -/* - * Copyright (c) 2020, 2022 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.media.multipart; - -import java.net.URLDecoder; -import java.net.URLEncoder; -import java.nio.charset.StandardCharsets; -import java.time.ZonedDateTime; -import java.util.Collections; -import java.util.HashMap; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Objects; -import java.util.Optional; -import java.util.OptionalLong; - -import io.helidon.common.http.CharMatcher; -import io.helidon.common.http.Http; -import io.helidon.common.http.Tokenizer; - -/** - * A generic representation of the {@code Content-Disposition} header. - *

- * Parameter encoding is not supported, other than - * URI percent - * encoding in the filename parameter. See {@link java.net.URLDecoder}. - *

- * See also: - * - */ -public final class ContentDisposition { - - private static final CharMatcher TOKEN_MATCHER = CharMatcher.ascii() - .and(CharMatcher.javaIsoControl().negate()) - .and(CharMatcher.isNot(' ')) - .and(CharMatcher.noneOf("()<>@,;:\\\"/[]?=")); - - private static final CharMatcher LINEAR_WHITE_SPACE = CharMatcher.anyOf(" \t\r\n"); - - private static final CharMatcher QUOTED_TEXT_MATCHER = CharMatcher.noneOf("\"\\\r"); - - private static final String NAME_PARAMETER = "name"; - private static final String FILENAME_PARAMETER = "filename"; - private static final String CREATION_DATE_PARAMETER = "creation-date"; - private static final String MODIFICATION_DATE_PARAMETER = "modification-date"; - private static final String READ_DATE_PARAMETER = "read-date"; - private static final String SIZE_PARAMETER = "size"; - - /** - * Empty content disposition. - */ - static final ContentDisposition EMPTY = new ContentDisposition("", Collections.emptyMap()); - - private final String type; - private final Map parameters; - - /** - * Create a new instance. - * @param type content disposition type - * @param params content disposition parameters - */ - private ContentDisposition(String type, Map params) { - this.type = type; - this.parameters = params; - } - - /** - * The content disposition type. - * @return type, never {@code null} - */ - public String type() { - return type; - } - - /** - * Get the value of the {@code name} parameter. In the case of a - * {@code form-data} disposition type the value is the original field name - * from the form. - * - * @return {@code Optional}, never {@code null} - */ - public Optional name() { - return Optional.ofNullable(parameters.get(NAME_PARAMETER)); - } - - /** - * Get the value of the {@code filename} parameter that can be used to - * suggest a filename to be used if the entity is detached and stored in a - * separate file. - * - * @return {@code Optional}, never {@code null} - */ - public Optional filename() { - String filename = null; - String value = parameters.get(FILENAME_PARAMETER); - if (value != null) { - filename = URLDecoder.decode(value, StandardCharsets.UTF_8); - } - return Optional.ofNullable(filename); - } - - /** - * Get the value of the {@code creation-date} parameter that can be used - * to indicate the date at which the file was created. - * @return {@code Optional}, never {@code null} - */ - public Optional creationDate() { - return Optional.ofNullable(parameters.get(CREATION_DATE_PARAMETER)).map(Http.DateTime::parse); - } - - /** - * Get the value of the {@code modification-date} parameter that can be - * used to indicate the date at which the file was last modified. - * - * @return {@code Optional}, never {@code null} - */ - public Optional modificationDate() { - return Optional.ofNullable(parameters.get(MODIFICATION_DATE_PARAMETER)).map(Http.DateTime::parse); - } - - /** - * Get the value of the {@code modification-date} parameter that can be - * used to indicate the date at which the file was last read. - * - * @return {@code Optional}, never {@code null} - */ - public Optional readDate() { - return Optional.ofNullable(parameters.get(READ_DATE_PARAMETER)).map(Http.DateTime::parse); - } - - /** - * Get the value of the {@code size} parameter that can be - * used to indicate an approximate size of the file in octets. - * - * @return {@code OptionalLong}, never {@code null} - */ - public OptionalLong size() { - String size = parameters.get(SIZE_PARAMETER); - if (size != null) { - return OptionalLong.of(Long.parseLong(size)); - } - return OptionalLong.empty(); - } - - /** - * Get the parameters map. - * @return map, never {@code null} - */ - public Map parameters() { - return parameters; - } - - /** - * Convert the content disposition to a string suitable for use as the value - * of a corresponding HTTP header. - * - * @return a string version of the content disposition - */ - @Override - public String toString() { - StringBuilder sb = new StringBuilder(); - sb.append(type); - for (Entry param : parameters.entrySet()) { - sb.append(";"); - sb.append(param.getKey()); - sb.append("="); - if (SIZE_PARAMETER.equals(param.getKey())) { - sb.append(param.getValue()); - } else { - sb.append("\""); - sb.append(param.getValue()); - sb.append("\""); - } - } - return sb.toString(); - } - - /** - * Create a new builder instance. - * - * @return Builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Parse the header value of a {@code Content-Disposition} header. - * @param input header value to parse - * @return ContentDisposition instance - * @throws IllegalArgumentException if a parsing error occurs - */ - static ContentDisposition parse(String input) { - Objects.requireNonNull(input, "Parameter 'input' is null!"); - Tokenizer tokenizer = new Tokenizer(input.trim()); - try { - String type = tokenizer.consumeToken(TOKEN_MATCHER).toLowerCase(); - Map parameters = new HashMap<>(); - while (tokenizer.hasMore()) { - tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); - tokenizer.consumeCharacter(';'); - tokenizer.consumeTokenIfPresent(LINEAR_WHITE_SPACE); - String attribute = tokenizer.consumeToken(TOKEN_MATCHER); - tokenizer.consumeCharacter('='); - final String value; - if ('"' == tokenizer.previewChar()) { - tokenizer.consumeCharacter('"'); - StringBuilder valueBuilder = new StringBuilder(); - while ('"' != tokenizer.previewChar()) { - // quoted pair - // '\' escapes '"' or '\' - if ('\\' == tokenizer.previewChar()) { - tokenizer.consumeCharacter('\\'); - char c = tokenizer.previewChar(); - if ('"' == c || '\\' == c) { - // process - valueBuilder.append(tokenizer.consumeCharacter(CharMatcher.ascii())); - continue; - } else { - valueBuilder.append('\\'); - } - } - valueBuilder.append(tokenizer.consumeToken(QUOTED_TEXT_MATCHER)); - } - value = valueBuilder.toString(); - tokenizer.consumeCharacter('"'); - } else { - value = tokenizer.consumeToken(TOKEN_MATCHER); - } - parameters.put(attribute, value); - } - return new ContentDisposition(type, parameters); - } catch (IllegalStateException e) { - throw new IllegalArgumentException("Could not parse '" + input + "'", e); - } - } - - /** - * Builder class to create {@link ContentDisposition} instances. - */ - public static final class Builder implements io.helidon.common.Builder { - - private String type = "form-data"; - private final Map params = new HashMap<>(); - - /** - * Set the content disposition type. - * @param type content disposition type - * @return this builder - */ - public Builder type(String type) { - this.type = type; - return this; - } - - /** - * Set the content disposition {@code name} parameter. - * @param name control name - * @return this builder - */ - public Builder name(String name) { - params.put(NAME_PARAMETER, name); - return this; - } - - /** - * Set the content disposition {@code filename} parameter. - * @param filename filename parameter - * @return this builder - */ - public Builder filename(String filename) { - params.put(FILENAME_PARAMETER, URLEncoder.encode(filename, StandardCharsets.UTF_8)); - return this; - } - - /** - * Set the content disposition {@code creation-date} parameter. - * @param date date value - * @return this builder - */ - public Builder creationDate(ZonedDateTime date) { - params.put(CREATION_DATE_PARAMETER, date.format(Http.DateTime.RFC_1123_DATE_TIME)); - return this; - } - - /** - * Set the content disposition {@code modification-date} parameter. - * @param date date value - * @return this builder - */ - public Builder modificationDate(ZonedDateTime date) { - params.put(MODIFICATION_DATE_PARAMETER, date.format(Http.DateTime.RFC_1123_DATE_TIME)); - return this; - } - - /** - * Set the content disposition {@code read-date} parameter. - * @param date date value - * @return this builder - */ - public Builder readDate(ZonedDateTime date) { - params.put(READ_DATE_PARAMETER, date.format(Http.DateTime.RFC_1123_DATE_TIME)); - return this; - } - - /** - * Set the content disposition {@code size} parameter. - * @param size size value - * @return this builder - */ - public Builder size(long size) { - params.put(SIZE_PARAMETER, Long.toString(size)); - return this; - } - - /** - * Add a new content disposition header parameter. - * @param name parameter name - * @param value parameter value - * @return this builder - */ - public Builder parameter(String name, String value) { - params.put(name, value); - return this; - } - - @Override - public ContentDisposition build() { - return new ContentDisposition(type, params); - } - } -} diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/FileFormParams.java b/media/multipart/src/main/java/io/helidon/media/multipart/FileFormParams.java index 657e99545c5..b69b6e33ad7 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/FileFormParams.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/FileFormParams.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,8 +17,6 @@ import java.nio.file.Path; -import io.helidon.common.http.FormBuilder; - /** * Form object which simplifies sending of multipart forms. */ @@ -36,7 +34,7 @@ static Builder builder() { /** * Fluent API builder of {@link FileFormParams}. */ - class Builder implements FormBuilder { + class Builder implements io.helidon.common.Builder { private final WriteableMultiPart.Builder builder = WriteableMultiPart.builder(); @@ -48,7 +46,6 @@ public FileFormParams build() { return new FileFormParamsImpl(builder.build().bodyParts()); } - @Override public Builder add(String name, String... values) { for (String value : values) { builder.bodyPart(name, value); diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java index cd09d0c8b42..ad012850bd0 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java @@ -20,9 +20,9 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.Http; import io.helidon.common.http.HttpMediaType; import io.helidon.common.mapper.Mapper; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyWriter; @@ -37,6 +37,7 @@ public final class MultiPartBodyWriter implements MessageBodyWriter type, MessageBodyWriterContext context) { return context.contentType() - .or(() -> Optional.of(MediaType.MULTIPART_FORM_DATA)) - .filter(mediaType -> mediaType == MediaType.MULTIPART_FORM_DATA) + .or(() -> Optional.of(HttpMediaType.create(MULTIPART_FORM_DATA))) + .filter(mediaType -> mediaType == MULTIPART_FORM_DATA) .map(it -> PredicateResult.supports(WriteableMultiPart.class, type)) .orElse(PredicateResult.NOT_SUPPORTED); } @@ -57,13 +58,12 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont public Publisher write(Single content, GenericType type, MessageBodyWriterContext context) { - MediaType mediaType = MediaType.MULTIPART_FORM_DATA; - MediaType mediaWithBoundary = MediaType.builder() - .type(mediaType.type()) - .subtype(mediaType.subtype()) + + HttpMediaType mediaWithBoundary = HttpMediaType.builder() + .mediaType(MULTIPART_FORM_DATA.mediaType()) .addParameter("boundary", "\"" + boundary + "\"") .build(); - context.headers().put(Http.Header.CONTENT_TYPE, mediaWithBoundary.toString()); + context.headers().contentType(mediaWithBoundary); return content.flatMap(new MultiPartToChunks(boundary, context)); } diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartDecoder.java b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartDecoder.java index 8e75d92416a..1573cf84a77 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartDecoder.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartDecoder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import java.util.concurrent.atomic.AtomicLong; import io.helidon.common.http.DataChunk; +import io.helidon.common.http.Http; import io.helidon.common.reactive.Multi; import io.helidon.common.reactive.SubscriptionHelper; import io.helidon.media.common.MessageBodyReadableContent; @@ -284,7 +285,7 @@ protected void drainBoth() { break; case HEADER: MimeParser.HeaderEvent headerEvent = event.asHeaderEvent(); - bodyPartHeaderBuilder.header(headerEvent.name(), headerEvent.value()); + bodyPartHeaderBuilder.header(Http.Header.create(headerEvent.name()), headerEvent.value()); break; case END_HEADERS: bodyPartPublisher = new DataChunkPublisher(); @@ -375,7 +376,7 @@ private ReadableBodyPart createPart() { // create a reader context for the part MessageBodyReaderContext partContext = MessageBodyReaderContext.create(context, - /* eventListener */ null, headers, Optional.of(headers.contentType())); + /* eventListener */ null, headers, Optional.of(headers.partContentType())); // create a readable content for the part MessageBodyReadableContent partContent = MessageBodyReadableContent.create(bodyPartPublisher, diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/ReadableBodyPartHeaders.java b/media/multipart/src/main/java/io/helidon/media/multipart/ReadableBodyPartHeaders.java index ea4c7562b38..420d2142a9c 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/ReadableBodyPartHeaders.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/ReadableBodyPartHeaders.java @@ -15,44 +15,27 @@ */ package io.helidon.media.multipart; -import java.util.ArrayList; +import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.TreeMap; +import java.util.function.Supplier; +import io.helidon.common.http.ContentDisposition; +import io.helidon.common.http.Headers; +import io.helidon.common.http.HeadersWritable; import io.helidon.common.http.Http; import io.helidon.common.http.HttpMediaType; -import io.helidon.common.http.ReadOnlyParameters; /** * Readable body part headers. */ -public final class ReadableBodyPartHeaders extends ReadOnlyParameters implements BodyPartHeaders { +public final class ReadableBodyPartHeaders implements BodyPartHeaders { private final Object internalLock = new Object(); + private final Headers headers; private ContentDisposition contentDisposition; - private ReadableBodyPartHeaders(Map> params) { - super(params); - } - - @Override - public MediaType contentType() { - return first(Http.Header.CONTENT_TYPE) - .map(MediaType::parse) - .orElseGet(this::defaultContentType); - } - - @Override - public ContentDisposition contentDisposition() { - if (contentDisposition == null) { - synchronized (internalLock) { - contentDisposition = first(Http.Header.CONTENT_DISPOSITION) - .map(ContentDisposition::parse) - .orElse(ContentDisposition.EMPTY); - } - } - return contentDisposition; + private ReadableBodyPartHeaders(Headers headers) { + this.headers = headers; } /** @@ -66,12 +49,66 @@ public static Builder builder() { /** * Create a new instance of {@link ReadableBodyPartHeaders}. + * * @return ReadableBodyPartHeaders */ public static ReadableBodyPartHeaders create() { return new ReadableBodyPartHeaders(null); } + @Override + public List all(Http.HeaderName name, Supplier> defaultSupplier) { + return headers.all(name, defaultSupplier); + } + + @Override + public boolean contains(Http.HeaderName name) { + return headers.contains(name); + } + + @Override + public boolean contains(Http.HeaderValue value) { + return headers.contains(value); + } + + @Override + public Http.HeaderValue get(Http.HeaderName name) { + return headers.get(name); + } + + @Override + public int size() { + return headers.size(); + } + + @Override + public List acceptedTypes() { + return headers.acceptedTypes(); + } + + @Override + public Iterator iterator() { + return headers.iterator(); + } + + @Override + public HttpMediaType partContentType() { + return contentType() + .orElseGet(this::defaultContentType); + } + + @Override + public ContentDisposition contentDisposition() { + if (contentDisposition == null) { + synchronized (internalLock) { + contentDisposition = first(Http.Header.CONTENT_DISPOSITION) + .map(ContentDisposition::parse) + .orElse(ContentDisposition.empty()); + } + } + return contentDisposition; + } + /** * Builder class to create {@link ReadableBodyPartHeaders} instances. */ @@ -80,8 +117,7 @@ public static final class Builder implements io.helidon.common.Builder> headers - = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private final HeadersWritable headers = HeadersWritable.create(); /** * Force the use of {@link ReadableBodyPartHeaders#builder() }. @@ -89,26 +125,21 @@ public static final class Builder implements io.helidon.common.Builder values = headers.get(name); - if (values == null) { - values = new ArrayList<>(); - headers.put(name, values); - } - values.add(value); + Builder header(Http.HeaderName name, String value) { + headers.add(name, value); return this; } - - @Override - public ReadableBodyPartHeaders build() { - return new ReadableBodyPartHeaders(headers); - } } } diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/VirtualBuffer.java b/media/multipart/src/main/java/io/helidon/media/multipart/VirtualBuffer.java index f9bb2b14e34..6deac1de7d2 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/VirtualBuffer.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/VirtualBuffer.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -47,7 +47,7 @@ final class VirtualBuffer { /** * Create a new virtual buffer. - * @param capacity initial capacity + * @param initialCapacity initial capacity */ private VirtualBuffer(int initialCapacity) { bufferIds = new int[initialCapacity]; diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/WriteableBodyPart.java b/media/multipart/src/main/java/io/helidon/media/multipart/WriteableBodyPart.java index 69e18ffc099..9be7010b09e 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/WriteableBodyPart.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/WriteableBodyPart.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,7 +21,7 @@ import io.helidon.common.GenericType; import io.helidon.common.http.DataChunk; -import io.helidon.common.http.Parameters; +import io.helidon.common.http.HeadersWritable; import io.helidon.common.reactive.Single; import io.helidon.common.reactive.SubscriptionHelper; import io.helidon.media.common.MessageBodyWriterContext; @@ -128,7 +128,7 @@ public Builder headers(WriteableBodyPartHeaders headers) { } /** - * Name which will be used in {@link ContentDisposition}. + * Name which will be used in {@link io.helidon.common.http.ContentDisposition}. * * This value will be ignored if an actual instance of {@link WriteableBodyPartHeaders} is set. * @@ -141,7 +141,7 @@ public Builder name(String name) { } /** - * Filename which will be used in {@link ContentDisposition}. + * Filename which will be used in {@link io.helidon.common.http.ContentDisposition}. * * This value will be ignored if an actual instance of {@link WriteableBodyPartHeaders} is set. * @@ -188,10 +188,10 @@ private static final class EntityBodyPartContent implements WriteableBodyPartCon private final Object entity; private final GenericType type; - private final Parameters headers; + private final HeadersWritable headers; private Publisher publisher; - EntityBodyPartContent(Object entity, Parameters headers) { + EntityBodyPartContent(Object entity, WriteableBodyPartHeaders headers) { this.entity = Objects.requireNonNull(entity, "entity cannot be null!"); this.headers = Objects.requireNonNull(headers, "headers cannot be null"); type = GenericType.create(entity.getClass()); @@ -218,10 +218,10 @@ private static final class EntityStreamBodyPartContent implements WriteableBo private final Publisher stream; private final GenericType type; - private final Parameters headers; + private final HeadersWritable headers; private Publisher publisher; - EntityStreamBodyPartContent(Publisher stream, GenericType type, Parameters headers) { + EntityStreamBodyPartContent(Publisher stream, GenericType type, HeadersWritable headers) { this.stream = Objects.requireNonNull(stream, "entity cannot be null!"); this.type = Objects.requireNonNull(type, "type cannot be null!"); this.headers = Objects.requireNonNull(headers, "headers cannot be null"); diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/WriteableBodyPartHeaders.java b/media/multipart/src/main/java/io/helidon/media/multipart/WriteableBodyPartHeaders.java index 033a5b19609..5928e2414dc 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/WriteableBodyPartHeaders.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/WriteableBodyPartHeaders.java @@ -15,81 +15,143 @@ */ package io.helidon.media.multipart; -import java.util.ArrayList; +import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.TreeMap; +import java.util.function.Consumer; +import java.util.function.Supplier; -import io.helidon.common.http.HashParameters; +import io.helidon.common.http.ContentDisposition; +import io.helidon.common.http.HeadersWritable; import io.helidon.common.http.Http; import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; /** * Writeable body part headers. */ -public final class WriteableBodyPartHeaders extends HashParameters implements BodyPartHeaders { +public final class WriteableBodyPartHeaders implements BodyPartHeaders, HeadersWritable { - private WriteableBodyPartHeaders(Map> params) { - super(params); + private final HeadersWritable delegate; + + private WriteableBodyPartHeaders(HeadersWritable delegate) { + this.delegate = delegate; } - @Override - public MediaType contentType() { - return first(Http.Header.CONTENT_TYPE) - .map(MediaType::parse) - .orElseGet(this::defaultContentType); + /** + * Create a new builder instance. + * + * @return Builder + */ + public static Builder builder() { + return new Builder(); } /** - * Sets the MIME type of the body part. + * Create a new instance of {@link WriteableBodyPartHeaders} with empty + * headers. * - * @param contentType Media type of the content. + * @return WriteableBodyPartHeaders */ - public void contentType(MediaType contentType) { - if (contentType == null) { - remove(Http.Header.CONTENT_TYPE); - } else { - put(Http.Header.CONTENT_TYPE, contentType.toString()); - } + public static WriteableBodyPartHeaders create() { + return new WriteableBodyPartHeaders(HeadersWritable.create()); + } + + @Override + public List all(Http.HeaderName name, Supplier> defaultSupplier) { + return delegate.all(name, defaultSupplier); + } + + @Override + public boolean contains(Http.HeaderName name) { + return delegate.contains(name); + } + + @Override + public boolean contains(Http.HeaderValue value) { + return delegate.contains(value); + } + + @Override + public Http.HeaderValue get(Http.HeaderName name) { + return delegate.get(name); + } + + @Override + public int size() { + return delegate.size(); + } + + @Override + public List acceptedTypes() { + return delegate.acceptedTypes(); + } + + @Override + public WriteableBodyPartHeaders setIfAbsent(Http.HeaderValue header) { + delegate.setIfAbsent(header); + return this; + } + + @Override + public WriteableBodyPartHeaders add(Http.HeaderValue header) { + delegate.add(header); + return this; + } + + @Override + public WriteableBodyPartHeaders remove(Http.HeaderName name) { + delegate.remove(name); + return this; + } + + @Override + public WriteableBodyPartHeaders remove(Http.HeaderName name, Consumer removedConsumer) { + delegate.remove(name, removedConsumer); + return this; + } + + @Override + public WriteableBodyPartHeaders set(Http.HeaderValue header) { + delegate.set(header); + return this; + } + + @Override + public WriteableBodyPartHeaders clear() { + delegate.clear(); + return this; + } + + @Override + public Iterator iterator() { + return delegate.iterator(); + } + + @Override + public HttpMediaType partContentType() { + return contentType() + .orElseGet(this::defaultContentType); } @Override public ContentDisposition contentDisposition() { return first(Http.Header.CONTENT_DISPOSITION) .map(ContentDisposition::parse) - .orElse(ContentDisposition.EMPTY); + .orElse(ContentDisposition.empty()); } /** * Sets the value of - * {@value io.helidon.common.http.Http.Header#CONTENT_DISPOSITION} header. + * {@link io.helidon.common.http.Http.Header#CONTENT_DISPOSITION} header. * * @param contentDisposition content disposition */ public void contentDisposition(ContentDisposition contentDisposition) { if (contentDisposition != null) { - put(Http.Header.CONTENT_DISPOSITION, contentDisposition.toString()); + set(Http.Header.CONTENT_DISPOSITION, contentDisposition.toString()); } } - /** - * Create a new builder instance. - * - * @return Builder - */ - public static Builder builder() { - return new Builder(); - } - - /** - * Create a new instance of {@link WriteableBodyPartHeaders} with empty - * headers. - * @return WriteableBodyPartHeaders - */ - public static WriteableBodyPartHeaders create() { - return new WriteableBodyPartHeaders(null); - } - /** * Builder class to create {@link WriteableBodyPartHeaders} instances. */ @@ -98,7 +160,7 @@ public static final class Builder implements io.helidon.common.Builder> headers = new TreeMap<>(String.CASE_INSENSITIVE_ORDER); + private final HeadersWritable headers = HeadersWritable.create(); private String name; private String fileName; @@ -111,26 +173,28 @@ private Builder() { /** * Add a new header. * - * @param name header name + * @param name header name * @param value header value * @return this builder */ - public Builder header(String name, String value) { - headers.computeIfAbsent(name, k -> new ArrayList<>()).add(value); + public Builder header(Http.HeaderName name, String value) { + headers.add(name, value); return this; } /** * Add a {@code Content-Type} header. + * * @param contentType value for the {@code Content-Type} header * @return this builder */ - public Builder contentType(MediaType contentType) { - return header(Http.Header.CONTENT_TYPE, contentType.toString()); + public Builder contentType(HttpMediaType contentType) { + return header(Http.Header.CONTENT_TYPE, contentType.toString()); } /** * Add a {@code Content-Disposition} header. + * * @param contentDisp content disposition * @return this builder */ @@ -166,12 +230,12 @@ public Builder filename(String fileName) { @Override public WriteableBodyPartHeaders build() { - if (!headers.containsKey(Http.Header.CONTENT_DISPOSITION) && name != null) { + if (!headers.contains(Http.Header.CONTENT_DISPOSITION) && name != null) { ContentDisposition.Builder builder = ContentDisposition.builder().name(this.name); if (fileName != null) { builder.filename(fileName); - if (!headers.containsKey(Http.Header.CONTENT_TYPE)) { - contentType(MediaType.APPLICATION_OCTET_STREAM); + if (!headers.contains(Http.Header.CONTENT_TYPE)) { + contentType(HttpMediaType.create(MediaTypes.APPLICATION_OCTET_STREAM)); } } contentDisposition(builder.build()); diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartHeadersTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartHeadersTest.java index 2521ca68476..0613f70ea42 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartHeadersTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartHeadersTest.java @@ -15,10 +15,14 @@ */ package io.helidon.media.multipart; +import io.helidon.common.http.ContentDisposition; +import io.helidon.common.http.Http; import io.helidon.common.http.HttpMediaType; import org.junit.jupiter.api.Test; +import static io.helidon.common.http.Http.Header.CONTENT_DISPOSITION; +import static io.helidon.common.http.Http.Header.CONTENT_TYPE; import static org.hamcrest.CoreMatchers.equalTo; import static org.hamcrest.CoreMatchers.hasItems; import static org.hamcrest.CoreMatchers.is; @@ -33,35 +37,35 @@ public class BodyPartHeadersTest { @Test public void testHeaderNameCaseInsensitive(){ ReadableBodyPartHeaders headers = ReadableBodyPartHeaders.builder() - .header("content-type", "text/plain") - .header("Content-ID", "test") - .header("my-header", "abc=def; blah; key=value") - .header("My-header", "foo=bar") + .header(CONTENT_TYPE, "text/plain") + .header(Http.Header.create("Content-ID"), "test") + .header(Http.Header.create("my-header"), "abc=def; blah; key=value") + .header(Http.Header.create("My-header"), "foo=bar") .build(); - assertThat(headers.values("Content-Type"), hasItems("text/plain")); - assertThat(headers.values("Content-Id"), hasItems("test")); - assertThat(headers.values("my-header"), + assertThat(headers.values(Http.Header.create("Content-Type")), hasItems("text/plain")); + assertThat(headers.values(Http.Header.create("Content-Id")), hasItems("test")); + assertThat(headers.values(Http.Header.create("my-header")), hasItems("abc=def; blah; key=value", "foo=bar")); } @Test public void testContentType() { ReadableBodyPartHeaders headers = ReadableBodyPartHeaders.builder() - .header("content-type", "application/json") + .header(CONTENT_TYPE, "application/json") .build(); assertThat(headers.contentType(), is(notNullValue())); assertThat(headers.contentType(), - is(equalTo(MediaType.APPLICATION_JSON))); + is(equalTo(HttpMediaType.APPLICATION_JSON))); } @Test public void testBuilderWithContentType() { WriteableBodyPartHeaders headers = WriteableBodyPartHeaders.builder() - .contentType(MediaType.APPLICATION_JSON) + .contentType(HttpMediaType.APPLICATION_JSON) .build(); assertThat(headers.contentType(), is(notNullValue())); assertThat(headers.contentType(), - is(equalTo(MediaType.APPLICATION_JSON))); + is(equalTo(HttpMediaType.APPLICATION_JSON))); } @Test @@ -69,28 +73,28 @@ public void testDefaultContentType() { ReadableBodyPartHeaders headers = ReadableBodyPartHeaders.builder() .build(); assertThat(headers.contentType(), is(notNullValue())); - assertThat(headers.contentType(), is(equalTo(MediaType.TEXT_PLAIN))); + assertThat(headers.contentType(), is(equalTo(HttpMediaType.TEXT_PLAIN))); } @Test public void testDefaultContentTypeForFile() { ReadableBodyPartHeaders headers = ReadableBodyPartHeaders.builder() - .header("Content-Disposition", "form-data; filename=foo") + .header(CONTENT_DISPOSITION, "form-data; filename=foo") .build(); assertThat(headers.contentType(), is(notNullValue())); assertThat(headers.contentType(), - is(equalTo(MediaType.APPLICATION_OCTET_STREAM))); + is(equalTo(HttpMediaType.APPLICATION_OCTET_STREAM))); } @Test public void testContentDisposition() { ReadableBodyPartHeaders headers = ReadableBodyPartHeaders.builder() - .header("Content-Disposition", "form-data; name=foo") + .header(CONTENT_DISPOSITION, "form-data; name=foo") .build(); assertThat(headers.contentDisposition(), is(notNullValue())); ContentDisposition cd = headers.contentDisposition(); assertThat(cd.type(), is(equalTo("form-data"))); - assertThat(cd.name().isPresent(), is(equalTo(true))); - assertThat(cd.name().get(), is(equalTo("foo"))); + assertThat(cd.contentName().isPresent(), is(equalTo(true))); + assertThat(cd.contentName().get(), is(equalTo("foo"))); } } diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartTest.java index f43371cfb67..5ebd09a9057 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.util.concurrent.Flow.Publisher; import java.util.concurrent.atomic.AtomicBoolean; +import io.helidon.common.http.ContentDisposition; import io.helidon.common.http.DataChunk; import io.helidon.common.reactive.Multi; import io.helidon.media.common.ContentReaders; diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/ContentDispositionTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/ContentDispositionTest.java index 19865dff055..60e8bfa6c6b 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/ContentDispositionTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/ContentDispositionTest.java @@ -18,6 +18,7 @@ import java.time.ZoneId; import java.time.ZonedDateTime; +import io.helidon.common.http.ContentDisposition; import io.helidon.common.http.Http; import org.junit.jupiter.api.Test; @@ -29,7 +30,7 @@ import static org.junit.jupiter.api.Assertions.assertThrows; /** - * Tests {@link ContentDisposition}. + * Tests {@link io.helidon.common.http.ContentDisposition}. */ public class ContentDispositionTest { @@ -64,8 +65,8 @@ public void testQuotedString() { public void testName() { ContentDisposition cd = ContentDisposition.parse("form-data; name=user"); assertThat(cd.type(), is(equalTo("form-data"))); - assertThat(cd.name().isPresent(), is(equalTo(true))); - assertThat(cd.name().get(), is(equalTo("user"))); + assertThat(cd.contentName().isPresent(), is(equalTo(true))); + assertThat(cd.contentName().get(), is(equalTo("user"))); assertThat(cd.parameters(), is(notNullValue())); assertThat(cd.parameters().size(), is(equalTo(1))); } @@ -154,8 +155,8 @@ public void testParamsWithQuotedPair() { public void testCaseInsensitiveType() { ContentDisposition cd = ContentDisposition.parse("aTTachMENT; name=bar"); assertThat(cd.type(), is(equalTo("attachment"))); - assertThat(cd.name().isPresent(), is(equalTo(true))); - assertThat(cd.name().get(), is(equalTo("bar"))); + assertThat(cd.contentName().isPresent(), is(equalTo(true))); + assertThat(cd.contentName().get(), is(equalTo("bar"))); assertThat(cd.parameters(), is(notNullValue())); assertThat(cd.parameters().size(), is(equalTo(1))); } @@ -251,8 +252,8 @@ public void testDateQuotes() { public void testNonAsciiFilename() { ContentDisposition cd = ContentDisposition.parse("form-data; name=\"file[]\"; filename=\"\u60A8\u597D.txt\""); assertThat(cd.type(), is(equalTo("form-data"))); - assertThat(cd.name().isPresent(), is(equalTo(true))); - assertThat(cd.name().get(), is(equalTo("file[]"))); + assertThat(cd.contentName().isPresent(), is(equalTo(true))); + assertThat(cd.contentName().get(), is(equalTo("file[]"))); assertThat(cd.filename().isPresent(), is(equalTo(true))); assertThat(cd.filename().get(), is(equalTo("\u60A8\u597D.txt"))); assertThat(cd.parameters(), is(notNullValue())); diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/MimeParserTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/MimeParserTest.java index 7a8c6dca28f..eec3a2cc03d 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/MimeParserTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/MimeParserTest.java @@ -25,8 +25,6 @@ import java.util.logging.Level; import java.util.logging.Logger; -import io.helidon.common.http.Utils; - import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderTest.java index 7a87e5e2b1e..aa75b9b23c7 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartDecoderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,6 +28,7 @@ import java.util.function.Consumer; import io.helidon.common.http.DataChunk; +import io.helidon.common.http.Http; import io.helidon.common.reactive.Multi; import org.junit.jupiter.api.Test; @@ -44,7 +45,7 @@ * Tests {@link MultiPartDecoder}. */ public class MultiPartDecoderTest { - + private static final Http.HeaderName CONTENT_ID = Http.Header.create("Content-Id"); @Test public void testOnePartInOneChunk() { String boundary = "boundary"; @@ -59,7 +60,7 @@ public void testOnePartInOneChunk() { Consumer consumer = (part) -> { latch.countDown(); - assertThat(part.headers().values("Content-Id"), + assertThat(part.headers().values(CONTENT_ID), hasItems("part1")); DataChunkSubscriber subscriber = new DataChunkSubscriber(); part.content().subscribe(subscriber); @@ -103,7 +104,7 @@ public void testTwoPartsInOneChunk() { Consumer consumer = (part) -> { latch.countDown(); if (latch.getCount() == 3) { - assertThat(part.headers().values("Content-Id"), hasItems("part1")); + assertThat(part.headers().values(CONTENT_ID), hasItems("part1")); DataChunkSubscriber subscriber = new DataChunkSubscriber(); part.content().subscribe(subscriber); subscriber.content().thenAccept(body -> { @@ -111,7 +112,7 @@ public void testTwoPartsInOneChunk() { assertThat(body, is(equalTo("body 1"))); }); } else { - assertThat(part.headers().values("Content-Id"), hasItems("part2")); + assertThat(part.headers().values(CONTENT_ID), hasItems("part2")); DataChunkSubscriber subscriber = new DataChunkSubscriber(); part.content().subscribe(subscriber); subscriber.content().thenAccept(body -> { @@ -144,7 +145,7 @@ public void testContentAcrossChunks() { final CountDownLatch latch = new CountDownLatch(2); Consumer consumer = (part) -> { latch.countDown(); - assertThat(part.headers().values("Content-Id"), hasItems("part1")); + assertThat(part.headers().values(CONTENT_ID), hasItems("part1")); DataChunkSubscriber subscriber = new DataChunkSubscriber(); part.content().subscribe(subscriber); subscriber.content().thenAccept(body -> { @@ -180,7 +181,7 @@ public void testContentAcrossChunksAsyncRequest() { final CountDownLatch latch = new CountDownLatch(2); Consumer consumer = (part) -> { latch.countDown(); - assertThat(part.headers().values("Content-Id"), hasItems("part1")); + assertThat(part.headers().values(CONTENT_ID), hasItems("part1")); DataChunkSubscriber subscriber = new DataChunkSubscriber(); part.content().subscribe(subscriber); subscriber.content().thenAccept(body -> { @@ -220,9 +221,9 @@ public void testMultipleChunksBeforeContent() { final CountDownLatch latch = new CountDownLatch(2); Consumer consumer = (part) -> { latch.countDown(); - assertThat(part.headers().values("Content-Id"), hasItems("part1")); - assertThat(part.headers().values("Content-Type"), hasItems("text/plain")); - assertThat(part.headers().values("Set-Cookie"), hasItems("bob=alice", "foo=bar")); + assertThat(part.headers().values(CONTENT_ID), hasItems("part1")); + assertThat(part.headers().values(Http.Header.CONTENT_TYPE), hasItems("text/plain")); + assertThat(part.headers().values(Http.Header.SET_COOKIE), hasItems("bob=alice", "foo=bar")); DataChunkSubscriber subscriber = new DataChunkSubscriber(); part.content().subscribe(subscriber); subscriber.content().thenAccept(body -> { @@ -258,7 +259,7 @@ public void testMulitiplePartsWithOneByOneSubscriber() { Consumer consumer = (part) -> { latch.countDown(); if (latch.getCount()== 3) { - assertThat(part.headers().values("Content-Id"), hasItems("part1")); + assertThat(part.headers().values(CONTENT_ID), hasItems("part1")); DataChunkSubscriber subscriber = new DataChunkSubscriber(); part.content().subscribe(subscriber); subscriber.content().thenAccept(body -> { @@ -266,7 +267,7 @@ public void testMulitiplePartsWithOneByOneSubscriber() { assertThat(body, is(equalTo("body 1"))); }); } else { - assertThat(part.headers().values("Content-Id"), hasItems("part2")); + assertThat(part.headers().values(CONTENT_ID), hasItems("part2")); DataChunkSubscriber subscriber = new DataChunkSubscriber(); part.content().subscribe(subscriber); subscriber.content().thenAccept(body -> { @@ -303,7 +304,7 @@ public void testSubscriberCancelAfterOnePart() { Consumer consumer = (part) -> { latch.countDown(); if (latch.getCount()== 1) { - assertThat(part.headers().values("Content-Id"), hasItems("part1")); + assertThat(part.headers().values(CONTENT_ID), hasItems("part1")); DataChunkSubscriber subscriber1 = new DataChunkSubscriber(); part.content().subscribe(subscriber1); subscriber1.content().thenAccept(body -> { @@ -377,7 +378,7 @@ public void testPartContentSubscriberThrottling() { Consumer consumer = (part) -> { latch.countDown(); if (latch.getCount() == 2) { - assertThat(part.headers().values("Content-Id"), hasItems("part1")); + assertThat(part.headers().values(CONTENT_ID), hasItems("part1")); } part.content().subscribe(new Subscriber() { diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTest.java index 49940694da9..a81f977f183 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/MultiPartEncoderTest.java @@ -68,7 +68,7 @@ public void testEncodeOnePartWithHeaders() throws Exception { String message = encodeParts(boundary, WriteableBodyPart.builder() .headers(WriteableBodyPartHeaders.builder() - .contentType(MediaType.TEXT_PLAIN) + .contentType(HttpMediaType.TEXT_PLAIN) .build()) .entity("part1") .build()); diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/Utils.java b/media/multipart/src/test/java/io/helidon/media/multipart/Utils.java new file mode 100644 index 00000000000..858cf5757fd --- /dev/null +++ b/media/multipart/src/test/java/io/helidon/media/multipart/Utils.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2022 Oracle and/or its affiliates. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.helidon.media.multipart; + +import java.nio.ByteBuffer; + +class Utils { + static byte[] toByteArray(ByteBuffer byteBuffer) { + byte[] buff = new byte[byteBuffer.remaining()]; + return toByteArray(byteBuffer, buff, 0); + } + + static byte[] toByteArray(ByteBuffer byteBuffer, byte[] buff, int destPos) { + if (byteBuffer.hasArray()) { + System.arraycopy(byteBuffer.array(), byteBuffer.arrayOffset() + byteBuffer.position(), buff, destPos, buff.length); + } else { + byteBuffer.get(buff, destPos, byteBuffer.remaining()); + } + return buff; + } +} diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/VirtualBufferTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/VirtualBufferTest.java index 8269a17c614..8beae1d814c 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/VirtualBufferTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/VirtualBufferTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,6 @@ import java.nio.ByteBuffer; import java.util.List; -import io.helidon.common.http.Utils; import io.helidon.media.multipart.VirtualBuffer.BufferEntry; import org.junit.jupiter.api.Test; From 27e3971ce564aac995510ddb8436002e6504598c Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:56:28 +0200 Subject: [PATCH 26/54] FT refactored to new common. --- fault-tolerance/pom.xml | 14 ++++++++++++++ .../src/test/resources/logging.properties | 4 ++-- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/fault-tolerance/pom.xml b/fault-tolerance/pom.xml index 4cee896d62d..d81a5b40e8c 100644 --- a/fault-tolerance/pom.xml +++ b/fault-tolerance/pom.xml @@ -80,4 +80,18 @@ test + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + diff --git a/fault-tolerance/src/test/resources/logging.properties b/fault-tolerance/src/test/resources/logging.properties index fd324b95959..8265e94c772 100644 --- a/fault-tolerance/src/test/resources/logging.properties +++ b/fault-tolerance/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2020 Oracle and/or its affiliates. +# Copyright (c) 2020, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n From a820b24aca10d340491c3575a89e8c2f5d02901b Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:56:42 +0200 Subject: [PATCH 27/54] Health refactored to new common. --- .../test/java/io/helidon/health/checks/ConfigTest.java | 6 +++--- .../src/main/java/io/helidon/health/HealthSupport.java | 8 ++++---- .../test/java/io/helidon/health/HealthServerTest.java | 9 ++++----- 3 files changed, 11 insertions(+), 12 deletions(-) diff --git a/health/health-checks/src/test/java/io/helidon/health/checks/ConfigTest.java b/health/health-checks/src/test/java/io/helidon/health/checks/ConfigTest.java index ff7d1054ced..1bfab1b0961 100644 --- a/health/health-checks/src/test/java/io/helidon/health/checks/ConfigTest.java +++ b/health/health-checks/src/test/java/io/helidon/health/checks/ConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import java.util.concurrent.TimeoutException; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.config.Config; import io.helidon.config.ConfigSources; import io.helidon.health.HealthSupport; @@ -106,7 +106,7 @@ private JsonObject runWithConfig(String configKey, int expectedStatus) throws In WebClientResponse response = webClientBuilder(webServer) .build() .get() - .accept(MediaType.APPLICATION_JSON) + .accept(HttpMediaType.APPLICATION_JSON) .path("health/live") .submit() .await(); diff --git a/health/health/src/main/java/io/helidon/health/HealthSupport.java b/health/health/src/main/java/io/helidon/health/HealthSupport.java index 44901fb0293..a61f9b17532 100644 --- a/health/health/src/main/java/io/helidon/health/HealthSupport.java +++ b/health/health/src/main/java/io/helidon/health/HealthSupport.java @@ -215,7 +215,7 @@ HealthResponse callHealthChecks(List healthChecks) { .findFirst() .orElse(Status.UP); - Http.ResponseStatus httpStatus = responses.stream() + Http.Status httpStatus = responses.stream() .filter(HcResponse::internalError) .findFirst() .map(it -> Http.Status.INTERNAL_SERVER_ERROR_500) @@ -578,15 +578,15 @@ public Optional> data() { } static final class HealthResponse { - private final Http.ResponseStatus status; + private final Http.Status status; private final JsonObject json; - private HealthResponse(Http.ResponseStatus status, JsonObject json) { + private HealthResponse(Http.Status status, JsonObject json) { this.status = status; this.json = json; } - Http.ResponseStatus status() { + Http.Status status() { return status; } diff --git a/health/health/src/test/java/io/helidon/health/HealthServerTest.java b/health/health/src/test/java/io/helidon/health/HealthServerTest.java index 180d3ab80cb..31a6aeeed03 100644 --- a/health/health/src/test/java/io/helidon/health/HealthServerTest.java +++ b/health/health/src/test/java/io/helidon/health/HealthServerTest.java @@ -23,11 +23,8 @@ import java.util.logging.Level; import java.util.logging.Logger; -import jakarta.json.Json; -import jakarta.json.JsonObject; - import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.media.common.MessageBodyReadableContent; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; @@ -37,6 +34,8 @@ import io.helidon.webserver.Service; import io.helidon.webserver.WebServer; +import jakarta.json.Json; +import jakarta.json.JsonObject; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; @@ -143,7 +142,7 @@ private static void checkResponse(Supplier requestFacto try { response = requestFactory.get() .path(requestPath) - .accept(MediaType.APPLICATION_JSON) + .accept(HttpMediaType.APPLICATION_JSON) .request() .await(); From dfdf6a8ef13d12b30f78059f8d70749c8dbddb2f Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:57:11 +0200 Subject: [PATCH 28/54] Metrics refactored to new common. --- .../metrics/api/TestMetricsSettings.java | 17 ++++---------- metrics/metrics/pom.xml | 9 ++++++++ .../io/helidon/metrics/MetricsSupport.java | 23 ++++++++++--------- .../java/io/helidon/metrics/TestServer.java | 11 ++++----- ...verWithKeyPerformanceIndicatorMetrics.java | 6 ++--- .../metrics/prometheus/PrometheusSupport.java | 6 ++--- 6 files changed, 36 insertions(+), 36 deletions(-) diff --git a/metrics/api/src/test/java/io/helidon/metrics/api/TestMetricsSettings.java b/metrics/api/src/test/java/io/helidon/metrics/api/TestMetricsSettings.java index f9420038ec0..98fb8127659 100644 --- a/metrics/api/src/test/java/io/helidon/metrics/api/TestMetricsSettings.java +++ b/metrics/api/src/test/java/io/helidon/metrics/api/TestMetricsSettings.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,31 +15,22 @@ */ package io.helidon.metrics.api; -import io.helidon.common.http.MediaType; -import io.helidon.common.media.type.MediaTypes; +import java.io.IOException; +import java.util.Set; + import io.helidon.config.Config; import io.helidon.config.ConfigSources; -import io.helidon.config.yaml.YamlConfigParser; - import org.eclipse.microprofile.metrics.MetricRegistry; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStream; -import java.io.InputStreamReader; -import java.io.StringReader; -import java.util.Set; - import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.is; import static org.hamcrest.Matchers.not; import static org.hamcrest.Matchers.notNullValue; -import static org.hamcrest.Matchers.nullValue; public class TestMetricsSettings { diff --git a/metrics/metrics/pom.xml b/metrics/metrics/pom.xml index 1412af15207..99e3049a62a 100644 --- a/metrics/metrics/pom.xml +++ b/metrics/metrics/pom.xml @@ -102,6 +102,15 @@ + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + org.apache.maven.plugins maven-surefire-plugin diff --git a/metrics/metrics/src/main/java/io/helidon/metrics/MetricsSupport.java b/metrics/metrics/src/main/java/io/helidon/metrics/MetricsSupport.java index 36f22678b9a..cb1b03436ec 100644 --- a/metrics/metrics/src/main/java/io/helidon/metrics/MetricsSupport.java +++ b/metrics/metrics/src/main/java/io/helidon/metrics/MetricsSupport.java @@ -36,7 +36,8 @@ import java.util.stream.Stream; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.DeprecatedConfig; import io.helidon.media.common.MessageBodyWriter; @@ -196,7 +197,7 @@ public static Builder builder() { } private static MediaType findBestAccepted(RequestHeaders headers) { - Optional mediaType = headers.bestAccepted(MediaType.TEXT_PLAIN, MediaType.APPLICATION_JSON); + Optional mediaType = headers.bestAccepted(MediaTypes.TEXT_PLAIN, MediaTypes.APPLICATION_JSON); return mediaType.orElse(null); } @@ -219,9 +220,9 @@ private static void getAll(ServerRequest req, ServerResponse res, Registry regis } MediaType mediaType = findBestAccepted(req.headers()); - if (mediaType == MediaType.APPLICATION_JSON) { + if (mediaType == MediaTypes.APPLICATION_JSON) { sendJson(res, toJsonData(registry)); - } else if (mediaType == MediaType.TEXT_PLAIN) { + } else if (mediaType == MediaTypes.TEXT_PLAIN) { res.send(toPrometheusData(registry)); } else { res.status(Http.Status.NOT_ACCEPTABLE_406); @@ -237,7 +238,7 @@ private void optionsAll(ServerRequest req, ServerResponse res, Registry registry } // Options returns only the metadata, so it's OK to allow caching. - if (req.headers().isAccepted(MediaType.APPLICATION_JSON)) { + if (req.headers().isAccepted(MediaTypes.APPLICATION_JSON)) { sendJson(res, toJsonMeta(registry)); } else { res.status(Http.Status.NOT_ACCEPTABLE_406); @@ -524,9 +525,9 @@ private void getByName(ServerRequest req, ServerResponse res, Registry registry) registry.getOptionalMetricEntry(metricName) .ifPresentOrElse(entry -> { MediaType mediaType = findBestAccepted(req.headers()); - if (mediaType == MediaType.APPLICATION_JSON) { + if (mediaType == MediaTypes.APPLICATION_JSON) { sendJson(res, jsonDataByName(registry, metricName)); - } else if (mediaType == MediaType.TEXT_PLAIN) { + } else if (mediaType == MediaTypes.TEXT_PLAIN) { res.send(prometheusDataByName(registry, metricName)); } else { res.status(Http.Status.NOT_ACCEPTABLE_406); @@ -569,9 +570,9 @@ private static void sendJson(ServerResponse res, JsonObject object) { private void getMultiple(ServerRequest req, ServerResponse res, Registry... registries) { MediaType mediaType = findBestAccepted(req.headers()); res.cachingStrategy(ServerResponse.CachingStrategy.NO_CACHING); - if (mediaType == MediaType.APPLICATION_JSON) { + if (mediaType == MediaTypes.APPLICATION_JSON) { sendJson(res, toJsonData(registries)); - } else if (mediaType == MediaType.TEXT_PLAIN) { + } else if (mediaType == MediaTypes.TEXT_PLAIN) { res.send(toPrometheusData(registries)); } else { res.status(Http.Status.NOT_ACCEPTABLE_406); @@ -581,7 +582,7 @@ private void getMultiple(ServerRequest req, ServerResponse res, Registry... regi private void optionsMultiple(ServerRequest req, ServerResponse res, Registry... registries) { // Options returns metadata only, so do not discourage caching. - if (req.headers().isAccepted(MediaType.APPLICATION_JSON)) { + if (req.headers().isAccepted(MediaTypes.APPLICATION_JSON)) { sendJson(res, toJsonMeta(registries)); } else { res.status(Http.Status.NOT_ACCEPTABLE_406); @@ -595,7 +596,7 @@ private void optionsOne(ServerRequest req, ServerResponse res, Registry registry Optional.ofNullable(registry.metadataWithIDs(metricName)) .ifPresentOrElse(entry -> { // Options returns only metadata, so do not discourage caching. - if (req.headers().isAccepted(MediaType.APPLICATION_JSON)) { + if (req.headers().isAccepted(MediaTypes.APPLICATION_JSON)) { JsonObjectBuilder builder = JSON.createObjectBuilder(); // The returned list of metric IDs is guaranteed to have at least one element at this point. // Use the first to find a metric which will know how to create the metadata output. diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/TestServer.java b/metrics/metrics/src/test/java/io/helidon/metrics/TestServer.java index 3180af9855e..7cce1625b3d 100644 --- a/metrics/metrics/src/test/java/io/helidon/metrics/TestServer.java +++ b/metrics/metrics/src/test/java/io/helidon/metrics/TestServer.java @@ -23,8 +23,7 @@ import java.util.logging.Level; import java.util.logging.Logger; -import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; @@ -110,7 +109,7 @@ public void checkNormalURL() throws ExecutionException, InterruptedException { WebClientResponse response = webClientBuilder .build() .get() - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path("metrics") .submit() .await(CLIENT_TIMEOUT); @@ -126,7 +125,7 @@ public void checkVendorURL() { WebClientResponse response = webClientBuilder .build() .get() - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path("metrics/vendor") .submit() .await(CLIENT_TIMEOUT); @@ -176,7 +175,7 @@ void checkMetricsForExecutorService() { WebClientRequestBuilder metricsRequestBuilder = webClientBuilder .build() .get() - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path("metrics/vendor"); WebClientResponse response = metricsRequestBuilder @@ -197,7 +196,7 @@ void checkMetricsForExecutorService() { WebClientResponse slowGreetResponse = webClientBuilder .build() .get() - .accept(MediaType.TEXT_PLAIN) + .accept(MediaTypes.TEXT_PLAIN) .path("greet/slow") .submit() .await(CLIENT_TIMEOUT); diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/TestServerWithKeyPerformanceIndicatorMetrics.java b/metrics/metrics/src/test/java/io/helidon/metrics/TestServerWithKeyPerformanceIndicatorMetrics.java index 4f26b2d8d70..21b4405fbd3 100644 --- a/metrics/metrics/src/test/java/io/helidon/metrics/TestServerWithKeyPerformanceIndicatorMetrics.java +++ b/metrics/metrics/src/test/java/io/helidon/metrics/TestServerWithKeyPerformanceIndicatorMetrics.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeoutException; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.common.reactive.Single; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.metrics.api.KeyPerformanceIndicatorMetricsSettings; @@ -87,7 +87,7 @@ void checkInflightRequests() throws InterruptedException, ExecutionException { Single response = webClientBuilder .build() .get() - .accept(MediaType.APPLICATION_JSON) + .accept(HttpMediaType.APPLICATION_JSON) .path("greet/slow") .request(String.class); GreetService.awaitSlowRequestStarted(); diff --git a/metrics/prometheus/src/main/java/io/helidon/metrics/prometheus/PrometheusSupport.java b/metrics/prometheus/src/main/java/io/helidon/metrics/prometheus/PrometheusSupport.java index 8fdf92a577d..243a65664f8 100644 --- a/metrics/prometheus/src/main/java/io/helidon/metrics/prometheus/PrometheusSupport.java +++ b/metrics/prometheus/src/main/java/io/helidon/metrics/prometheus/PrometheusSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,7 @@ import java.util.HashSet; import java.util.Set; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerRequest; import io.helidon.webserver.ServerResponse; @@ -48,7 +48,7 @@ public final class PrometheusSupport implements Service { */ private static final String DEFAULT_PATH = "/metrics"; - private static final MediaType CONTENT_TYPE = MediaType.parse("text/plain; version=0.0.4; charset=utf-8"); + private static final HttpMediaType CONTENT_TYPE = HttpMediaType.create("text/plain; version=0.0.4; charset=utf-8"); private final CollectorRegistry collectorRegistry; private final String path; From be28c65c5bce55ba658aba4e07bd622955f9951c Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:57:36 +0200 Subject: [PATCH 29/54] LRA and messaging refactored to new common. --- .../client/narayana/NarayanaClient.java | 12 ++++++------ lra/coordinator/client/spi/pom.xml | 4 ++++ .../client/spi/src/main/java/module-info.java | 4 +++- .../lra/coordinator/CoordinatorService.java | 16 +++++++++------- messaging/connectors/aq/pom.xml | 15 +++++++++++++++ messaging/connectors/jms/pom.xml | 15 +++++++++++++++ messaging/messaging/pom.xml | 15 +++++++++++++++ .../messaging/src/main/java/module-info.java | 1 + 8 files changed, 68 insertions(+), 14 deletions(-) diff --git a/lra/coordinator/client/narayana-client/src/main/java/io/helidon/lra/coordinator/client/narayana/NarayanaClient.java b/lra/coordinator/client/narayana-client/src/main/java/io/helidon/lra/coordinator/client/narayana/NarayanaClient.java index bcf62571685..1f91839f518 100644 --- a/lra/coordinator/client/narayana-client/src/main/java/io/helidon/lra/coordinator/client/narayana/NarayanaClient.java +++ b/lra/coordinator/client/narayana-client/src/main/java/io/helidon/lra/coordinator/client/narayana/NarayanaClient.java @@ -42,14 +42,14 @@ import io.helidon.webclient.WebClientResponse; import org.eclipse.microprofile.lra.annotation.LRAStatus; - -import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER; -import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_RECOVERY_HEADER; +import org.eclipse.microprofile.lra.annotation.ws.rs.LRA; /** * Narayana LRA coordinator client. */ public class NarayanaClient implements CoordinatorClient { + private static final Http.HeaderName LRA_HTTP_CONTEXT_HEADER = Http.Header.create(LRA.LRA_HTTP_CONTEXT_HEADER); + private static final Http.HeaderName LRA_HTTP_RECOVERY_HEADER = Http.Header.create(LRA.LRA_HTTP_RECOVERY_HEADER); private static final Logger LOGGER = Logger.getLogger(NarayanaClient.class.getName()); @@ -103,7 +103,7 @@ private Single startInternal(URI parentLRA, String clientID, PropagatedHead .queryParam(QUERY_PARAM_PARENT_LRA, parentLRA == null ? "" : parentLRA.toASCIIString()) .submit() .flatMap(res -> { - Http.ResponseStatus status = res.status(); + Http.Status status = res.status(); if (status.code() != 201) { return res.content().as(String.class).flatMap(cont -> connectionError("Unexpected response " + status + " from coordinator " @@ -200,7 +200,7 @@ public Single> join(URI lraId, .queryParam(QUERY_PARAM_TIME_LIMIT, String.valueOf(timeLimit)) .headers(h -> { h.add(HEADER_LINK, links); // links are expected either in header - headers.toMap().forEach(h::add); // header propagation + headers.toMap().forEach((name, value) -> h.set(Http.Header.create(name), value)); // header propagation return h; }) .submit(links) // or as a body @@ -343,7 +343,7 @@ static URI parseBaseUri(String lraUri) { private Function copyHeaders(PropagatedHeaders headers) { return wcHeaders -> { - headers.toMap().forEach(wcHeaders::add); + headers.toMap().forEach((key, value) -> wcHeaders.set(Http.Header.create(key), value)); return wcHeaders; }; } diff --git a/lra/coordinator/client/spi/pom.xml b/lra/coordinator/client/spi/pom.xml index 3d1994ed46c..a07207c0eac 100644 --- a/lra/coordinator/client/spi/pom.xml +++ b/lra/coordinator/client/spi/pom.xml @@ -40,5 +40,9 @@ io.helidon.common helidon-common-reactive + + io.helidon.common + helidon-common-http + \ No newline at end of file diff --git a/lra/coordinator/client/spi/src/main/java/module-info.java b/lra/coordinator/client/spi/src/main/java/module-info.java index 075c4ae2aad..5c9db978c0f 100644 --- a/lra/coordinator/client/spi/src/main/java/module-info.java +++ b/lra/coordinator/client/spi/src/main/java/module-info.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,5 +20,7 @@ module io.helidon.lra.coordinator.client { requires microprofile.lra.api; requires io.helidon.common.reactive; + requires io.helidon.common.http; + exports io.helidon.lra.coordinator.client; } \ No newline at end of file diff --git a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/CoordinatorService.java b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/CoordinatorService.java index da95aa49d71..25d32723cc8 100644 --- a/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/CoordinatorService.java +++ b/lra/coordinator/server/src/main/java/io/helidon/lra/coordinator/CoordinatorService.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,6 +30,7 @@ import java.util.stream.Stream; import io.helidon.common.LazyValue; +import io.helidon.common.http.Http; import io.helidon.common.reactive.Single; import io.helidon.config.Config; import io.helidon.media.common.MessageBodyWriter; @@ -50,9 +51,7 @@ import jakarta.json.JsonStructure; import jakarta.json.JsonValue; import org.eclipse.microprofile.lra.annotation.LRAStatus; - -import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_CONTEXT_HEADER; -import static org.eclipse.microprofile.lra.annotation.ws.rs.LRA.LRA_HTTP_RECOVERY_HEADER; +import org.eclipse.microprofile.lra.annotation.ws.rs.LRA; /** * LRA coordinator with Narayana like rest api. @@ -70,6 +69,9 @@ public class CoordinatorService implements Service { static final String DEFAULT_COORDINATOR_URL = "http://localhost:8070/lra-coordinator"; private static final Logger LOGGER = Logger.getLogger(CoordinatorService.class.getName()); + private static final Http.HeaderName LRA_HTTP_CONTEXT_HEADER = Http.Header.create(LRA.LRA_HTTP_CONTEXT_HEADER); + private static final Http.HeaderName LRA_HTTP_RECOVERY_HEADER = Http.Header.create(LRA.LRA_HTTP_RECOVERY_HEADER); + private static final Set RECOVERABLE_STATUSES = Set.of(LRAStatus.Cancelling, LRAStatus.Closing, LRAStatus.Active); private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); private static final MessageBodyWriter JSON_WRITER = JsonpSupport.create().writerInstance(); @@ -223,7 +225,7 @@ private void cancel(ServerRequest req, ServerResponse res) { private void join(ServerRequest req, ServerResponse res) { String lraId = req.path().param("LraId"); - String compensatorLink = req.headers().first("Link").orElse(""); + String compensatorLink = req.headers().first(Http.Header.LINK).orElse(""); Lra lra = lraPersistentRegistry.get(lraId); if (lra == null) { @@ -237,8 +239,8 @@ private void join(ServerRequest req, ServerResponse res) { lra.addParticipant(compensatorLink); String recoveryUrl = coordinatorUriWithPath("/" + lraId + "/recovery").toASCIIString(); - res.headers().put(LRA_HTTP_RECOVERY_HEADER, recoveryUrl); - res.headers().put("Location", recoveryUrl); + res.headers().set(LRA_HTTP_RECOVERY_HEADER, recoveryUrl); + res.headers().set(Http.Header.LOCATION, recoveryUrl); res.status(200) .send(recoveryUrl); } diff --git a/messaging/connectors/aq/pom.xml b/messaging/connectors/aq/pom.xml index af0312fd719..fdda51447ff 100644 --- a/messaging/connectors/aq/pom.xml +++ b/messaging/connectors/aq/pom.xml @@ -82,4 +82,19 @@ test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + diff --git a/messaging/connectors/jms/pom.xml b/messaging/connectors/jms/pom.xml index 6a43d824c4b..0b063a50a3b 100644 --- a/messaging/connectors/jms/pom.xml +++ b/messaging/connectors/jms/pom.xml @@ -83,4 +83,19 @@ test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + diff --git a/messaging/messaging/pom.xml b/messaging/messaging/pom.xml index 9e9c2417a13..6fdf1fe5831 100644 --- a/messaging/messaging/pom.xml +++ b/messaging/messaging/pom.xml @@ -74,4 +74,19 @@ test + + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + diff --git a/messaging/messaging/src/main/java/module-info.java b/messaging/messaging/src/main/java/module-info.java index 45af45a206b..bd03dd9d43d 100644 --- a/messaging/messaging/src/main/java/module-info.java +++ b/messaging/messaging/src/main/java/module-info.java @@ -29,6 +29,7 @@ requires transitive microprofile.config.api; requires transitive microprofile.reactive.messaging.api; requires transitive microprofile.reactive.streams.operators.api; + requires transitive io.helidon.common.reactive; exports io.helidon.messaging; } From b03b2f7b33cfac21150e30389cbcf9f0d0ebabf2 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:57:54 +0200 Subject: [PATCH 30/54] Scheduling refactored to new common. --- scheduling/src/test/resources/logging.properties | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/scheduling/src/test/resources/logging.properties b/scheduling/src/test/resources/logging.properties index f10d960f623..e79691c6451 100644 --- a/scheduling/src/test/resources/logging.properties +++ b/scheduling/src/test/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2021 Oracle and/or its affiliates. +# Copyright (c) 2021, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n From ec38e96f8af96d1c9eafdeaa81754bdff91b06ba Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:58:20 +0200 Subject: [PATCH 31/54] GraphQL refactored to new common. --- graphql/server/pom.xml | 14 ++++++++++++++ .../io/helidon/graphql/server/GraphQlSupport.java | 6 +++--- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/graphql/server/pom.xml b/graphql/server/pom.xml index 4a97e4061c9..b8a0f1e7e73 100644 --- a/graphql/server/pom.xml +++ b/graphql/server/pom.xml @@ -61,4 +61,18 @@ test + + + + + org.apache.maven.plugins + maven-compiler-plugin + + + --enable-preview + + + + + diff --git a/graphql/server/src/main/java/io/helidon/graphql/server/GraphQlSupport.java b/graphql/server/src/main/java/io/helidon/graphql/server/GraphQlSupport.java index 05506d0e659..601a0cf1d26 100644 --- a/graphql/server/src/main/java/io/helidon/graphql/server/GraphQlSupport.java +++ b/graphql/server/src/main/java/io/helidon/graphql/server/GraphQlSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,7 +26,7 @@ import io.helidon.common.GenericType; import io.helidon.common.configurable.ServerThreadPoolSupplier; -import io.helidon.common.http.Parameters; +import io.helidon.common.uri.UriQuery; import io.helidon.config.Config; import io.helidon.media.common.MessageBodyReader; import io.helidon.media.common.MessageBodyWriter; @@ -119,7 +119,7 @@ private void graphQlPost(ServerRequest req, ServerResponse res) { // handle GET request for GraphQL endpoint private void graphQlGet(ServerRequest req, ServerResponse res) { - Parameters queryParams = req.queryParams(); + UriQuery queryParams = req.queryParams(); String query = queryParams.first("query").orElseThrow(() -> new IllegalStateException("Query must be defined")); String operationName = queryParams.first("operationName").orElse(null); Map variables = queryParams.first("variables") From 1c9fb4792b9dfe488960dfbeb337f80aeb1ec047 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:58:36 +0200 Subject: [PATCH 32/54] OpenAPI refactored to new common. --- .../io/helidon/openapi/OpenAPISupport.java | 39 ++++++------- .../openapi/ServerModelReaderTest.java | 12 ++-- .../java/io/helidon/openapi/ServerTest.java | 27 ++++----- .../java/io/helidon/openapi/TestCors.java | 10 ++-- .../java/io/helidon/openapi/TestUtil.java | 57 +++++++++---------- 5 files changed, 72 insertions(+), 73 deletions(-) diff --git a/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java b/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java index 6ebcb9d4c80..0ff19b432f8 100644 --- a/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java +++ b/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java @@ -43,7 +43,8 @@ import java.util.stream.Collectors; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.metadata.Configured; import io.helidon.config.metadata.ConfiguredOption; @@ -108,10 +109,10 @@ public abstract class OpenAPISupport implements Service { * Default media type used in responses in absence of incoming Accept * header. */ - public static final MediaType DEFAULT_RESPONSE_MEDIA_TYPE = MediaType.APPLICATION_OPENAPI_YAML; + public static final MediaType DEFAULT_RESPONSE_MEDIA_TYPE = MediaTypes.APPLICATION_OPENAPI_YAML; private enum QueryParameterRequestedFormat { - JSON(MediaType.APPLICATION_JSON), YAML(MediaType.APPLICATION_OPENAPI_YAML); + JSON(MediaTypes.APPLICATION_JSON), YAML(MediaTypes.APPLICATION_OPENAPI_YAML); static QueryParameterRequestedFormat chooseFormat(String format) { return QueryParameterRequestedFormat.valueOf(format); @@ -581,16 +582,16 @@ private static Object convertJsonValue(JsonValue jsonValue) { enum OpenAPIMediaType { JSON(Format.JSON, - new MediaType[]{MediaType.APPLICATION_OPENAPI_JSON, - MediaType.APPLICATION_JSON}, + new MediaType[]{MediaTypes.APPLICATION_OPENAPI_JSON, + MediaTypes.APPLICATION_JSON}, "json"), YAML(Format.YAML, - new MediaType[]{MediaType.APPLICATION_OPENAPI_YAML, - MediaType.APPLICATION_X_YAML, - MediaType.APPLICATION_YAML, - MediaType.TEXT_PLAIN, - MediaType.TEXT_X_YAML, - MediaType.TEXT_YAML}, + new MediaType[]{MediaTypes.APPLICATION_OPENAPI_YAML, + MediaTypes.APPLICATION_X_YAML, + MediaTypes.APPLICATION_YAML, + MediaTypes.TEXT_PLAIN, + MediaTypes.TEXT_X_YAML, + MediaTypes.TEXT_YAML}, "yaml", "yml"); private static final OpenAPIMediaType DEFAULT_TYPE = YAML; @@ -658,14 +659,14 @@ private static OpenAPIMediaType byFormat(Format format) { */ private static MediaType[] preferredOrdering() { return new MediaType[]{ - MediaType.APPLICATION_OPENAPI_YAML, - MediaType.APPLICATION_X_YAML, - MediaType.APPLICATION_YAML, - MediaType.APPLICATION_OPENAPI_JSON, - MediaType.APPLICATION_JSON, - MediaType.TEXT_X_YAML, - MediaType.TEXT_YAML, - MediaType.TEXT_PLAIN + MediaTypes.APPLICATION_OPENAPI_YAML, + MediaTypes.APPLICATION_X_YAML, + MediaTypes.APPLICATION_YAML, + MediaTypes.APPLICATION_OPENAPI_JSON, + MediaTypes.APPLICATION_JSON, + MediaTypes.TEXT_X_YAML, + MediaTypes.TEXT_YAML, + MediaTypes.TEXT_PLAIN }; } } diff --git a/openapi/src/test/java/io/helidon/openapi/ServerModelReaderTest.java b/openapi/src/test/java/io/helidon/openapi/ServerModelReaderTest.java index 8618636b646..c1df7ecd768 100644 --- a/openapi/src/test/java/io/helidon/openapi/ServerModelReaderTest.java +++ b/openapi/src/test/java/io/helidon/openapi/ServerModelReaderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,7 +17,7 @@ import java.net.HttpURLConnection; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.ConfigSources; import io.helidon.openapi.test.MyModelReader; @@ -65,8 +65,8 @@ public void checkCustomModelReader() throws Exception { webServer.port(), "GET", SIMPLE_PROPS_PATH, - MediaType.APPLICATION_OPENAPI_JSON); - TestUtil.validateResponseMediaType(cnx, MediaType.APPLICATION_OPENAPI_JSON); + MediaTypes.APPLICATION_OPENAPI_JSON); + TestUtil.validateResponseMediaType(cnx, MediaTypes.APPLICATION_OPENAPI_JSON); JsonStructure json = TestUtil.jsonFromResponse(cnx); // The model reader adds the following key/value (among others) to the model. JsonValue v = json.getValue(String.format("/paths/%s/get/summary", @@ -84,8 +84,8 @@ public void makeSureFilteredPathIsMissing() throws Exception { webServer.port(), "GET", SIMPLE_PROPS_PATH, - MediaType.APPLICATION_OPENAPI_JSON); - TestUtil.validateResponseMediaType(cnx, MediaType.APPLICATION_OPENAPI_JSON); + MediaTypes.APPLICATION_OPENAPI_JSON); + TestUtil.validateResponseMediaType(cnx, MediaTypes.APPLICATION_OPENAPI_JSON); JsonStructure json = TestUtil.jsonFromResponse(cnx); /* * Although the model reader adds this path, the filter should have diff --git a/openapi/src/test/java/io/helidon/openapi/ServerTest.java b/openapi/src/test/java/io/helidon/openapi/ServerTest.java index 3833f2d447d..a64d2475f88 100644 --- a/openapi/src/test/java/io/helidon/openapi/ServerTest.java +++ b/openapi/src/test/java/io/helidon/openapi/ServerTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,7 +20,8 @@ import java.util.Map; import java.util.function.Consumer; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.ConfigSources; import io.helidon.webserver.WebServer; @@ -93,7 +94,7 @@ public void testGreetingAsYAML() throws Exception { greetingWebServer.port(), "GET", GREETING_PATH, - MediaType.APPLICATION_OPENAPI_YAML); + MediaTypes.APPLICATION_OPENAPI_YAML); Map openAPIDocument = TestUtil.yamlFromResponse(cnx); ArrayList> servers = TestUtil.as( @@ -129,7 +130,7 @@ public void testGreetingAsConfig() throws Exception { greetingWebServer.port(), "GET", GREETING_PATH, - MediaType.APPLICATION_OPENAPI_YAML); + MediaTypes.APPLICATION_OPENAPI_YAML); Config c = TestUtil.configFromResponse(cnx); assertEquals("Sets the greeting prefix", TestUtil.fromConfig(c, "paths./greet/greeting.put.summary")); @@ -148,10 +149,10 @@ public void testGreetingAsConfig() throws Exception { */ @Test public void checkExplicitResponseMediaTypeViaHeaders() throws Exception { - connectAndConsumePayload(MediaType.APPLICATION_OPENAPI_YAML); - connectAndConsumePayload(MediaType.APPLICATION_YAML); - connectAndConsumePayload(MediaType.APPLICATION_OPENAPI_JSON); - connectAndConsumePayload(MediaType.APPLICATION_JSON); + connectAndConsumePayload(MediaTypes.APPLICATION_OPENAPI_YAML); + connectAndConsumePayload(MediaTypes.APPLICATION_YAML); + connectAndConsumePayload(MediaTypes.APPLICATION_OPENAPI_JSON); + connectAndConsumePayload(MediaTypes.APPLICATION_JSON); } @Test @@ -159,12 +160,12 @@ void checkExplicitResponseMediaTypeViaQueryParameter() throws Exception { TestUtil.connectAndConsumePayload(greetingWebServer.port(), GREETING_PATH, "format=JSON", - MediaType.APPLICATION_JSON); + MediaTypes.APPLICATION_JSON); TestUtil.connectAndConsumePayload(greetingWebServer.port(), GREETING_PATH, "format=YAML", - MediaType.APPLICATION_OPENAPI_YAML); + MediaTypes.APPLICATION_OPENAPI_YAML); } /** @@ -198,7 +199,7 @@ private void commonTestTimeAsConfig(Consumer headerSetter) th timeWebServer.port(), "GET", TIME_PATH, - MediaType.APPLICATION_OPENAPI_YAML); + MediaTypes.APPLICATION_OPENAPI_YAML); if (headerSetter != null) { headerSetter.accept(cnx); } @@ -217,12 +218,12 @@ public void ensureNoCrosstalkAmongPorts() throws Exception { timeWebServer.port(), "GET", TIME_PATH, - MediaType.APPLICATION_OPENAPI_YAML); + MediaTypes.APPLICATION_OPENAPI_YAML); HttpURLConnection greetingCnx = TestUtil.getURLConnection( greetingWebServer.port(), "GET", GREETING_PATH, - MediaType.APPLICATION_OPENAPI_YAML); + MediaTypes.APPLICATION_OPENAPI_YAML); Config greetingConfig = TestUtil.configFromResponse(greetingCnx); Config timeConfig = TestUtil.configFromResponse(timeCnx); assertFalse(timeConfig.get("paths./greet/greeting.put.summary").exists(), diff --git a/openapi/src/test/java/io/helidon/openapi/TestCors.java b/openapi/src/test/java/io/helidon/openapi/TestCors.java index 6ed5292ff04..fa2ee48d256 100644 --- a/openapi/src/test/java/io/helidon/openapi/TestCors.java +++ b/openapi/src/test/java/io/helidon/openapi/TestCors.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ import java.net.HttpURLConnection; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.webserver.WebServer; @@ -48,7 +48,7 @@ public void testCrossOriginGreetingWithoutCors() throws Exception { greetingWebServer.port(), "GET", GREETING_PATH, - MediaType.APPLICATION_OPENAPI_YAML); + MediaTypes.APPLICATION_OPENAPI_YAML); cnx.setRequestProperty("Origin", "http://foo.bar"); cnx.setRequestProperty("Host", "localhost"); @@ -63,7 +63,7 @@ public void testTimeRestrictedCorsValidOrigin() throws Exception { timeWebServer.port(), "GET", TIME_PATH, - MediaType.APPLICATION_OPENAPI_YAML); + MediaTypes.APPLICATION_OPENAPI_YAML); cnx.setRequestProperty("Origin", "http://foo.bar"); cnx.setRequestProperty("Host", "localhost"); @@ -76,7 +76,7 @@ public void testTimeRestrictedCorsInvalidOrigin() throws Exception { timeWebServer.port(), "GET", TIME_PATH, - MediaType.APPLICATION_OPENAPI_YAML); + MediaTypes.APPLICATION_OPENAPI_YAML); cnx.setRequestProperty("Origin", "http://other.com"); cnx.setRequestProperty("Host", "localhost"); diff --git a/openapi/src/test/java/io/helidon/openapi/TestUtil.java b/openapi/src/test/java/io/helidon/openapi/TestUtil.java index 00f46d057a1..ab3e8592a13 100644 --- a/openapi/src/test/java/io/helidon/openapi/TestUtil.java +++ b/openapi/src/test/java/io/helidon/openapi/TestUtil.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,10 +31,11 @@ import java.util.logging.Logger; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.ConfigSources; -import io.helidon.config.yaml.YamlConfigParser; import io.helidon.webserver.Routing; import io.helidon.webserver.WebServer; @@ -81,8 +82,8 @@ public static WebServer startServer(OpenAPISupport.Builder builder) { * @throws IOException in case of errors reading the HTTP response payload */ public static String stringYAMLFromResponse(HttpURLConnection cnx) throws IOException { - MediaType returnedMediaType = mediaTypeFromResponse(cnx); - assertTrue(MediaType.APPLICATION_OPENAPI_YAML.test(returnedMediaType), + HttpMediaType returnedMediaType = mediaTypeFromResponse(cnx); + assertTrue(HttpMediaType.create(MediaTypes.APPLICATION_OPENAPI_YAML).test(returnedMediaType), "Unexpected returned media type"); return stringFromResponse(cnx, returnedMediaType); } @@ -103,11 +104,11 @@ public static String stringYAMLFromResponse(HttpURLConnection cnx) throws IOExce public static MediaType connectAndConsumePayload( int port, String path, MediaType expectedMediaType) throws Exception { HttpURLConnection cnx = getURLConnection(port, "GET", path, expectedMediaType); - MediaType actualMT = validateResponseMediaType(cnx, expectedMediaType); - if (actualMT.test(MediaType.APPLICATION_OPENAPI_YAML) || actualMT.test(MediaType.APPLICATION_YAML)) { + HttpMediaType actualMT = validateResponseMediaType(cnx, expectedMediaType); + if (actualMT.test(MediaTypes.APPLICATION_OPENAPI_YAML) || actualMT.test(MediaTypes.APPLICATION_YAML)) { yamlFromResponse(cnx); - } else if (actualMT.test(MediaType.APPLICATION_OPENAPI_JSON) - || actualMT.test(MediaType.APPLICATION_JSON)) { + } else if (actualMT.test(MediaTypes.APPLICATION_OPENAPI_JSON) + || actualMT.test(MediaTypes.APPLICATION_JSON)) { jsonFromResponse(cnx); } else { throw new IllegalArgumentException( @@ -119,11 +120,11 @@ public static MediaType connectAndConsumePayload( static MediaType connectAndConsumePayload( int port, String path, String queryParameter, MediaType expectedMediaType) throws Exception { HttpURLConnection cnx = getURLConnection(port, "GET", path, queryParameter); - MediaType actualMT = validateResponseMediaType(cnx, expectedMediaType); - if (actualMT.test(MediaType.APPLICATION_OPENAPI_YAML) || actualMT.test(MediaType.APPLICATION_YAML)) { + HttpMediaType actualMT = validateResponseMediaType(cnx, expectedMediaType); + if (actualMT.test(MediaTypes.APPLICATION_OPENAPI_YAML) || actualMT.test(MediaTypes.APPLICATION_YAML)) { yamlFromResponse(cnx); - } else if (actualMT.test(MediaType.APPLICATION_OPENAPI_JSON) - || actualMT.test(MediaType.APPLICATION_JSON)) { + } else if (actualMT.test(MediaTypes.APPLICATION_OPENAPI_JSON) + || actualMT.test(MediaTypes.APPLICATION_JSON)) { jsonFromResponse(cnx); } else { throw new IllegalArgumentException( @@ -139,14 +140,10 @@ static MediaType connectAndConsumePayload( * @param cnx the HttpURLConnection from which to get the content type * @return the MediaType corresponding to the content type in the response */ - public static MediaType mediaTypeFromResponse(HttpURLConnection cnx) { - MediaType returnedMediaType = MediaType.parse(cnx.getContentType()); - if (!returnedMediaType.charset().isPresent()) { - returnedMediaType = MediaType.builder() - .type(returnedMediaType.type()) - .subtype(returnedMediaType.subtype()) - .charset(Charset.defaultCharset().name()) - .build(); + public static HttpMediaType mediaTypeFromResponse(HttpURLConnection cnx) { + HttpMediaType returnedMediaType = HttpMediaType.create(cnx.getContentType()); + if (returnedMediaType.charset().isEmpty()) { + returnedMediaType = returnedMediaType.withCharset(Charset.defaultCharset().name()); } return returnedMediaType; } @@ -162,10 +159,10 @@ public static MediaType mediaTypeFromResponse(HttpURLConnection cnx) { * config */ public static Config configFromResponse(HttpURLConnection cnx) throws IOException { - MediaType mt = mediaTypeFromResponse(cnx); - String configMT = MediaType.APPLICATION_OPENAPI_YAML.test(mt) - ? YamlConfigParser.MEDIA_TYPE_APPLICATION_YAML - : MediaType.APPLICATION_JSON.toString(); + HttpMediaType mt = mediaTypeFromResponse(cnx); + MediaType configMT = HttpMediaType.create(MediaTypes.APPLICATION_OPENAPI_YAML).test(mt) + ? MediaTypes.APPLICATION_X_YAML + : MediaTypes.APPLICATION_JSON; String yaml = stringYAMLFromResponse(cnx); return Config.create(ConfigSources.create(yaml, configMT)); } @@ -181,7 +178,7 @@ public static Config configFromResponse(HttpURLConnection cnx) throws IOExceptio */ @SuppressWarnings(value = "unchecked") public static Map yamlFromResponse(HttpURLConnection cnx) throws IOException { - MediaType returnedMediaType = mediaTypeFromResponse(cnx); + HttpMediaType returnedMediaType = mediaTypeFromResponse(cnx); Yaml yaml = new Yaml(); Charset cs = Charset.defaultCharset(); if (returnedMediaType.charset().isPresent()) { @@ -266,7 +263,7 @@ public static String escapeForJsonPointer(String pointer) { * @throws Exception in case of errors reading the content type from the * response */ - public static MediaType validateResponseMediaType( + public static HttpMediaType validateResponseMediaType( HttpURLConnection cnx, MediaType expectedMediaType) throws Exception { assertEquals(Http.Status.OK_200.code(), cnx.getResponseCode(), @@ -274,8 +271,8 @@ public static MediaType validateResponseMediaType( MediaType expectedMT = expectedMediaType != null ? expectedMediaType : OpenAPISupport.DEFAULT_RESPONSE_MEDIA_TYPE; - MediaType actualMT = mediaTypeFromResponse(cnx); - assertTrue(expectedMT.test(actualMT), + HttpMediaType actualMT = mediaTypeFromResponse(cnx); + assertTrue(HttpMediaType.create(expectedMT).test(actualMT), "Expected response media type " + expectedMT.toString() + " but received " @@ -374,7 +371,7 @@ public static WebServer startServer( * specified {@code MediaType} * @throws IOException in case of errors reading the response payload */ - public static String stringFromResponse(HttpURLConnection cnx, MediaType mediaType) throws IOException { + public static String stringFromResponse(HttpURLConnection cnx, HttpMediaType mediaType) throws IOException { try (final InputStreamReader isr = new InputStreamReader( cnx.getInputStream(), mediaType.charset().get())) { StringBuilder sb = new StringBuilder(); From 38862f49609fda32e373a45204f001bde816ab34 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:58:59 +0200 Subject: [PATCH 33/54] Application and Quickstarts refactored to new common. --- applications/pom.xml | 2 +- .../src/main/resources/logging.properties | 4 ++-- .../src/main/resources/logging.properties | 4 ++-- .../src/main/resources/logging.properties | 4 ++-- .../src/main/resources/logging.properties | 4 ++-- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/applications/pom.xml b/applications/pom.xml index 8cf0a293bde..387ecd4fb68 100644 --- a/applications/pom.xml +++ b/applications/pom.xml @@ -40,7 +40,7 @@ UTF-8 UTF-8 - 17 + 19 ${maven.compiler.source} ${maven.compiler.source} 3.8.1 diff --git a/examples/quickstarts/helidon-quickstart-mp/src/main/resources/logging.properties b/examples/quickstarts/helidon-quickstart-mp/src/main/resources/logging.properties index d4f05e084ad..749648fb33c 100644 --- a/examples/quickstarts/helidon-quickstart-mp/src/main/resources/logging.properties +++ b/examples/quickstarts/helidon-quickstart-mp/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/quickstarts/helidon-quickstart-se/src/main/resources/logging.properties b/examples/quickstarts/helidon-quickstart-se/src/main/resources/logging.properties index fce837e53e0..384ec7e992f 100644 --- a/examples/quickstarts/helidon-quickstart-se/src/main/resources/logging.properties +++ b/examples/quickstarts/helidon-quickstart-se/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/quickstarts/helidon-standalone-quickstart-mp/src/main/resources/logging.properties b/examples/quickstarts/helidon-standalone-quickstart-mp/src/main/resources/logging.properties index 496bbab6544..968d8b40fe9 100644 --- a/examples/quickstarts/helidon-standalone-quickstart-mp/src/main/resources/logging.properties +++ b/examples/quickstarts/helidon-standalone-quickstart-mp/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties ## Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # ## HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n diff --git a/examples/quickstarts/helidon-standalone-quickstart-se/src/main/resources/logging.properties b/examples/quickstarts/helidon-standalone-quickstart-se/src/main/resources/logging.properties index fce837e53e0..384ec7e992f 100644 --- a/examples/quickstarts/helidon-standalone-quickstart-se/src/main/resources/logging.properties +++ b/examples/quickstarts/helidon-standalone-quickstart-se/src/main/resources/logging.properties @@ -1,5 +1,5 @@ # -# Copyright (c) 2018, 2021 Oracle and/or its affiliates. +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -18,7 +18,7 @@ # For more information see $JAVA_HOME/jre/lib/logging.properties # Send messages to the console -handlers=io.helidon.common.HelidonConsoleHandler +handlers=io.helidon.logging.jul.HelidonConsoleHandler # HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n From 8832492f1f2d5d064961a3b98cb4712945499fbd Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 13:59:17 +0200 Subject: [PATCH 34/54] GraalVM support refactored to new common. --- .../graal/nativeimage/extension/HelidonReflectionFeature.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java index 24da8151a7a..3bb1a0abd25 100644 --- a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java +++ b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,9 +32,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; -import io.helidon.common.HelidonFeatures; import io.helidon.common.LogConfig; import io.helidon.common.Reflected; +import io.helidon.common.features.HelidonFeatures; import io.helidon.config.mp.MpConfigProviderResolver; import com.oracle.svm.core.jdk.Resources; From 9a62bf635f985934923c79e12a89b8f2efa75ca1 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 14:46:12 +0200 Subject: [PATCH 35/54] WebServer - fix test + related fixes --- .../io/helidon/webserver/RequestPredicate.java | 5 +++-- .../io/helidon/webserver/RequestContentTest.java | 2 +- .../io/helidon/webserver/RequestPredicateTest.java | 14 +++++++------- .../java/io/helidon/webserver/RequestTest.java | 2 ++ .../java/io/helidon/webserver/RoutingTest.java | 4 +++- 5 files changed, 16 insertions(+), 11 deletions(-) diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/RequestPredicate.java b/webserver/webserver/src/main/java/io/helidon/webserver/RequestPredicate.java index 5886c0ed8e4..d17f0ae9e3a 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/RequestPredicate.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/RequestPredicate.java @@ -16,6 +16,7 @@ package io.helidon.webserver; +import java.util.List; import java.util.Objects; import java.util.Optional; import java.util.function.Predicate; @@ -318,7 +319,7 @@ public RequestPredicate containsQueryParameter(final String name, Objects.requireNonNull(name, "query param name"); Objects.requireNonNull(predicate, "query param predicate"); return and((req) -> req.queryParams() - .all(name) + .all(name, List::of) .stream() .anyMatch(predicate)); } @@ -367,7 +368,7 @@ public RequestPredicate containsCookie(final String name, Objects.requireNonNull(predicate, "cookie predicate"); return and((req) -> req.headers() .cookies() - .all(name) + .all(name, List::of) .stream() .anyMatch(predicate)); } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/RequestContentTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/RequestContentTest.java index 99050968045..9be80b584a1 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/RequestContentTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/RequestContentTest.java @@ -240,7 +240,7 @@ public void failingReader() throws Exception { @Override public Single read(Publisher publisher, GenericType type, MessageBodyReaderContext context) { - throw new IllegalStateException("failed read"); + throw new IllegalStateException("failed-read"); } @Override diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/RequestPredicateTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/RequestPredicateTest.java index 5d424ae17ab..6d85fdc01a3 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/RequestPredicateTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/RequestPredicateTest.java @@ -550,8 +550,8 @@ public void hasContentType2() { final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .get("/contentType2", RequestPredicate.create() - .hasContentType(HttpMediaType.PLAINTEXT_UTF_8, - HttpMediaType.JSON_UTF_8) + .hasContentType(HttpMediaType.TEXT_PLAIN, + HttpMediaType.APPLICATION_JSON) .thenApply((req, resp) -> { checker.handlerInvoked("hasContentType"); }).otherwise((req, res) -> { @@ -561,7 +561,7 @@ public void hasContentType2() { assertThrows(NullPointerException.class, () -> { RequestPredicate.create() - .hasContentType(new HttpMediaType[0]); + .hasContentType((HttpMediaType[]) null); }); routing.route(mockRequest("/contentType2", Map.of(CONTENT_TYPE, @@ -598,8 +598,8 @@ public void multipleConditions() { final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .any("/multiple", RequestPredicate.create() - .accepts(HttpMediaType.PLAINTEXT_UTF_8) - .hasContentType(HttpMediaType.PLAINTEXT_UTF_8) + .accepts(HttpMediaType.TEXT_PLAIN) + .hasContentType(HttpMediaType.TEXT_PLAIN) .containsQueryParameter("my-param") .containsCookie("my-cookie") .isOfMethod(Http.Method.GET) @@ -653,7 +653,7 @@ public void or(){ final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .get("/or", RequestPredicate.create() - .hasContentType(HttpMediaType.PLAINTEXT_UTF_8) + .hasContentType(HttpMediaType.TEXT_PLAIN) .or((req) -> req.headers().contains(MY_HEADER)) .thenApply((req, resp) -> { checker.handlerInvoked("hasAnyCondition"); @@ -681,7 +681,7 @@ public void negate(){ final RoutingChecker checker = new RoutingChecker(); Routing routing = Routing.builder() .get("/negate", RequestPredicate.create() - .hasContentType(HttpMediaType.PLAINTEXT_UTF_8) + .hasContentType(HttpMediaType.TEXT_PLAIN) .containsCookie("my-cookie") .negate() .thenApply((req, resp) -> { diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/RequestTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/RequestTest.java index 5253b58f3c0..e7a54ba255d 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/RequestTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/RequestTest.java @@ -21,6 +21,7 @@ import io.helidon.common.reactive.Single; +import io.netty.handler.codec.http.DefaultHttpHeaders; import org.junit.jupiter.api.Test; import static org.hamcrest.CoreMatchers.nullValue; @@ -81,6 +82,7 @@ public void queryEncodingTest() throws Exception { BareRequest mock = mock(BareRequest.class); when(mock.uri()).thenReturn(new URI("http://localhost:123/one/two?a=b%26c=d&e=f&e=g&h=x%63%23e%3c#a%20frag%23ment")); when(mock.bodyPublisher()).thenReturn(Single.empty()); + when(mock.headers()).thenReturn(new NettyRequestHeaders(new DefaultHttpHeaders())); WebServer webServer = mock(WebServer.class); Request request = new RequestTestStub(mock, webServer); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/RoutingTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/RoutingTest.java index 5a554a2b5d6..be4fd3c5915 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/RoutingTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/RoutingTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import io.helidon.common.http.Http; import io.helidon.common.reactive.Single; +import io.netty.handler.codec.http.DefaultHttpHeaders; import org.junit.jupiter.api.Test; import org.mockito.Mockito; @@ -130,6 +131,7 @@ static BareRequest mockRequest(String path, Http.Method method) { WebServer webServerMock = mock(WebServer.class); when(webServerMock.context()).thenReturn(Context.create()); doReturn(webServerMock).when(bareRequestMock).webServer(); + doReturn(new NettyRequestHeaders(new DefaultHttpHeaders())).when(bareRequestMock).headers(); return bareRequestMock; } From 935bd02f500244b3f90141e9d4a3f37c7ff142e4 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 15:43:37 +0200 Subject: [PATCH 36/54] Tests and checkstyle fixes for SE Media --- .../common/MessageBodyWriterContext.java | 7 +- .../io/helidon/media/jsonb/JsonbProvider.java | 8 +- .../io/helidon/media/jsonb/JsonbSupport.java | 14 +- .../io/helidon/media/jsonp/JsonpSupport.java | 14 +- .../media/multipart/FileFormParams.java | 7 + .../media/multipart/BodyPartHeadersTest.java | 14 +- .../multipart/ContentDispositionTest.java | 262 ------------------ .../webserver/testsupport/TestClient.java | 6 +- .../webserver/testsupport/TestRequest.java | 12 +- ...eadersMap.java => TestRequestHeaders.java} | 77 ++--- .../webserver/testsupport/TestResponse.java | 3 +- 11 files changed, 80 insertions(+), 344 deletions(-) delete mode 100644 media/multipart/src/test/java/io/helidon/media/multipart/ContentDispositionTest.java rename webserver/test-support/src/main/java/io/helidon/webserver/testsupport/{RequestHeadersMap.java => TestRequestHeaders.java} (58%) diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java index 10e1600faee..65a0465ad3d 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java @@ -430,7 +430,12 @@ public HttpMediaType findAccepted(Predicate predicate, HttpMediaT if (mt.isWildcardType() || mt.isWildcardSubtype()) { return defaultType; } - return acceptedType; + if (acceptedType.test(defaultType)) { + // if application/json; q=0.4 and default is application/json; charset="UTF-8" I want to use default + return defaultType; + } + // return a type without quality factor and other parameters + return HttpMediaType.create(acceptedType.mediaType()); } } } diff --git a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbProvider.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbProvider.java index 2659fe3a194..5632c23b589 100644 --- a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbProvider.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -31,6 +31,12 @@ public class JsonbProvider implements MediaSupportProvider { private static final String JSON_B = "json-b"; + /** + * Constructor required for {@link java.util.ServiceLoader}. + */ + public JsonbProvider() { + } + @Override public MediaSupport create(Config config) { JsonbConfig jsonbConfig = new JsonbConfig(); diff --git a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbSupport.java b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbSupport.java index bff72df4cf1..5e176033587 100644 --- a/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbSupport.java +++ b/media/jsonb/src/main/java/io/helidon/media/jsonb/JsonbSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -140,7 +140,7 @@ public static MessageBodyStreamWriter streamWriter(Jsonb jsonb) { /** * Return a default JSON-B entity event stream writer. - * This writer is for {@link io.helidon.common.http.MediaType#TEXT_EVENT_STREAM} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#TEXT_EVENT_STREAM} content type. * * @return new JSON-B body stream writer instance */ @@ -150,7 +150,7 @@ public static MessageBodyStreamWriter eventStreamWriter() { /** * Create a new JSON-B entity stream writer based on {@link Jsonb} instance. - * This writer is for {@link io.helidon.common.http.MediaType#TEXT_EVENT_STREAM} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#TEXT_EVENT_STREAM} content type. * * @param jsonb jsonb instance * @return new JSON-B body stream writer instance @@ -162,7 +162,7 @@ public static MessageBodyStreamWriter eventStreamWriter(Jsonb jsonb) { /** * Return a default JSON-B entity event stream writer. - * This writer is for {@link io.helidon.common.http.MediaType#APPLICATION_X_NDJSON} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_NDJSON} content type. * * @return new JSON-B body stream writer instance */ @@ -172,7 +172,7 @@ public static MessageBodyStreamWriter ndJsonStreamWriter() { /** * Create a new JSON-B entity stream writer based on {@link Jsonb} instance. - * This writer is for {@link io.helidon.common.http.MediaType#APPLICATION_X_NDJSON} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_NDJSON} content type. * * @param jsonb jsonb instance * @return new JSON-B body stream writer instance @@ -210,7 +210,7 @@ public MessageBodyStreamWriter streamWriterInstance() { } /** - * Return JSON-B stream writer instance for {@link io.helidon.common.http.MediaType#TEXT_EVENT_STREAM} content type. + * Return JSON-B stream writer instance for {@link io.helidon.common.media.type.MediaTypes#TEXT_EVENT_STREAM} content type. * * @return JSON-B event stream writer instance */ @@ -219,7 +219,7 @@ public MessageBodyStreamWriter eventStreamWriterInstance() { } /** - * Return JSON-B stream writer instance for {@link io.helidon.common.http.MediaType#APPLICATION_X_NDJSON} content type. + * Return JSON-B stream writer instance for {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_NDJSON} content type. * * @return JSON-B event stream writer instance */ diff --git a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpSupport.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpSupport.java index 88c8bb12a9a..dee8143b851 100644 --- a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpSupport.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpSupport.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -142,7 +142,7 @@ public static MessageBodyStreamWriter streamWriter(JsonWriterFact /** * Return a default JSON-P entity event stream writer. - * This writer is for {@link io.helidon.common.http.MediaType#TEXT_EVENT_STREAM} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#TEXT_EVENT_STREAM} content type. * * @return new JSON-P body stream writer instance */ @@ -152,7 +152,7 @@ public static MessageBodyStreamWriter eventStreamWriter() { /** * Create a new JSON-P entity stream writer based on {@link JsonWriterFactory} instance. - * This writer is for {@link io.helidon.common.http.MediaType#TEXT_EVENT_STREAM} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#TEXT_EVENT_STREAM} content type. * * @param writerFactory json writer factory * @return new JSON-P body stream writer instance @@ -163,7 +163,7 @@ public static MessageBodyStreamWriter eventStreamWriter(JsonWrite /** * Return a default JSON-P entity event stream writer. - * This writer is for {@link io.helidon.common.http.MediaType#APPLICATION_X_NDJSON} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_NDJSON} content type. * * @return new JSON-P body stream writer instance */ @@ -173,7 +173,7 @@ public static MessageBodyStreamWriter ndJsonStreamWriter() { /** * Create a new JSON-P entity stream writer based on {@link JsonWriterFactory} instance. - * This writer is for {@link io.helidon.common.http.MediaType#APPLICATION_X_NDJSON} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_NDJSON} content type. * * @param writerFactory json writer factory * @return new JSON-P body stream writer instance @@ -226,7 +226,7 @@ public MessageBodyStreamWriter streamWriterInstance() { * \n * * - * This writer is for {@link io.helidon.common.http.MediaType#TEXT_EVENT_STREAM} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#TEXT_EVENT_STREAM} content type. * * @return JSON processing stream writer. */ @@ -245,7 +245,7 @@ public MessageBodyStreamWriter eventStreamWriterInstance() { * {"json2":"data2"} * * - * This writer is for {@link io.helidon.common.http.MediaType#APPLICATION_X_NDJSON} content type. + * This writer is for {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_NDJSON} content type. * * @return JSON processing stream writer. */ diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/FileFormParams.java b/media/multipart/src/main/java/io/helidon/media/multipart/FileFormParams.java index b69b6e33ad7..fd683f943a1 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/FileFormParams.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/FileFormParams.java @@ -46,6 +46,13 @@ public FileFormParams build() { return new FileFormParamsImpl(builder.build().bodyParts()); } + /** + * Add a parameter. + * + * @param name parameter name + * @param values parameter value + * @return updated builder + */ public Builder add(String name, String... values) { for (String value : values) { builder.bodyPart(name, value); diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartHeadersTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartHeadersTest.java index 0613f70ea42..8eb82ee81b8 100644 --- a/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartHeadersTest.java +++ b/media/multipart/src/test/java/io/helidon/media/multipart/BodyPartHeadersTest.java @@ -53,8 +53,8 @@ public void testContentType() { ReadableBodyPartHeaders headers = ReadableBodyPartHeaders.builder() .header(CONTENT_TYPE, "application/json") .build(); - assertThat(headers.contentType(), is(notNullValue())); - assertThat(headers.contentType(), + assertThat(headers.partContentType(), is(notNullValue())); + assertThat(headers.partContentType(), is(equalTo(HttpMediaType.APPLICATION_JSON))); } @@ -64,7 +64,7 @@ public void testBuilderWithContentType() { .contentType(HttpMediaType.APPLICATION_JSON) .build(); assertThat(headers.contentType(), is(notNullValue())); - assertThat(headers.contentType(), + assertThat(headers.partContentType(), is(equalTo(HttpMediaType.APPLICATION_JSON))); } @@ -72,8 +72,8 @@ public void testBuilderWithContentType() { public void testDefaultContentType() { ReadableBodyPartHeaders headers = ReadableBodyPartHeaders.builder() .build(); - assertThat(headers.contentType(), is(notNullValue())); - assertThat(headers.contentType(), is(equalTo(HttpMediaType.TEXT_PLAIN))); + assertThat(headers.partContentType(), is(notNullValue())); + assertThat(headers.partContentType(), is(equalTo(HttpMediaType.TEXT_PLAIN))); } @Test @@ -81,8 +81,8 @@ public void testDefaultContentTypeForFile() { ReadableBodyPartHeaders headers = ReadableBodyPartHeaders.builder() .header(CONTENT_DISPOSITION, "form-data; filename=foo") .build(); - assertThat(headers.contentType(), is(notNullValue())); - assertThat(headers.contentType(), + assertThat(headers.partContentType(), is(notNullValue())); + assertThat(headers.partContentType(), is(equalTo(HttpMediaType.APPLICATION_OCTET_STREAM))); } diff --git a/media/multipart/src/test/java/io/helidon/media/multipart/ContentDispositionTest.java b/media/multipart/src/test/java/io/helidon/media/multipart/ContentDispositionTest.java deleted file mode 100644 index 60e8bfa6c6b..00000000000 --- a/media/multipart/src/test/java/io/helidon/media/multipart/ContentDispositionTest.java +++ /dev/null @@ -1,262 +0,0 @@ -/* - * Copyright (c) 2020, 2022 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package io.helidon.media.multipart; - -import java.time.ZoneId; -import java.time.ZonedDateTime; - -import io.helidon.common.http.ContentDisposition; -import io.helidon.common.http.Http; - -import org.junit.jupiter.api.Test; - -import static org.hamcrest.CoreMatchers.equalTo; -import static org.hamcrest.CoreMatchers.is; -import static org.hamcrest.CoreMatchers.notNullValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.junit.jupiter.api.Assertions.assertThrows; - -/** - * Tests {@link io.helidon.common.http.ContentDisposition}. - */ -public class ContentDispositionTest { - - private static final ZonedDateTime ZDT = ZonedDateTime.of(2008, 6, 3, 11, 5, 30, 0, ZoneId.of("Z")); - - @Test - public void testNoType() { - assertThrows(IllegalArgumentException.class, () -> ContentDisposition.parse("foo=\"bar\"; bar=\"foo\"")); - } - - @Test - public void testWhiteSpaces() { - ContentDisposition cd = ContentDisposition.parse(" inline;foo=bar; bar=foo ; abc=xyz "); - assertThat(cd.type(), is(equalTo("inline"))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(3))); - assertThat(cd.parameters().get("foo"), is(equalTo("bar"))); - assertThat(cd.parameters().get("bar"), is(equalTo("foo"))); - assertThat(cd.parameters().get("abc"), is(equalTo("xyz"))); - } - - @Test - public void testQuotedString() { - ContentDisposition cd = ContentDisposition.parse("inline; foo=\" b a r\""); - assertThat(cd.type(), is(equalTo("inline"))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(1))); - assertThat(cd.parameters().get("foo"), is(equalTo(" b a r"))); - } - - @Test - public void testName() { - ContentDisposition cd = ContentDisposition.parse("form-data; name=user"); - assertThat(cd.type(), is(equalTo("form-data"))); - assertThat(cd.contentName().isPresent(), is(equalTo(true))); - assertThat(cd.contentName().get(), is(equalTo("user"))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(1))); - } - - @Test - public void testFilename() { - ContentDisposition cd = ContentDisposition.parse("attachment; filename=index.html"); - assertThat(cd.type(), is(equalTo("attachment"))); - assertThat(cd.filename().isPresent(), is(equalTo(true))); - assertThat(cd.filename().get(), is(equalTo("index.html"))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(1))); - } - - @Test - public void testCreationDate() { - ContentDisposition cd = ContentDisposition.parse("attachment; creation-date=\"Tue, 3 Jun 2008 11:05:30 GMT\""); - assertThat(cd.type(), is(equalTo("attachment"))); - assertThat(cd.creationDate().isPresent(), is(equalTo(true))); - assertThat(cd.creationDate().get(), is(equalTo(ZDT))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(1))); - } - - @Test - public void testModificationDate() { - ContentDisposition cd = ContentDisposition.parse("attachment; modification-date=\"Tue, 3 Jun 2008 11:05:30 GMT\""); - assertThat(cd.type(), is(equalTo("attachment"))); - assertThat(cd.modificationDate().isPresent(), is(equalTo(true))); - assertThat(cd.modificationDate().get(), is(equalTo(ZDT))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(1))); - } - - @Test - public void testReadDate() { - ContentDisposition cd = ContentDisposition.parse("attachment; read-date=\"Tue, 3 Jun 2008 11:05:30 GMT\""); - assertThat(cd.type(), is(equalTo("attachment"))); - assertThat(cd.readDate().isPresent(), is(equalTo(true))); - assertThat(cd.readDate().get(), is(equalTo(ZDT))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(1))); - } - - @Test - public void testSize() { - ContentDisposition cd = ContentDisposition.parse("inline; size=128"); - assertThat(cd.type(), is(equalTo("inline"))); - assertThat(cd.size().isPresent(), is(equalTo(true))); - assertThat(cd.size().getAsLong(), is(equalTo(128L))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(1))); - } - - @Test - public void testPercentEncodedFilename() { - ContentDisposition cd = ContentDisposition.parse("inline; filename=the%20great%20file.html"); - assertThat(cd.type(), is(equalTo("inline"))); - assertThat(cd.filename().isPresent(), is(equalTo(true))); - assertThat(cd.filename().get(), is(equalTo("the great file.html"))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(1))); - } - - @Test - public void testFilenameWithBackslash() { - ContentDisposition cd = ContentDisposition.parse("inline; filename=\"C:\\index.html\""); - assertThat(cd.type(), is(equalTo("inline"))); - assertThat(cd.filename().isPresent(), is(equalTo(true))); - assertThat(cd.filename().get(), is(equalTo("C:\\index.html"))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(1))); - } - - @Test - public void testParamsWithQuotedPair() { - ContentDisposition cd = ContentDisposition.parse("inline; foo=\"\\\\\"; bar=\"\\\"\""); - assertThat(cd.type(), is(equalTo("inline"))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(2))); - assertThat(cd.parameters().get("foo"), is(equalTo("\\"))); - assertThat(cd.parameters().get("bar"), is(equalTo("\""))); - } - - @Test - public void testCaseInsensitiveType() { - ContentDisposition cd = ContentDisposition.parse("aTTachMENT; name=bar"); - assertThat(cd.type(), is(equalTo("attachment"))); - assertThat(cd.contentName().isPresent(), is(equalTo(true))); - assertThat(cd.contentName().get(), is(equalTo("bar"))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(1))); - } - - @Test - public void testBuilderWithFilenameEncoded() { - ContentDisposition cd = ContentDisposition.builder() - .type("inline") - .filename("filename with spaces.txt") - .build(); - assertThat(cd.type(), is(equalTo("inline"))); - assertThat(cd.filename().isPresent(), is(equalTo(true))); - assertThat(cd.filename().get(), is(equalTo("filename with spaces.txt"))); - assertThat(cd.size().isPresent(), is(equalTo(false))); - } - - @Test - public void testBuilderWithDates() { - ContentDisposition cd = ContentDisposition.builder() - .type("inline") - .modificationDate(ZDT) - .creationDate(ZDT) - .readDate(ZDT) - .build(); - assertThat(cd.type(), is(equalTo("inline"))); - assertThat(cd.modificationDate().isPresent(), is(equalTo(true))); - assertThat(cd.modificationDate().get(), is(equalTo(ZDT))); - assertThat(cd.creationDate().isPresent(), is(equalTo(true))); - assertThat(cd.creationDate().get(), is(equalTo(ZDT))); - assertThat(cd.readDate().isPresent(), is(equalTo(true))); - assertThat(cd.readDate().get(), is(equalTo(ZDT))); - } - - @Test - public void testBuilderWithSize() { - ContentDisposition cd = ContentDisposition.builder() - .type("inline") - .size(128) - .build(); - assertThat(cd.type(), is(equalTo("inline"))); - assertThat(cd.size().isPresent(), is(equalTo(true))); - assertThat(cd.size().getAsLong(), is(equalTo(128L))); - } - - @Test - public void testBuilderWithCustomParam() { - ContentDisposition cd = ContentDisposition.builder() - .type("inline") - .parameter("foo", "bar") - .build(); - assertThat(cd.type(), is(equalTo("inline"))); - assertThat(cd.parameters().get("foo"), is(equalTo("bar"))); - } - - @Test - public void testContentDispositionDefault(){ - ContentDisposition cd = ContentDisposition.builder().build(); - assertThat(cd.type(), is(equalTo("form-data"))); - assertThat(cd.parameters().size(), is(0)); - } - - @Test - public void testQuotes(){ - String template = "form-data;" - + "filename=\"file.txt\";" - + "size=300;" - + "name=\"someName\""; - ContentDisposition cd = ContentDisposition.builder() - .name("someName") - .filename("file.txt") - .size(300) - .build(); - assertThat(cd.toString(), is(equalTo(template))); - } - - @Test - public void testDateQuotes() { - ZonedDateTime zonedDateTime = ZonedDateTime.now(); - String date = zonedDateTime.format(Http.DateTime.RFC_1123_DATE_TIME); - String template = "form-data;" - + "creation-date=\"" + date + "\";" - + "modification-date=\"" + date + "\";" - + "read-date=\"" + date + "\""; - ContentDisposition cd = ContentDisposition.builder() - .creationDate(zonedDateTime) - .readDate(zonedDateTime) - .modificationDate(zonedDateTime) - .build(); - assertThat(cd.toString(), is(equalTo(template))); - } - - @Test - public void testNonAsciiFilename() { - ContentDisposition cd = ContentDisposition.parse("form-data; name=\"file[]\"; filename=\"\u60A8\u597D.txt\""); - assertThat(cd.type(), is(equalTo("form-data"))); - assertThat(cd.contentName().isPresent(), is(equalTo(true))); - assertThat(cd.contentName().get(), is(equalTo("file[]"))); - assertThat(cd.filename().isPresent(), is(equalTo(true))); - assertThat(cd.filename().get(), is(equalTo("\u60A8\u597D.txt"))); - assertThat(cd.parameters(), is(notNullValue())); - assertThat(cd.parameters().size(), is(equalTo(2))); - } -} diff --git a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestClient.java b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestClient.java index b13d305f40b..6323da26c80 100644 --- a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestClient.java +++ b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestClient.java @@ -143,7 +143,7 @@ public TestRequest path(String path) { TestResponse call(Http.Method method, Http.Version version, URI path, - Map> headers, + TestRequestHeaders headers, Flow.Publisher publisher) throws InterruptedException, TimeoutException { TestWebServer webServer = new TestWebServer(mediaContext); TestBareRequest req = new TestBareRequest(method, version, path, headers, publisher, webServer); @@ -172,7 +172,7 @@ private static class TestBareRequest implements BareRequest { TestBareRequest(Http.Method method, Http.Version version, URI path, - Map> headers, + TestRequestHeaders headers, Flow.Publisher publisher, TestWebServer webServer) { @@ -180,7 +180,7 @@ private static class TestBareRequest implements BareRequest { this.method = Objects.requireNonNull(method, "Parameter 'method' is null!"); this.version = Objects.requireNonNull(version, "Parameter 'version' is null!"); this.path = Objects.requireNonNull(path, "Parameter 'path' is null!"); - this.headers = new RequestHeadersMap(headers); + this.headers = headers; if (publisher == null) { this.publisher = Single.empty(); } else { diff --git a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestRequest.java b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestRequest.java index 6517e50b897..97d112be909 100644 --- a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestRequest.java +++ b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestRequest.java @@ -19,10 +19,6 @@ import java.net.URI; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.List; -import java.util.Map; import java.util.Objects; import java.util.concurrent.TimeoutException; @@ -36,7 +32,7 @@ public class TestRequest { private final TestClient testClient; private final String path; private final StringBuilder query = new StringBuilder(); - private final Map> headers = new HashMap<>(); + private final TestRequestHeaders headers = new TestRequestHeaders(); private volatile Http.Version version = Http.Version.V1_1; /** @@ -95,7 +91,7 @@ public TestRequest queryParameter(String name, String value) { public TestRequest header(Http.HeaderName name, String value) { Objects.requireNonNull(name, "Parameter 'name' is null!"); Objects.requireNonNull(name, "Parameter 'value' is null!"); - headers.computeIfAbsent(name.defaultCase(), k -> new ArrayList<>()).add(value); + headers.setIfAbsent(Http.HeaderValue.create(name, value)); return this; } @@ -290,8 +286,8 @@ public TestResponse trace() throws InterruptedException, TimeoutException { */ public TestResponse call(Http.Method method, MediaPublisher mediaPublisher) throws InterruptedException, TimeoutException { - if (mediaPublisher != null && !headers.containsKey(Http.Header.CONTENT_TYPE) && mediaPublisher.mediaType() != null) { - header(Http.Header.CONTENT_TYPE, mediaPublisher.mediaType().toString()); + if (mediaPublisher != null && !headers.contains(Http.Header.CONTENT_TYPE) && mediaPublisher.mediaType() != null) { + header(Http.Header.CONTENT_TYPE, mediaPublisher.mediaType().text()); } return testClient.call(method, version, uri(), headers, mediaPublisher); } diff --git a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/RequestHeadersMap.java b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestRequestHeaders.java similarity index 58% rename from webserver/test-support/src/main/java/io/helidon/webserver/testsupport/RequestHeadersMap.java rename to webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestRequestHeaders.java index 25d7017d276..85a41b5d143 100644 --- a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/RequestHeadersMap.java +++ b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestRequestHeaders.java @@ -18,76 +18,75 @@ import java.util.Iterator; import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.OptionalLong; -import java.util.Spliterator; import java.util.function.Consumer; import java.util.function.Supplier; import io.helidon.common.http.HeadersWritable; import io.helidon.common.http.Http; import io.helidon.common.http.HttpMediaType; -import io.helidon.common.media.type.MediaType; import io.helidon.webserver.RequestHeaders; -class RequestHeadersMap implements RequestHeaders { +class TestRequestHeaders implements RequestHeaders, HeadersWritable { private final HeadersWritable delegate; - RequestHeadersMap(Map> headers) { + TestRequestHeaders() { this.delegate = HeadersWritable.create(); - headers.forEach((key, value) -> delegate.set(Http.Header.create(key), value)); } @Override - @Deprecated(forRemoval = true) - public List all(String headerName) { - return delegate.all(headerName); + public TestRequestHeaders setIfAbsent(Http.HeaderValue header) { + delegate.setIfAbsent(header); + return this; } @Override - public List all(Http.HeaderName name, Supplier> defaultSupplier) { - return delegate.all(name, defaultSupplier); + public TestRequestHeaders add(Http.HeaderValue header) { + delegate.add(header); + return this; } @Override - public boolean contains(Http.HeaderName name) { - return delegate.contains(name); + public TestRequestHeaders remove(Http.HeaderName name) { + delegate.remove(name); + return this; } @Override - public boolean contains(Http.HeaderValue value) { - return delegate.contains(value); + public TestRequestHeaders remove(Http.HeaderName name, Consumer removedConsumer) { + delegate.remove(name, removedConsumer); + return this; } @Override - public Http.HeaderValue get(Http.HeaderName name) { - return delegate.get(name); + public TestRequestHeaders set(Http.HeaderValue header) { + delegate.set(header); + return this; } @Override - public Optional value(Http.HeaderName headerName) { - return delegate.value(headerName); + public TestRequestHeaders clear() { + delegate.clear(); + return this; } @Override - public Optional first(Http.HeaderName headerName) { - return delegate.first(headerName); + public List all(Http.HeaderName name, Supplier> defaultSupplier) { + return delegate.all(name, defaultSupplier); } @Override - public List values(Http.HeaderName headerName) { - return delegate.values(headerName); + public boolean contains(Http.HeaderName name) { + return delegate.contains(name); } @Override - public OptionalLong contentLength() { - return delegate.contentLength(); + public boolean contains(Http.HeaderValue value) { + return delegate.contains(value); } @Override - public Optional contentType() { - return delegate.contentType(); + public Http.HeaderValue get(Http.HeaderName name) { + return delegate.get(name); } @Override @@ -100,29 +99,13 @@ public List acceptedTypes() { return delegate.acceptedTypes(); } - @Override - public boolean isAccepted(MediaType mediaType) { - return delegate.isAccepted(mediaType); - } - - @Override - @Deprecated(forRemoval = true) - public Map> toMap() { - return delegate.toMap(); - } - @Override public Iterator iterator() { return delegate.iterator(); } @Override - public void forEach(Consumer action) { - delegate.forEach(action); - } - - @Override - public Spliterator spliterator() { - return delegate.spliterator(); + public String toString() { + return delegate.toString(); } } diff --git a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestResponse.java b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestResponse.java index 5cc6637f916..1f9b9a75160 100644 --- a/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestResponse.java +++ b/webserver/test-support/src/main/java/io/helidon/webserver/testsupport/TestResponse.java @@ -34,7 +34,7 @@ public class TestResponse { private final Http.Status status; - private final HeadersWritable headers; + private final HeadersWritable headers; // todo Needs much better solution. private final TestClient.TestBareResponse bareResponse; @@ -50,6 +50,7 @@ public class TestResponse { TestClient.TestBareResponse bareResponse) { this.status = status; this.headers = HeadersWritable.create(); + headers.forEach((key, value) -> this.headers.set(Http.Header.create(key), value)); this.bareResponse = bareResponse; } From 91085819457d84775b5375ce302e1b3cac2c482b Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 16:03:03 +0200 Subject: [PATCH 37/54] Static content fixes. --- .../staticcontent/StaticContentHandler.java | 21 ++++++++++++------- .../StaticContentHandlerTest.java | 20 +++++++++++------- 2 files changed, 26 insertions(+), 15 deletions(-) diff --git a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentHandler.java b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentHandler.java index a392ba7a69f..ffa163eba9c 100644 --- a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentHandler.java +++ b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentHandler.java @@ -34,6 +34,9 @@ import io.helidon.webserver.ServerRequest; import io.helidon.webserver.ServerResponse; +import static io.helidon.common.http.Http.Header.IF_MATCH; +import static io.helidon.common.http.Http.Header.IF_NONE_MATCH; + /** * Base implementation of static content support. */ @@ -146,17 +149,21 @@ static void processEtag(String etag, RequestHeaders requestHeaders, ResponseHead etag = unquoteETag(etag); // Put ETag into the response responseHeaders.set(Http.Header.ETAG, '"' + etag + '"'); + // Process If-None-Match header - List ifNoneMatches = requestHeaders.all(Http.Header.IF_NONE_MATCH, List::of); - for (String ifNoneMatch : ifNoneMatches) { - ifNoneMatch = unquoteETag(ifNoneMatch); - if ("*".equals(ifNoneMatch) || ifNoneMatch.equals(etag)) { - throw new HttpException("Accepted by If-None-Match header!", Http.Status.NOT_MODIFIED_304); + if (requestHeaders.contains(IF_NONE_MATCH)) { + List ifNoneMatches = requestHeaders.values(IF_NONE_MATCH); + for (String ifNoneMatch : ifNoneMatches) { + ifNoneMatch = unquoteETag(ifNoneMatch); + if ("*".equals(ifNoneMatch) || ifNoneMatch.equals(etag)) { + throw new HttpException("Accepted by If-None-Match header!", Http.Status.NOT_MODIFIED_304); + } } } // Process If-Match header - List ifMatches = requestHeaders.all(Http.Header.IF_MATCH, List::of); - if (!ifMatches.isEmpty()) { + if (requestHeaders.contains(IF_MATCH)) { + List ifMatches = requestHeaders.values(Http.Header.IF_MATCH); + boolean ifMatchChecked = false; for (String ifMatch : ifMatches) { ifMatch = unquoteETag(ifMatch); diff --git a/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/StaticContentHandlerTest.java b/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/StaticContentHandlerTest.java index f0a7d03d352..ce5d0c3e8b2 100644 --- a/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/StaticContentHandlerTest.java +++ b/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/StaticContentHandlerTest.java @@ -36,6 +36,8 @@ import org.junit.jupiter.api.Test; import org.mockito.Mockito; +import static io.helidon.common.http.Http.Header.IF_MATCH; +import static io.helidon.common.http.Http.Header.IF_NONE_MATCH; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.mockito.Mockito.mock; @@ -63,8 +65,8 @@ private static void assertHttpException(Runnable runnable, Http.Status status) { @Test void etag_InNonMatch_NotAccept() { RequestHeaders req = mock(RequestHeaders.class); - when(req.values(Http.Header.IF_NONE_MATCH)).thenReturn(List.of("\"ccc\"", "\"ddd\"")); - when(req.values(Http.Header.IF_MATCH)).thenReturn(Collections.emptyList()); + when(req.values(IF_NONE_MATCH)).thenReturn(List.of("\"ccc\"", "\"ddd\"")); + when(req.values(IF_MATCH)).thenReturn(Collections.emptyList()); ResponseHeaders res = mock(ResponseHeaders.class); StaticContentHandler.processEtag("aaa", req, res); verify(res).set(Http.Header.ETAG, "\"aaa\""); @@ -73,8 +75,9 @@ void etag_InNonMatch_NotAccept() { @Test void etag_InNonMatch_Accept() { RequestHeaders req = mock(RequestHeaders.class); - when(req.values(Http.Header.IF_NONE_MATCH)).thenReturn(List.of("\"ccc\"", "W/\"aaa\"")); - when(req.values(Http.Header.IF_MATCH)).thenReturn(Collections.emptyList()); + when(req.contains(IF_NONE_MATCH)).thenReturn(true); + when(req.values(IF_NONE_MATCH)).thenReturn(List.of("\"ccc\"", "W/\"aaa\"")); + when(req.contains(IF_MATCH)).thenReturn(false); ResponseHeaders res = mock(ResponseHeaders.class); assertHttpException(() -> StaticContentHandler.processEtag("aaa", req, res), Http.Status.NOT_MODIFIED_304); verify(res).set(Http.Header.ETAG, "\"aaa\""); @@ -83,8 +86,9 @@ void etag_InNonMatch_Accept() { @Test void etag_InMatch_NotAccept() { RequestHeaders req = mock(RequestHeaders.class); - when(req.values(Http.Header.IF_MATCH)).thenReturn(List.of("\"ccc\"", "\"ddd\"")); - when(req.values(Http.Header.IF_NONE_MATCH)).thenReturn(Collections.emptyList()); + when(req.contains(IF_MATCH)).thenReturn(true); + when(req.values(IF_MATCH)).thenReturn(List.of("\"ccc\"", "\"ddd\"")); + when(req.contains(IF_NONE_MATCH)).thenReturn(false); ResponseHeaders res = mock(ResponseHeaders.class); assertHttpException(() -> StaticContentHandler.processEtag("aaa", req, res), Http.Status.PRECONDITION_FAILED_412); verify(res).set(Http.Header.ETAG, "\"aaa\""); @@ -93,8 +97,8 @@ void etag_InMatch_NotAccept() { @Test void etag_InMatch_Accept() { RequestHeaders req = mock(RequestHeaders.class); - when(req.values(Http.Header.IF_MATCH)).thenReturn(List.of("\"ccc\"", "\"aaa\"")); - when(req.values(Http.Header.IF_NONE_MATCH)).thenReturn(Collections.emptyList()); + when(req.values(IF_MATCH)).thenReturn(List.of("\"ccc\"", "\"aaa\"")); + when(req.values(IF_NONE_MATCH)).thenReturn(Collections.emptyList()); ResponseHeaders res = mock(ResponseHeaders.class); StaticContentHandler.processEtag("aaa", req, res); verify(res).set(Http.Header.ETAG, "\"aaa\""); From fe542d3d096a65ed4a6a1ee621d457529798cd79 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 16:03:11 +0200 Subject: [PATCH 38/54] Javadoc fixes. --- security/security/pom.xml | 9 +++++++++ webclient/jaxrs/pom.xml | 9 +++++++++ webserver/jersey/pom.xml | 9 +++++++++ 3 files changed, 27 insertions(+) diff --git a/security/security/pom.xml b/security/security/pom.xml index 88f43888219..198d68c74ea 100644 --- a/security/security/pom.xml +++ b/security/security/pom.xml @@ -103,6 +103,15 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + + --enable-preview + + + diff --git a/webclient/jaxrs/pom.xml b/webclient/jaxrs/pom.xml index 3730cf02889..cd29e116e70 100644 --- a/webclient/jaxrs/pom.xml +++ b/webclient/jaxrs/pom.xml @@ -62,6 +62,15 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + + --enable-preview + + + diff --git a/webserver/jersey/pom.xml b/webserver/jersey/pom.xml index 3a20acc1d82..fdf07a05e20 100644 --- a/webserver/jersey/pom.xml +++ b/webserver/jersey/pom.xml @@ -119,6 +119,15 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + + --enable-preview + + + From 12b72b8ca929172b5b84c1d59be0f89bd4b38b0a Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Wed, 4 May 2022 16:09:23 +0200 Subject: [PATCH 39/54] Security integration fixes. --- .../integration/webserver/SecurityHandler.java | 18 +++++++++++------- .../webserver/WebSecurityQueryParamTest.java | 2 ++ 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java b/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java index 8935a3c248a..882a3771eb1 100644 --- a/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java +++ b/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java @@ -36,6 +36,7 @@ import io.helidon.common.context.Context; import io.helidon.common.context.Contexts; import io.helidon.common.http.Http; +import io.helidon.common.uri.UriQuery; import io.helidon.config.Config; import io.helidon.security.AuditEvent; import io.helidon.security.AuthenticationResponse; @@ -1108,13 +1109,16 @@ public static QueryParamHandler create(String queryParamName, TokenHandler heade } void extract(ServerRequest req, Map> headers) { - List values = req.queryParams().all(queryParamName); - - values.forEach(token -> { - String tokenValue = headerHandler.extractToken(token); - headerHandler.addHeader(headers, tokenValue); - } - ); + UriQuery uriQuery = req.queryParams(); + if (uriQuery.contains(queryParamName)) { + List values = uriQuery.all(queryParamName); + + values.forEach(token -> { + String tokenValue = headerHandler.extractToken(token); + headerHandler.addHeader(headers, tokenValue); + } + ); + } } } } diff --git a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityQueryParamTest.java b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityQueryParamTest.java index c0e8f7ca8d8..f2c43cfe0aa 100644 --- a/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityQueryParamTest.java +++ b/security/integration/webserver/src/test/java/io/helidon/security/integration/webserver/WebSecurityQueryParamTest.java @@ -56,7 +56,9 @@ public void testQueryParams() { ServerRequest req = Mockito.mock(ServerRequest.class); UriQuery params = Mockito.mock(UriQuery.class); + when(params.contains("jwt")).thenReturn(true); when(params.all("jwt")).thenReturn(List.of("bearer jwt_content")); + when(params.contains("name")).thenReturn(true); when(params.all("name")).thenReturn(List.of("name_content")); when(req.queryParams()).thenReturn(params); From 49e561fdf2fc5e0ee60ecda80c1f7c29628a1d30 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 5 May 2022 10:11:36 +0200 Subject: [PATCH 40/54] Fixed test issues --- applications/pom.xml | 1 + examples/integrations/neo4j/neo4j-se/pom.xml | 1 + .../examples/media/multipart/Main.java | 2 ++ .../src/main/resources/logging.properties | 34 +++++++++++++++++++ examples/microprofile/static-content/pom.xml | 4 +++ .../examples/quickstart/se/MainTest.java | 15 ++++++-- .../helidon-standalone-quickstart-mp/pom.xml | 3 +- .../helidon-standalone-quickstart-se/pom.xml | 3 +- .../examples/tutorial/CommentService.java | 3 +- .../src/main/resources/logging.properties | 34 +++++++++++++++++++ .../examples/tutorial/CommentServiceTest.java | 13 +++++-- .../media/multipart/MultiPartBodyWriter.java | 2 +- messaging/connectors/aq/pom.xml | 9 +++++ messaging/connectors/jms/pom.xml | 9 +++++ messaging/messaging/pom.xml | 9 +++++ .../jwt/auth/JwtAuthProvider.java | 4 +-- .../OpentracingJavaMockTracerProvider.java | 8 ++--- tests/apps/bookstore/bookstore-mp/pom.xml | 7 ++++ tests/apps/bookstore/bookstore-se/pom.xml | 7 ++++ .../io/helidon/tests/bookstore/MainTest.java | 3 +- tests/integration/health/mp-disabled/pom.xml | 12 +++++++ tests/integration/mp-bean-validation/pom.xml | 7 ++++ tests/integration/mp-grpc/pom.xml | 1 + tests/integration/mp-security-client/pom.xml | 12 +++++++ tests/integration/mp-ws-services/pom.xml | 12 +++++++ .../tests/integration/webclient/FormTest.java | 2 +- .../integration/webclient/TestParent.java | 16 ++------- .../integration/webclient/UriPartTest.java | 19 +++++------ tests/integration/zipkin-mp-2.2/pom.xml | 7 ++++ .../WebClientRequestBuilderImpl.java | 2 +- webserver/webserver/pom.xml | 1 - 31 files changed, 216 insertions(+), 46 deletions(-) create mode 100644 examples/media/multipart/src/main/resources/logging.properties create mode 100644 examples/webserver/tutorial/src/main/resources/logging.properties diff --git a/applications/pom.xml b/applications/pom.xml index 387ecd4fb68..8acfd9a074b 100644 --- a/applications/pom.xml +++ b/applications/pom.xml @@ -81,6 +81,7 @@ ${project.build.outputDirectory}/logging.properties + --enable-preview diff --git a/examples/integrations/neo4j/neo4j-se/pom.xml b/examples/integrations/neo4j/neo4j-se/pom.xml index 3a78c4529d4..3bb393dc6bb 100644 --- a/examples/integrations/neo4j/neo4j-se/pom.xml +++ b/examples/integrations/neo4j/neo4j-se/pom.xml @@ -128,6 +128,7 @@ as the database process is separate from the application --> + --enable-preview --add-exports=java.base/sun.nio.ch=ALL-UNNAMED --add-opens=java.base/java.lang=ALL-UNNAMED --add-opens=java.base/java.lang.reflect=ALL-UNNAMED diff --git a/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java index 9d960c7a89f..74dd3dbe02a 100644 --- a/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java +++ b/examples/media/multipart/src/main/java/io/helidon/examples/media/multipart/Main.java @@ -15,6 +15,7 @@ */ package io.helidon.examples.media.multipart; +import io.helidon.common.LogConfig; import io.helidon.common.http.Http; import io.helidon.common.reactive.Single; import io.helidon.media.jsonp.JsonpSupport; @@ -64,6 +65,7 @@ public static void main(final String[] args) { * @return the created {@link WebServer} instance */ static Single startServer() { + LogConfig.configureRuntime(); WebServer server = WebServer.builder(createRouting()) .port(8080) .addMediaSupport(MultiPartSupport.create()) diff --git a/examples/media/multipart/src/main/resources/logging.properties b/examples/media/multipart/src/main/resources/logging.properties new file mode 100644 index 00000000000..f9194f9b74f --- /dev/null +++ b/examples/media/multipart/src/main/resources/logging.properties @@ -0,0 +1,34 @@ +# +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Example Logging Configuration File +# For more information see $JAVA_HOME/jre/lib/logging.properties + +# Send messages to the console +handlers=io.helidon.logging.jul.HelidonConsoleHandler + +# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n + +# Global logging level. Can be overridden by specific loggers +.level=FINEST + +# Component specific log levels +#io.helidon.webserver.level=INFO +#io.helidon.config.level=INFO +#io.helidon.security.level=INFO +#io.helidon.common.level=INFO +#io.netty.level=INFO diff --git a/examples/microprofile/static-content/pom.xml b/examples/microprofile/static-content/pom.xml index b1bc140dd75..a611ba57c86 100644 --- a/examples/microprofile/static-content/pom.xml +++ b/examples/microprofile/static-content/pom.xml @@ -90,11 +90,15 @@ **/*$* io/helidon/microprofile/example/staticc/StaticContentTest.java + --enable-preview org.apache.maven.plugins maven-failsafe-plugin + + --enable-preview + Run integration tests diff --git a/examples/quickstarts/helidon-quickstart-se/src/test/java/io/helidon/examples/quickstart/se/MainTest.java b/examples/quickstarts/helidon-quickstart-se/src/test/java/io/helidon/examples/quickstart/se/MainTest.java index 4631be06e3c..dcb84591cc0 100644 --- a/examples/quickstarts/helidon-quickstart-se/src/test/java/io/helidon/examples/quickstart/se/MainTest.java +++ b/examples/quickstarts/helidon-quickstart-se/src/test/java/io/helidon/examples/quickstart/se/MainTest.java @@ -19,6 +19,8 @@ import java.util.Collections; import java.util.concurrent.TimeUnit; +import io.helidon.common.LogConfig; +import io.helidon.common.http.Http; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientResponse; @@ -41,6 +43,7 @@ class MainTest { private static final JsonObject TEST_JSON_OBJECT; static { + LogConfig.configureRuntime(); TEST_JSON_OBJECT = JSON_BUILDER.createObjectBuilder() .add("greeting", "Hola") .build(); @@ -92,18 +95,24 @@ void testHelloWorld() { .request(JsonObject.class) .await(); assertEquals("Hola Joe!", jsonObject.getString("message")); + } - response = webClient.get() + @Test + void testHealth() { + WebClientResponse response = webClient.get() .path("/health") .request() .await(); assertEquals(200, response.status().code()); + } - response = webClient.get() + @Test + void testMetrics() { + WebClientResponse response = webClient.get() .path("/metrics") .request() .await(); - assertEquals(200, response.status().code()); + assertEquals(Http.Status.OK_200, response.status()); } } diff --git a/examples/quickstarts/helidon-standalone-quickstart-mp/pom.xml b/examples/quickstarts/helidon-standalone-quickstart-mp/pom.xml index 1fa74d07a98..270d458fc55 100644 --- a/examples/quickstarts/helidon-standalone-quickstart-mp/pom.xml +++ b/examples/quickstarts/helidon-standalone-quickstart-mp/pom.xml @@ -29,7 +29,7 @@ 4.0.0-SNAPSHOT io.helidon.microprofile.cdi.Main - 17 + 19 ${maven.compiler.source} true UTF-8 @@ -104,6 +104,7 @@ ${project.build.outputDirectory}/logging.properties + --enable-preview diff --git a/examples/quickstarts/helidon-standalone-quickstart-se/pom.xml b/examples/quickstarts/helidon-standalone-quickstart-se/pom.xml index 56d6f138a14..59f4e68c5c9 100644 --- a/examples/quickstarts/helidon-standalone-quickstart-se/pom.xml +++ b/examples/quickstarts/helidon-standalone-quickstart-se/pom.xml @@ -29,7 +29,7 @@ 4.0.0-SNAPSHOT io.helidon.examples.quickstart.se.Main - 17 + 19 ${maven.compiler.source} true UTF-8 @@ -117,6 +117,7 @@ ${project.build.outputDirectory}/logging.properties + --enable-preview diff --git a/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/CommentService.java b/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/CommentService.java index ef22d9782a1..73cf820adc0 100644 --- a/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/CommentService.java +++ b/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/CommentService.java @@ -161,13 +161,12 @@ public int hashCode() { } private static final class CommentWriter implements MessageBodyWriter> { - private static final GenericType> SUPPORTED_TYPE = new GenericType>() {}; private static final Http.HeaderValue CONTENT_TYPE_UTF_8 = Http.HeaderValue.createCached(Http.Header.CONTENT_TYPE, HttpMediaType.PLAINTEXT_UTF_8.text()); @Override public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { - if(type.equals(SUPPORTED_TYPE)) { + if(List.class.isAssignableFrom(type.rawType())) { if (context.headers().isAccepted(MediaTypes.TEXT_PLAIN)) { return PredicateResult.SUPPORTED; } else { diff --git a/examples/webserver/tutorial/src/main/resources/logging.properties b/examples/webserver/tutorial/src/main/resources/logging.properties new file mode 100644 index 00000000000..384ec7e992f --- /dev/null +++ b/examples/webserver/tutorial/src/main/resources/logging.properties @@ -0,0 +1,34 @@ +# +# Copyright (c) 2018, 2022 Oracle and/or its affiliates. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +# Example Logging Configuration File +# For more information see $JAVA_HOME/jre/lib/logging.properties + +# Send messages to the console +handlers=io.helidon.logging.jul.HelidonConsoleHandler + +# HelidonConsoleHandler uses a SimpleFormatter subclass that replaces "!thread!" with the current thread +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS %4$s %3$s !thread!: %5$s%6$s%n + +# Global logging level. Can be overridden by specific loggers +.level=INFO + +# Component specific log levels +#io.helidon.webserver.level=INFO +#io.helidon.config.level=INFO +#io.helidon.security.level=INFO +#io.helidon.common.level=INFO +#io.netty.level=INFO diff --git a/examples/webserver/tutorial/src/test/java/io/helidon/webserver/examples/tutorial/CommentServiceTest.java b/examples/webserver/tutorial/src/test/java/io/helidon/webserver/examples/tutorial/CommentServiceTest.java index 0700a91b76a..a025008abd0 100644 --- a/examples/webserver/tutorial/src/test/java/io/helidon/webserver/examples/tutorial/CommentServiceTest.java +++ b/examples/webserver/tutorial/src/test/java/io/helidon/webserver/examples/tutorial/CommentServiceTest.java @@ -18,6 +18,7 @@ import java.nio.charset.StandardCharsets; +import io.helidon.common.LogConfig; import io.helidon.common.http.Http; import io.helidon.common.http.HttpMediaType; import io.helidon.webserver.Routing; @@ -25,6 +26,7 @@ import io.helidon.webserver.testsupport.TestClient; import io.helidon.webserver.testsupport.TestResponse; +import org.junit.jupiter.api.BeforeAll; import org.junit.jupiter.api.Test; import static org.junit.jupiter.api.Assertions.assertEquals; @@ -33,10 +35,15 @@ /** * Tests {@link CommentService}. */ -public class CommentServiceTest { +class CommentServiceTest { + + @BeforeAll + static void setup() { + LogConfig.configureRuntime(); + } @Test - public void addAndGetComments() throws Exception { + void addAndGetComments() throws Exception { CommentService service = new CommentService(); assertEquals(0, service.getComments("one").size()); assertEquals(0, service.getComments("two").size()); @@ -52,7 +59,7 @@ public void addAndGetComments() throws Exception { } @Test - public void testRouting() throws Exception { + void testRouting() throws Exception { Routing routing = Routing.builder() .register(new CommentService()) .build(); diff --git a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java index ad012850bd0..fe898c2ed63 100644 --- a/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java +++ b/media/multipart/src/main/java/io/helidon/media/multipart/MultiPartBodyWriter.java @@ -49,7 +49,7 @@ private MultiPartBodyWriter(String boundary) { public PredicateResult accept(GenericType type, MessageBodyWriterContext context) { return context.contentType() .or(() -> Optional.of(HttpMediaType.create(MULTIPART_FORM_DATA))) - .filter(mediaType -> mediaType == MULTIPART_FORM_DATA) + .filter(mediaType -> mediaType.test(MULTIPART_FORM_DATA)) .map(it -> PredicateResult.supports(WriteableMultiPart.class, type)) .orElse(PredicateResult.NOT_SUPPORTED); } diff --git a/messaging/connectors/aq/pom.xml b/messaging/connectors/aq/pom.xml index fdda51447ff..16e61a655fb 100644 --- a/messaging/connectors/aq/pom.xml +++ b/messaging/connectors/aq/pom.xml @@ -95,6 +95,15 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + + --enable-preview + + + diff --git a/messaging/connectors/jms/pom.xml b/messaging/connectors/jms/pom.xml index 0b063a50a3b..320bc07b6d9 100644 --- a/messaging/connectors/jms/pom.xml +++ b/messaging/connectors/jms/pom.xml @@ -96,6 +96,15 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + + --enable-preview + + + diff --git a/messaging/messaging/pom.xml b/messaging/messaging/pom.xml index 6fdf1fe5831..0846c4f6b85 100644 --- a/messaging/messaging/pom.xml +++ b/messaging/messaging/pom.xml @@ -87,6 +87,15 @@ + + org.apache.maven.plugins + maven-javadoc-plugin + + + --enable-preview + + + diff --git a/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java b/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java index 7223c2f8a81..a19c9ec94e9 100644 --- a/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java +++ b/microprofile/jwt-auth/src/main/java/io/helidon/microprofile/jwt/auth/JwtAuthProvider.java @@ -295,7 +295,7 @@ AuthenticationResponse authenticate(ProviderRequest providerRequest, LoginConfig } private Optional findCookie(Map> headers) { - List cookies = headers.get(Http.Header.COOKIE); + List cookies = headers.get(Http.Header.COOKIE.defaultCase()); if ((null == cookies) || cookies.isEmpty()) { return Optional.empty(); } @@ -1126,7 +1126,7 @@ public Builder config(Config config) { * @return updated builder instance */ public Builder jwtHeader(String header) { - if (Http.Header.COOKIE.equals(header)) { + if (Http.Header.COOKIE.defaultCase().equalsIgnoreCase(header)) { useCookie = true; } else { useCookie = false; diff --git a/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingJavaMockTracerProvider.java b/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingJavaMockTracerProvider.java index 225bacfc2cc..2130ec5f482 100644 --- a/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingJavaMockTracerProvider.java +++ b/microprofile/tests/tck/tck-opentracing/src/test/java/io/helidon/microprofile/opentracing/tck/OpentracingJavaMockTracerProvider.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,14 +16,14 @@ package io.helidon.microprofile.opentracing.tck; +import io.helidon.common.Weight; +import io.helidon.common.Weighted; import io.helidon.tracing.opentracing.spi.OpenTracingProvider; -import jakarta.annotation.Priority; - /** * Provider for opentracing TCK. */ -@Priority(100) +@Weight(Weighted.DEFAULT_WEIGHT) public class OpentracingJavaMockTracerProvider implements OpenTracingProvider { @Override public OpentracingJavaMockTracerBuilder createBuilder() { diff --git a/tests/apps/bookstore/bookstore-mp/pom.xml b/tests/apps/bookstore/bookstore-mp/pom.xml index 613c059ed00..8448a580834 100644 --- a/tests/apps/bookstore/bookstore-mp/pom.xml +++ b/tests/apps/bookstore/bookstore-mp/pom.xml @@ -97,6 +97,13 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + --enable-preview + + diff --git a/tests/apps/bookstore/bookstore-se/pom.xml b/tests/apps/bookstore/bookstore-se/pom.xml index f7d7a00f7c4..cc951fe1d52 100644 --- a/tests/apps/bookstore/bookstore-se/pom.xml +++ b/tests/apps/bookstore/bookstore-se/pom.xml @@ -108,6 +108,13 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + --enable-preview + + diff --git a/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java b/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java index 0426b10d8da..e1fd44f35e8 100644 --- a/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java +++ b/tests/functional/bookstore/src/test/java/io/helidon/tests/bookstore/MainTest.java @@ -193,7 +193,7 @@ void exitOnStartedMp() throws Exception { private void runExitOnStartedTest(String edition) throws Exception { int port = localPlatform.getAvailablePorts().next(); - Arguments args = toArguments(editionToJarPath(edition), List.of("-Dexit.on.started=!"), null, port); + Arguments args = toArguments(editionToJarPath(edition), List.of("-Dexit.on.started=!", "--enable-preview"), null, port); CapturingApplicationConsole console = new CapturingApplicationConsole(); Application application = localPlatform.launch("java", args, Console.of(console)); Queue stdOut = console.getCapturedOutputLines(); @@ -473,6 +473,7 @@ private JsonObject getBookAsJsonObject() throws IOException { private static Arguments toArguments(String appJarPath, List javaArgs, String moduleName, int port) { List startArgs = new ArrayList<>(javaArgs); startArgs.add("-Dserver.port=" + port); + startArgs.add("--enable-preview"); if (moduleName != null && ! moduleName.isEmpty() ) { File jarFile = new File(appJarPath); diff --git a/tests/integration/health/mp-disabled/pom.xml b/tests/integration/health/mp-disabled/pom.xml index 45aa5b4bb5c..df615882e1c 100644 --- a/tests/integration/health/mp-disabled/pom.xml +++ b/tests/integration/health/mp-disabled/pom.xml @@ -50,4 +50,16 @@ test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + --enable-preview + + + + diff --git a/tests/integration/mp-bean-validation/pom.xml b/tests/integration/mp-bean-validation/pom.xml index 33a441839ec..804bf863241 100644 --- a/tests/integration/mp-bean-validation/pom.xml +++ b/tests/integration/mp-bean-validation/pom.xml @@ -78,6 +78,13 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + --enable-preview + + diff --git a/tests/integration/mp-grpc/pom.xml b/tests/integration/mp-grpc/pom.xml index e893c99f68b..8e7eaaa47b6 100644 --- a/tests/integration/mp-grpc/pom.xml +++ b/tests/integration/mp-grpc/pom.xml @@ -133,6 +133,7 @@ -Dgrpc.marshaller.java.enabled=true --add-opens=java.base/java.lang=ALL-UNNAMED + --enable-preview diff --git a/tests/integration/mp-security-client/pom.xml b/tests/integration/mp-security-client/pom.xml index e8854ab5d79..fe47eef3462 100644 --- a/tests/integration/mp-security-client/pom.xml +++ b/tests/integration/mp-security-client/pom.xml @@ -49,4 +49,16 @@ test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + --enable-preview + + + + diff --git a/tests/integration/mp-ws-services/pom.xml b/tests/integration/mp-ws-services/pom.xml index bcf460dbc5e..508b70ba0d8 100644 --- a/tests/integration/mp-ws-services/pom.xml +++ b/tests/integration/mp-ws-services/pom.xml @@ -45,4 +45,16 @@ test + + + + + org.apache.maven.plugins + maven-surefire-plugin + + --enable-preview + + + + diff --git a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/FormTest.java b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/FormTest.java index 0edc3bff29d..10f62ba41de 100644 --- a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/FormTest.java +++ b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/FormTest.java @@ -72,7 +72,7 @@ public void testSpecificContentTypeIncorrect() { .submit(TEST_FORM).await()); assertThat(ex.getCause().getMessage(), - startsWith("No writer found for type: class io.helidon.common.http.FormParamsImpl")); + startsWith("No writer found for type: class ")); } @Test diff --git a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TestParent.java b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TestParent.java index b76264f06ac..888c4c9c2dc 100644 --- a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TestParent.java +++ b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/TestParent.java @@ -31,7 +31,6 @@ import io.helidon.webserver.WebServer; import org.junit.jupiter.api.AfterAll; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeAll; /** @@ -46,24 +45,13 @@ class TestParent { protected static WebClient webClient; @BeforeAll - public static void startTheServer() throws Exception { + public static void startTheServer() { webServer = Main.startServer().await(TIMEOUT); - - long timeout = 2000; // 2 seconds should be enough to start the server - long now = System.currentTimeMillis(); - - while (!webServer.isRunning()) { - Thread.sleep(100); - if ((System.currentTimeMillis() - now) > timeout) { - Assertions.fail("Failed to start webserver"); - } - } - webClient = createNewClient(); } @AfterAll - public static void stopServer() throws Exception { + static void stopServer() throws Exception { if (webServer != null) { webServer.shutdown() .toCompletableFuture() diff --git a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/UriPartTest.java b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/UriPartTest.java index 0fffec28db1..72b057d8408 100644 --- a/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/UriPartTest.java +++ b/tests/integration/webclient/src/test/java/io/helidon/tests/integration/webclient/UriPartTest.java @@ -27,15 +27,12 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.junit.jupiter.api.Assertions.assertThrows; -/** - * TODO javadoc - */ -public class UriPartTest extends TestParent { +class UriPartTest extends TestParent { private static final String EXPECTED_QUERY = "some query &#&@Đ value"; @Test - public void testQuerySpace() { + void testQuerySpace() { String response = webClient.get() .path("obtainedQuery") .queryParam("param", "test") @@ -52,7 +49,7 @@ public void testQuerySpace() { } @Test - public void testQueryKeySpace() { + void testQueryKeySpace() { String queryNameWithSpace = "query name with space"; String response = webClient.get() .path("obtainedQuery") @@ -70,7 +67,7 @@ public void testQueryKeySpace() { } @Test - public void testPathWithSpace() { + void testPathWithSpace() { String response = webClient.get() .path("pattern with space") .request(String.class) @@ -84,7 +81,7 @@ public void testPathWithSpace() { } @Test - public void testFragment() { + void testFragment() { String fragment = "super fragment#&?/"; String response = webClient.get() .path("obtainedQuery") @@ -102,7 +99,7 @@ public void testFragment() { } @Test - public void testQueryNotDecoded() { + void testQueryNotDecoded() { WebClient webClient = createNewClient(request -> { assertThat(request.query(), is("first&second%26=val&ue%26")); return Single.just(request); @@ -116,7 +113,7 @@ public void testQueryNotDecoded() { } @Test - public void testQueryNotDoubleEncoded() { + void testQueryNotDoubleEncoded() { WebClient webClient = createNewClient(request -> { assertThat(request.query(), is("first%26second%26=val%26ue%26")); return Single.just(request); @@ -129,7 +126,7 @@ public void testQueryNotDoubleEncoded() { } @Test - public void testPathNotDecoded() { + void testPathNotDecoded() { WebClient webClient = createNewClient(request -> { assertThat(request.path().rawPath(), is("/greet/path%26")); return Single.just(request); diff --git a/tests/integration/zipkin-mp-2.2/pom.xml b/tests/integration/zipkin-mp-2.2/pom.xml index 4ce98317d37..319faaaeecd 100644 --- a/tests/integration/zipkin-mp-2.2/pom.xml +++ b/tests/integration/zipkin-mp-2.2/pom.xml @@ -81,6 +81,13 @@ + + org.apache.maven.plugins + maven-surefire-plugin + + --enable-preview + + diff --git a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilderImpl.java b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilderImpl.java index 4385a518f45..34525c9ca04 100644 --- a/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilderImpl.java +++ b/webclient/webclient/src/main/java/io/helidon/webclient/WebClientRequestBuilderImpl.java @@ -483,7 +483,7 @@ String query() { String queryFromParams() { if (skipUriEncoding) { - return queryParams.toString(); + return queryParams.value(); } return queryParams.rawValue(); } diff --git a/webserver/webserver/pom.xml b/webserver/webserver/pom.xml index 867101de8f4..b2dbec8d8d7 100644 --- a/webserver/webserver/pom.xml +++ b/webserver/webserver/pom.xml @@ -163,7 +163,6 @@ io.helidon.logging helidon-logging-jul - test From b91edf6160af7ce5587c1802706e3b539605e45d Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 11 Aug 2022 02:13:12 +0200 Subject: [PATCH 41/54] Config changes for new media types --- .../io/helidon/config/mp/MpHelidonSource.java | 5 +- .../config/mp/MpConfigSourcesTest.java | 3 +- config/config/pom.xml | 6 - .../io/helidon/config/AbstractConfigImpl.java | 11 +- .../helidon/config/AbstractConfigSource.java | 23 +-- .../config/AbstractConfigSourceBuilder.java | 33 ++-- .../helidon/config/AbstractSourceBuilder.java | 8 +- .../java/io/helidon/config/BuilderImpl.java | 3 +- .../helidon/config/ClasspathConfigSource.java | 9 +- .../main/java/io/helidon/config/Config.java | 7 +- .../java/io/helidon/config/ConfigParsers.java | 8 +- .../config/ConfigSourceRuntimeImpl.java | 16 +- .../java/io/helidon/config/ConfigSources.java | 7 +- .../java/io/helidon/config/ConfigUtils.java | 11 +- .../io/helidon/config/DeprecatedConfig.java | 10 +- .../io/helidon/config/FileConfigSource.java | 10 +- .../io/helidon/config/FileSourceHelper.java | 11 +- .../io/helidon/config/FileSystemWatcher.java | 21 ++- .../helidon/config/InMemoryConfigSource.java | 5 +- .../java/io/helidon/config/MetaConfig.java | 33 ++-- .../io/helidon/config/MetaConfigFinder.java | 43 ++--- .../config/PropertiesConfigParser.java | 10 +- .../java/io/helidon/config/ProviderImpl.java | 9 +- .../config/ScheduledPollingStrategy.java | 13 +- .../io/helidon/config/SimpleRetryPolicy.java | 13 +- .../io/helidon/config/UrlConfigSource.java | 33 ++-- .../java/io/helidon/config/UrlHelper.java | 13 +- .../helidon/config/ValueResolvingFilter.java | 11 +- .../io/helidon/config/spi/ConfigContent.java | 10 +- .../io/helidon/config/spi/ConfigParser.java | 20 +-- .../io/helidon/config/spi/ContentImpl.java | 15 +- .../io/helidon/config/spi/OverrideSource.java | 4 +- .../io/helidon/config/spi/ParsableSource.java | 5 +- .../AbstractConfigSourceBuilderTest.java | 10 +- .../config/AbstractConfigSourceTest.java | 18 ++- .../config/BuilderImplParsersTest.java | 23 +-- .../io/helidon/config/BuilderImplTest.java | 4 +- .../config/ClasspathConfigSourceTest.java | 18 ++- .../config/ConfigSourceMetaConfigTest.java | 10 +- .../io/helidon/config/ConfigSourceTest.java | 4 +- .../io/helidon/config/ConfigSourcesTest.java | 4 +- .../java/io/helidon/config/ConfigTest.java | 2 +- .../config/DirectoryConfigSourceTest.java | 4 +- .../helidon/config/FileConfigSourceTest.java | 15 +- .../config/FileOverrideSourceTest.java | 4 +- .../helidon/config/FileSourceHelperTest.java | 4 +- .../helidon/config/FileSystemWatcherTest.java | 4 +- .../config/MapConfigSourcePropertiesTest.java | 4 +- .../helidon/config/TestCustomDefaultFile.java | 8 +- .../java/io/helidon/config/TestHelper.java | 5 +- .../config/UrlConfigSourceServerMockTest.java | 24 +-- .../helidon/config/UrlConfigSourceTest.java | 14 +- .../UrlOverrideSourceServerMockTest.java | 38 ++--- .../helidon/config/etcd/EtcdConfigSource.java | 5 +- .../config/etcd/EtcdConfigSourceBuilder.java | 5 +- .../etcd/EtcdConfigSourceBuilderTest.java | 15 +- .../config/etcd/EtcdConfigSourceIT.java | 8 +- .../config/etcd/EtcdConfigSourceTest.java | 19 ++- config/git/pom.xml | 6 - .../helidon/config/git/GitConfigSource.java | 3 +- .../config/git/GitConfigSourceBuilder.java | 5 +- .../git/GitConfigSourceBuilderTest.java | 4 +- .../config/hocon/HoconConfigParser.java | 20 +-- .../io/helidon/config/hocon/package-info.java | 4 +- .../config/hocon/HoconConfigParserTest.java | 11 +- .../hocon/HoconMediaTypeDetectorTest.java | 6 +- config/metadata-processor/pom.xml | 2 +- config/pom.xml | 1 - config/test-infrastructure/pom.xml | 43 ----- .../infra/RestoreSystemPropertiesExt.java | 60 ------- .../config/test/infra/TemporaryFolderExt.java | 147 ------------------ .../config/test/infra/package-info.java | 20 --- .../src/main/java/module-info.java | 24 --- .../config/testing/ValueNodeMatcher.java | 50 +++--- .../AbstractParsers1ConfigParser.java | 8 +- .../config/tests/bundle/SmokeTest.java | 5 +- .../test-default_config-1-properties/pom.xml | 11 +- ...ConfigCreateDefaultFromPropertiesTest.java | 4 +- .../test-default_config-2-hocon-json/pom.xml | 11 +- .../ConfigCreateDefaultFromJsonTest.java | 4 +- .../tests/test-default_config-3-hocon/pom.xml | 11 +- .../ConfigCreateDefaultFromHoconTest.java | 4 +- .../tests/test-default_config-4-yaml/pom.xml | 11 +- .../ConfigCreateDefaultFromYamlTest.java | 4 +- .../test-default_config-5-env_vars/pom.xml | 9 +- .../ConfigCreateDefaultFromEnvVarsTest.java | 4 +- .../pom.xml | 11 +- ...igCreateDefaultFromMetaPropertiesTest.java | 4 +- .../pom.xml | 11 +- .../ConfigCreateDefaultFromMetaJsonTest.java | 4 +- .../test-default_config-8-meta-hocon/pom.xml | 9 +- .../ConfigCreateDefaultFromMetaHoconTest.java | 4 +- .../test-default_config-9-meta-yaml/pom.xml | 11 +- .../ConfigCreateDefaultFromMetaYamlTest.java | 4 +- .../parsers1/AbstractParserServicesTest.java | 5 +- .../helidon/config/yaml/YamlConfigParser.java | 13 +- .../io/helidon/config/yaml/package-info.java | 3 +- .../config/yaml/YamlConfigParserTest.java | 7 +- .../yaml/YamlMediaTypeDetectorTest.java | 39 ----- 99 files changed, 534 insertions(+), 791 deletions(-) delete mode 100644 config/test-infrastructure/pom.xml delete mode 100644 config/test-infrastructure/src/main/java/io/helidon/config/test/infra/RestoreSystemPropertiesExt.java delete mode 100644 config/test-infrastructure/src/main/java/io/helidon/config/test/infra/TemporaryFolderExt.java delete mode 100644 config/test-infrastructure/src/main/java/io/helidon/config/test/infra/package-info.java delete mode 100644 config/test-infrastructure/src/main/java/module-info.java delete mode 100644 config/yaml/src/test/java/io/helidon/config/yaml/YamlMediaTypeDetectorTest.java diff --git a/config/config-mp/src/main/java/io/helidon/config/mp/MpHelidonSource.java b/config/config-mp/src/main/java/io/helidon/config/mp/MpHelidonSource.java index 20e6eb48ff1..aae299aca92 100644 --- a/config/config-mp/src/main/java/io/helidon/config/mp/MpHelidonSource.java +++ b/config/config-mp/src/main/java/io/helidon/config/mp/MpHelidonSource.java @@ -24,6 +24,7 @@ import java.util.concurrent.ConcurrentHashMap; import io.helidon.common.HelidonServiceLoader; +import io.helidon.common.media.type.MediaType; import io.helidon.config.ConfigException; import io.helidon.config.ConfigHelper; import io.helidon.config.spi.ConfigContent; @@ -79,7 +80,7 @@ public static ConfigSource create(ParsableSource source) { } ConfigParser.Content content = load.get(); - String mediaType = content.mediaType() + MediaType mediaType = content.mediaType() .or(source::mediaType) .orElseThrow(() -> new ConfigException("Source " + source + " does not provide media type, cannot use it.")); @@ -94,7 +95,7 @@ public static ConfigSource create(ParsableSource source) { } - private static Optional findParser(String mediaType) { + private static Optional findParser(MediaType mediaType) { return HelidonServiceLoader.create(ServiceLoader.load(ConfigParser.class)) .asList() .stream() diff --git a/config/config-mp/src/test/java/io/helidon/config/mp/MpConfigSourcesTest.java b/config/config-mp/src/test/java/io/helidon/config/mp/MpConfigSourcesTest.java index 0f43e220ee9..a2027ff0dfc 100644 --- a/config/config-mp/src/test/java/io/helidon/config/mp/MpConfigSourcesTest.java +++ b/config/config-mp/src/test/java/io/helidon/config/mp/MpConfigSourcesTest.java @@ -23,6 +23,7 @@ import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.atomic.AtomicInteger; +import io.helidon.common.media.type.MediaType; import io.helidon.config.ConfigException; import io.helidon.config.ConfigSources; import io.helidon.config.PropertiesConfigParser; @@ -199,7 +200,7 @@ public Optional parser() { } @Override - public Optional mediaType() { + public Optional mediaType() { return Optional.empty(); } diff --git a/config/config/pom.xml b/config/config/pom.xml index fdf25fb9c45..c5aba5841dd 100644 --- a/config/config/pom.xml +++ b/config/config/pom.xml @@ -86,12 +86,6 @@ restito test - - io.helidon.config - helidon-config-test-infrastructure - ${project.version} - test - org.slf4j slf4j-jdk14 diff --git a/config/config/src/main/java/io/helidon/config/AbstractConfigImpl.java b/config/config/src/main/java/io/helidon/config/AbstractConfigImpl.java index ee1a1388721..f77c7afd67f 100644 --- a/config/config/src/main/java/io/helidon/config/AbstractConfigImpl.java +++ b/config/config/src/main/java/io/helidon/config/AbstractConfigImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,18 +21,15 @@ import java.util.Objects; import java.util.Optional; import java.util.function.Consumer; -import java.util.logging.Logger; import io.helidon.config.spi.ConfigMapper; /** - * Abstract common implementation of {@link Config} extended by appropriate Config node types: - * {@link ConfigListImpl}, {@link ConfigMissingImpl}, {@link ConfigObjectImpl}, {@link ConfigLeafImpl}. + * Abstract common implementation of {@link io.helidon.config.Config} extended by appropriate Config node types: + * {@link io.helidon.config.ConfigListImpl}, {@link io.helidon.config.ConfigMissingImpl}, {@link io.helidon.config.ConfigObjectImpl}, + * {@link io.helidon.config.ConfigLeafImpl}. */ abstract class AbstractConfigImpl implements Config { - - public static final Logger LOGGER = Logger.getLogger(AbstractConfigImpl.class.getName()); - private final ConfigKeyImpl prefix; private final ConfigKeyImpl key; private final ConfigKeyImpl realKey; diff --git a/config/config/src/main/java/io/helidon/config/AbstractConfigSource.java b/config/config/src/main/java/io/helidon/config/AbstractConfigSource.java index 44c768480f1..aec3b51fc65 100644 --- a/config/config/src/main/java/io/helidon/config/AbstractConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/AbstractConfigSource.java @@ -22,6 +22,7 @@ import java.util.Optional; import java.util.function.Function; +import io.helidon.common.media.type.MediaType; import io.helidon.config.spi.ConfigNode; import io.helidon.config.spi.ConfigNode.ObjectNode; import io.helidon.config.spi.ConfigParser; @@ -45,9 +46,9 @@ * @see io.helidon.config.spi.ParsableSource */ public abstract class AbstractConfigSource extends AbstractSource implements ConfigSource { - private final Optional mediaType; + private final Optional mediaType; private final Optional parser; - private final Optional>> mediaTypeMapping; + private final Optional>> mediaTypeMapping; private final Optional>> parserMapping; private final boolean mediaMappingSupported; @@ -76,7 +77,7 @@ protected AbstractConfigSource(AbstractConfigSourceBuilder builder) { * * @return configured media type or empty if none configured */ - protected Optional mediaType() { + protected Optional mediaType() { return mediaType; } @@ -95,7 +96,7 @@ public String toString() { return description(); } - ObjectNode processNodeMapping(Function> mediaToParser, + ObjectNode processNodeMapping(Function> mediaToParser, ConfigKeyImpl configKey, ObjectNode loaded) { @@ -106,7 +107,7 @@ ObjectNode processNodeMapping(Function> mediaToPa return processObject(mediaToParser, configKey, loaded); } - private ObjectNode processObject(Function> mediaToParser, + private ObjectNode processObject(Function> mediaToParser, ConfigKeyImpl key, ObjectNode objectNode) { ObjectNode.Builder builder = ObjectNode.builder(); @@ -116,7 +117,7 @@ private ObjectNode processObject(Function> mediaT return builder.build(); } - private ConfigNode processNode(Function> mediaToParser, + private ConfigNode processNode(Function> mediaToParser, ConfigKeyImpl key, ConfigNode node) { switch (node.nodeType()) { @@ -131,7 +132,7 @@ private ConfigNode processNode(Function> mediaToP } } - private ConfigNode.ListNode processList(Function> mediaToParser, + private ConfigNode.ListNode processList(Function> mediaToParser, ConfigKeyImpl key, ConfigNode.ListNode listNode) { ListNodeBuilderImpl builder = (ListNodeBuilderImpl) ConfigNode.ListNode.builder(); @@ -143,7 +144,7 @@ private ConfigNode.ListNode processList(Function> return builder.build(); } - private ConfigNode processValue(Function> mediaToParser, + private ConfigNode processValue(Function> mediaToParser, Config.Key key, ConfigNode.ValueNode valueNode) { @@ -167,7 +168,7 @@ private InputStream toStream(String string) { return new ByteArrayInputStream(string.getBytes(StandardCharsets.UTF_8)); } - private Optional findParserForKey(Function> mediaToParser, + private Optional findParserForKey(Function> mediaToParser, Config.Key key) { // try to find it in parser mapping (explicit parser for a key) @@ -178,14 +179,14 @@ private Optional findParserForKey(Function maybeMedia = mediaTypeMapping.flatMap(it -> it.apply(key)); + Optional maybeMedia = mediaTypeMapping.flatMap(it -> it.apply(key)); if (maybeMedia.isEmpty()) { // no media type configured, return empty return Optional.empty(); } - String mediaType = maybeMedia.get(); + MediaType mediaType = maybeMedia.get(); // if media is explicit, parser is required return Optional.of(mediaToParser.apply(mediaType) diff --git a/config/config/src/main/java/io/helidon/config/AbstractConfigSourceBuilder.java b/config/config/src/main/java/io/helidon/config/AbstractConfigSourceBuilder.java index a2b09fe0502..91caf2359b5 100644 --- a/config/config/src/main/java/io/helidon/config/AbstractConfigSourceBuilder.java +++ b/config/config/src/main/java/io/helidon/config/AbstractConfigSourceBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,14 @@ package io.helidon.config; +import java.util.HashMap; import java.util.Map; import java.util.Objects; import java.util.Optional; import java.util.function.Function; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.Source; @@ -36,13 +39,19 @@ public abstract class AbstractConfigSourceBuilder { private ConfigParser parser; - private String mediaType; - private Function> mediaTypeMapping; + private MediaType mediaType; + private Function> mediaTypeMapping; private Function> parserMapping; @SuppressWarnings("unchecked") private final B me = (B) this; + /** + * There is no side effect. + */ + protected AbstractConfigSourceBuilder() { + } + /** * {@inheritDoc} * @@ -69,13 +78,17 @@ public abstract class AbstractConfigSourceBuilder { + Map mapping = new HashMap<>(); + it.forEach((key, mediaType) -> mapping.put(key, MediaTypes.create(mediaType))); + this.mediaTypeMappingConfig(mapping); + }); return me; } - private void mediaTypeMappingConfig(Map mappingMap) { + private void mediaTypeMappingConfig(Map mappingMap) { mediaTypeMapping(key -> Optional.ofNullable(mappingMap.get(key.toString()))); } @@ -87,7 +100,7 @@ private void mediaTypeMappingConfig(Map mappingMap) { * @param mediaTypeMapping a mapping function * @return a modified builder */ - public B mediaTypeMapping(Function> mediaTypeMapping) { + public B mediaTypeMapping(Function> mediaTypeMapping) { Objects.requireNonNull(mediaTypeMapping, "mediaTypeMapping cannot be null"); this.mediaTypeMapping = mediaTypeMapping; @@ -120,7 +133,7 @@ protected B parser(ConfigParser parser) { return me; } - Optional>> mediaTypeMapping() { + Optional>> mediaTypeMapping() { return Optional.ofNullable(mediaTypeMapping); } @@ -139,7 +152,7 @@ Optional>> parserMapping() { * @param mediaType media type configured for this source * @return updated builder instance */ - protected B mediaType(String mediaType) { + protected B mediaType(MediaType mediaType) { this.mediaType = mediaType; return me; } @@ -148,7 +161,7 @@ Optional parser() { return Optional.ofNullable(parser); } - Optional mediaType() { + Optional mediaType() { return Optional.ofNullable(mediaType); } } diff --git a/config/config/src/main/java/io/helidon/config/AbstractSourceBuilder.java b/config/config/src/main/java/io/helidon/config/AbstractSourceBuilder.java index 9dab0a952d2..0e9db56d26c 100644 --- a/config/config/src/main/java/io/helidon/config/AbstractSourceBuilder.java +++ b/config/config/src/main/java/io/helidon/config/AbstractSourceBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -43,6 +43,12 @@ public abstract class AbstractSourceBuilder diff --git a/config/config/src/main/java/io/helidon/config/BuilderImpl.java b/config/config/src/main/java/io/helidon/config/BuilderImpl.java index 82cb1fbcc71..8533a34a43c 100644 --- a/config/config/src/main/java/io/helidon/config/BuilderImpl.java +++ b/config/config/src/main/java/io/helidon/config/BuilderImpl.java @@ -35,6 +35,7 @@ import io.helidon.common.HelidonServiceLoader; import io.helidon.common.Weighted; import io.helidon.common.Weights; +import io.helidon.common.media.type.MediaType; import io.helidon.config.ConfigMapperManager.MapperProviders; import io.helidon.config.spi.ConfigContext; import io.helidon.config.spi.ConfigFilter; @@ -555,7 +556,7 @@ private ConfigSourceRuntimeImpl sourceRuntimeBase(ConfigSource source) { return runtimes.computeIfAbsent(source, it -> new ConfigSourceRuntimeImpl(this, source)); } - Optional findParser(String mediaType) { + Optional findParser(MediaType mediaType) { Objects.requireNonNull(mediaType, "Unknown media type of resource."); return configParsers.stream() diff --git a/config/config/src/main/java/io/helidon/config/ClasspathConfigSource.java b/config/config/src/main/java/io/helidon/config/ClasspathConfigSource.java index 81842118ac2..c87f872eab8 100644 --- a/config/config/src/main/java/io/helidon/config/ClasspathConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/ClasspathConfigSource.java @@ -27,6 +27,7 @@ import java.util.function.Function; import io.helidon.common.LazyValue; +import io.helidon.common.media.type.MediaType; import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.ConfigParser.Content; @@ -34,14 +35,14 @@ import io.helidon.config.spi.ParsableSource; /** - * {@link ConfigSource} implementation that loads configuration content from a resource on a classpath. + * {@link io.helidon.config.spi.ConfigSource} implementation that loads configuration content from a resource on a classpath. * Classpath config source does not support changes (neither through polling nor through change notifications). */ public class ClasspathConfigSource extends AbstractConfigSource implements ConfigSource, ParsableSource { private final String resource; private final URL resourceUrl; - private final LazyValue> mediaType; + private final LazyValue> mediaType; ClasspathConfigSource(Builder builder) { super(builder); @@ -210,7 +211,7 @@ public String toString() { } @Override - public Optional mediaType() { + public Optional mediaType() { return super.mediaType(); } @@ -291,7 +292,7 @@ public Builder parser(ConfigParser parser) { } @Override - public Builder mediaType(String mediaType) { + public Builder mediaType(MediaType mediaType) { return super.mediaType(mediaType); } diff --git a/config/config/src/main/java/io/helidon/config/Config.java b/config/config/src/main/java/io/helidon/config/Config.java index a9906078737..ccb47ce812f 100644 --- a/config/config/src/main/java/io/helidon/config/Config.java +++ b/config/config/src/main/java/io/helidon/config/Config.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -239,6 +239,11 @@ * precedence over values from config sources with lower priority. */ public interface Config { + /** + * Generic type of configuration. + */ + GenericType GENERIC_TYPE = GenericType.create(Config.class); + /** * Returns empty instance of {@code Config}. * diff --git a/config/config/src/main/java/io/helidon/config/ConfigParsers.java b/config/config/src/main/java/io/helidon/config/ConfigParsers.java index 53b17c624f5..a8c0b49e271 100644 --- a/config/config/src/main/java/io/helidon/config/ConfigParsers.java +++ b/config/config/src/main/java/io/helidon/config/ConfigParsers.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -30,9 +30,9 @@ private ConfigParsers() { } /** - * Returns a {@link ConfigParser} implementation that parses Java Properties content - * (the media type {@value PropertiesConfigParser#MEDIA_TYPE_TEXT_JAVA_PROPERTIES}). - *

+ * Returns a {@link io.helidon.config.spi.ConfigParser} implementation that parses Java Properties content + * (the media type {@link io.helidon.config.PropertiesConfigParser#MEDIA_TYPE_TEXT_JAVA_PROPERTIES}). + * * @return {@code ConfigParser} that parses Java Properties content */ public static ConfigParser properties() { diff --git a/config/config/src/main/java/io/helidon/config/ConfigSourceRuntimeImpl.java b/config/config/src/main/java/io/helidon/config/ConfigSourceRuntimeImpl.java index 1db11f18fe0..7c91359737f 100644 --- a/config/config/src/main/java/io/helidon/config/ConfigSourceRuntimeImpl.java +++ b/config/config/src/main/java/io/helidon/config/ConfigSourceRuntimeImpl.java @@ -15,6 +15,7 @@ */ package io.helidon.config; +import java.lang.System.Logger.Level; import java.time.Instant; import java.util.HashMap; import java.util.LinkedList; @@ -27,9 +28,8 @@ import java.util.function.Consumer; import java.util.function.Function; import java.util.function.Supplier; -import java.util.logging.Level; -import java.util.logging.Logger; +import io.helidon.common.media.type.MediaType; import io.helidon.config.spi.ChangeEventType; import io.helidon.config.spi.ChangeWatcher; import io.helidon.config.spi.ConfigNode; @@ -50,7 +50,7 @@ * */ class ConfigSourceRuntimeImpl implements ConfigSourceRuntime { - private static final Logger LOGGER = Logger.getLogger(ConfigSourceRuntimeImpl.class.getName()); + private static final System.Logger LOGGER = System.getLogger(ConfigSourceRuntimeImpl.class.getName()); private final List> listeners = new LinkedList<>(); private final BuilderImpl.ConfigContextImpl configContext; @@ -343,7 +343,7 @@ public ChangeEventType poll(Instant when) { // this is a valid change triggerChanges(configContext, listeners, objectNode); } else { - LOGGER.info("Mandatory config source is not available, ignoring change."); + LOGGER.log(Level.INFO, "Mandatory config source is not available, ignoring change."); } return ChangeEventType.DELETED; } else { @@ -404,18 +404,18 @@ public void accept(ChangeWatcher.ChangeEvent change) { // this is a valid change triggerChanges(configContext, listeners, objectNode); } else { - LOGGER.info("Mandatory config source is not available, ignoring change."); + LOGGER.log(Level.INFO, "Mandatory config source is not available, ignoring change."); } } else { triggerChanges(configContext, listeners, objectNode); } } catch (Exception e) { - LOGGER.info("Failed to reload config source " + LOGGER.log(Level.INFO, "Failed to reload config source " + configSource + ", exception available in finest log level. " + "Change that triggered this event: " + change); - LOGGER.log(Level.FINEST, "Failed to reload config source", e); + LOGGER.log(Level.TRACE, "Failed to reload config source", e); } } } @@ -466,7 +466,7 @@ public Optional get() { } // media type should either be configured on config source, or in content - Optional mediaType = configSource.mediaType().or(content::mediaType); + Optional mediaType = configSource.mediaType().or(content::mediaType); if (mediaType.isPresent()) { parser = configContext.findParser(mediaType.get()); diff --git a/config/config/src/main/java/io/helidon/config/ConfigSources.java b/config/config/src/main/java/io/helidon/config/ConfigSources.java index a33dcfdfc3a..055a3da44a9 100644 --- a/config/config/src/main/java/io/helidon/config/ConfigSources.java +++ b/config/config/src/main/java/io/helidon/config/ConfigSources.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,6 +32,7 @@ import java.util.Properties; import java.util.function.Supplier; +import io.helidon.common.media.type.MediaType; import io.helidon.config.spi.ConfigContent; import io.helidon.config.spi.ConfigNode; import io.helidon.config.spi.ConfigParser; @@ -100,7 +101,7 @@ public static NodeConfigSource create(ConfigNode.ObjectNode objectNode) { * @param mediaType a configuration media type * @return a config source */ - public static ConfigSource create(InputStream data, String mediaType) { + public static ConfigSource create(InputStream data, MediaType mediaType) { return InMemoryConfigSource.create("Readable", ConfigParser.Content.builder() .data(data) .mediaType(mediaType) @@ -117,7 +118,7 @@ public static ConfigSource create(InputStream data, String mediaType) { * @param mediaType a configuration media type * @return a config source */ - public static ConfigSource create(String content, String mediaType) { + public static ConfigSource create(String content, MediaType mediaType) { return InMemoryConfigSource.create("String", ConfigParser.Content.builder() .data(new ByteArrayInputStream(content.getBytes(StandardCharsets.UTF_8))) .mediaType(mediaType) diff --git a/config/config/src/main/java/io/helidon/config/ConfigUtils.java b/config/config/src/main/java/io/helidon/config/ConfigUtils.java index 2ffdcfd583f..d498ef195dc 100644 --- a/config/config/src/main/java/io/helidon/config/ConfigUtils.java +++ b/config/config/src/main/java/io/helidon/config/ConfigUtils.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.helidon.config; +import java.lang.System.Logger.Level; import java.nio.charset.Charset; import java.nio.charset.StandardCharsets; import java.nio.charset.UnsupportedCharsetException; @@ -24,8 +25,6 @@ import java.util.Properties; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; import io.helidon.config.spi.ConfigNode; @@ -35,7 +34,7 @@ */ final class ConfigUtils { - private static final Logger LOGGER = Logger.getLogger(ConfigUtils.class.getName()); + private static final System.Logger LOGGER = System.getLogger(ConfigUtils.class.getName()); private ConfigUtils() { throw new AssertionError("Instantiation not allowed."); @@ -59,9 +58,7 @@ static ConfigNode.ObjectNode mapToObjectNode(Map map, boolean strict) { if (strict) { throw ex; } else { - LOGGER.log(Level.CONFIG, "Tree-structure failure on key '" + entry.getKey() + "', reason: " - + ex.getLocalizedMessage()); - LOGGER.log(Level.FINEST, "Detailed reason of failure of adding key '" + entry.getKey() + LOGGER.log(Level.TRACE, "Tree-structure failure on key '" + entry.getKey() + "' = '" + entry.getValue() + "'.", ex); } } diff --git a/config/config/src/main/java/io/helidon/config/DeprecatedConfig.java b/config/config/src/main/java/io/helidon/config/DeprecatedConfig.java index 8d94358cfe0..e25068bc348 100644 --- a/config/config/src/main/java/io/helidon/config/DeprecatedConfig.java +++ b/config/config/src/main/java/io/helidon/config/DeprecatedConfig.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,7 +16,7 @@ package io.helidon.config; -import java.util.logging.Logger; +import java.lang.System.Logger.Level; /** * A utility class to handle configuration properties that should no longer be used. @@ -25,7 +25,7 @@ * In next major release, the deprecated property is removed (as is use of this class). */ public final class DeprecatedConfig { - private static final Logger LOGGER = Logger.getLogger(DeprecatedConfig.class.getName()); + private static final System.Logger LOGGER = System.getLogger(DeprecatedConfig.class.getName()); private DeprecatedConfig() { } @@ -46,13 +46,13 @@ public static Config get(Config config, String currentKey, String deprecatedKey) if (deprecatedConfig.exists()) { if (currentConfig.exists()) { - LOGGER.warning("You are using both a deprecated configuration and a current one. " + LOGGER.log(Level.WARNING, "You are using both a deprecated configuration and a current one. " + "Deprecated key: \"" + deprecatedConfig.key() + "\", " + "current key: \"" + currentConfig.key() + "\", " + "only the current key will be used, and deprecated will be ignored."); return currentConfig; } else { - LOGGER.warning("You are using a deprecated configuration key. " + LOGGER.log(Level.WARNING, "You are using a deprecated configuration key. " + "Deprecated key: \"" + deprecatedConfig.key() + "\", " + "current key: \"" + currentConfig.key() + "\"."); return deprecatedConfig; diff --git a/config/config/src/main/java/io/helidon/config/FileConfigSource.java b/config/config/src/main/java/io/helidon/config/FileConfigSource.java index a4f1022357f..8288611b3ac 100644 --- a/config/config/src/main/java/io/helidon/config/FileConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/FileConfigSource.java @@ -23,8 +23,8 @@ import java.nio.file.Path; import java.util.Optional; import java.util.function.Function; -import java.util.logging.Logger; +import io.helidon.common.media.type.MediaType; import io.helidon.common.media.type.MediaTypes; import io.helidon.config.FileSourceHelper.DataAndDigest; import io.helidon.config.spi.ChangeWatcher; @@ -43,7 +43,7 @@ public class FileConfigSource extends AbstractConfigSource implements WatchableSource, ParsableSource, PollableSource { - private static final Logger LOGGER = Logger.getLogger(FileConfigSource.class.getName()); + private static final System.Logger LOGGER = System.getLogger(FileConfigSource.class.getName()); private static final String PATH_KEY = "path"; private final Path filePath; @@ -108,7 +108,7 @@ public Path target() { @Override public Optional load() throws ConfigException { - LOGGER.fine(() -> String.format("Getting content from '%s'", filePath)); + LOGGER.log(System.Logger.Level.TRACE, String.format("Getting content from '%s'", filePath)); // now we need to create all the necessary steps in one go, to make sure the digest matches the file Optional dataAndDigest = FileSourceHelper.readDataAndDigest(filePath); @@ -154,7 +154,7 @@ public Optional parser() { } @Override - public Optional mediaType() { + public Optional mediaType() { return super.mediaType(); } @@ -229,7 +229,7 @@ public Builder parser(ConfigParser parser) { } @Override - public Builder mediaType(String mediaType) { + public Builder mediaType(MediaType mediaType) { return super.mediaType(mediaType); } diff --git a/config/config/src/main/java/io/helidon/config/FileSourceHelper.java b/config/config/src/main/java/io/helidon/config/FileSourceHelper.java index 81b45808197..a834f893dfb 100644 --- a/config/config/src/main/java/io/helidon/config/FileSourceHelper.java +++ b/config/config/src/main/java/io/helidon/config/FileSourceHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,7 @@ import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.IOException; +import java.lang.System.Logger.Level; import java.nio.channels.FileLock; import java.nio.channels.NonWritableChannelException; import java.nio.charset.StandardCharsets; @@ -32,8 +33,6 @@ import java.time.Instant; import java.util.Arrays; import java.util.Optional; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; /** @@ -45,7 +44,7 @@ */ public final class FileSourceHelper { - private static final Logger LOGGER = Logger.getLogger(FileSourceHelper.class.getName()); + private static final System.Logger LOGGER = System.getLogger(FileSourceHelper.class.getName()); private static final int FILE_BUFFER_SIZE = 4096; private FileSourceHelper() { @@ -64,10 +63,10 @@ public static Optional lastModifiedTime(Path path) { } catch (FileNotFoundException e) { return Optional.empty(); } catch (IOException e) { - LOGGER.log(Level.FINE, e, () -> "Cannot obtain the last modified time of '" + path + "'."); + LOGGER.log(Level.TRACE, () -> "Cannot obtain the last modified time of '" + path + "'.", e); } Instant timestamp = Instant.MIN; - LOGGER.finer("Cannot obtain the last modified time. Used time '" + timestamp + "' as a content timestamp."); + LOGGER.log(Level.DEBUG, "Cannot obtain the last modified time. Used time '" + timestamp + "' as a content timestamp."); return Optional.of(timestamp); } diff --git a/config/config/src/main/java/io/helidon/config/FileSystemWatcher.java b/config/config/src/main/java/io/helidon/config/FileSystemWatcher.java index dad874be0de..b784b106117 100644 --- a/config/config/src/main/java/io/helidon/config/FileSystemWatcher.java +++ b/config/config/src/main/java/io/helidon/config/FileSystemWatcher.java @@ -17,6 +17,7 @@ package io.helidon.config; import java.io.IOException; +import java.lang.System.Logger.Level; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; @@ -32,8 +33,6 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeUnit; import java.util.function.Consumer; -import java.util.logging.Level; -import java.util.logging.Logger; import io.helidon.config.spi.ChangeEventType; import io.helidon.config.spi.ChangeWatcher; @@ -66,7 +65,7 @@ */ public final class FileSystemWatcher implements ChangeWatcher { - private static final Logger LOGGER = Logger.getLogger(FileSystemWatcher.class.getName()); + private static final System.Logger LOGGER = System.getLogger(FileSystemWatcher.class.getName()); /* * Configurable options through builder. @@ -209,7 +208,7 @@ private Monitor(Consumer> listener, this.watchServiceModifiers = watchServiceModifiers; this.fileExists = Files.exists(target); this.watchingFile = !Files.isDirectory(target); - this.watchedDir = watchingFile ? parentDir(target) : target; + this.watchedDir = watchingFile ? target.getParent() : target; } @SuppressWarnings("unchecked") @@ -257,24 +256,24 @@ public void run() { eventPath = watchedDir.resolve(eventPath); WatchEvent.Kind kind = event.kind(); if (kind.equals(OVERFLOW)) { - LOGGER.finest("Overflow event on path: " + eventPath); + LOGGER.log(Level.DEBUG, "Overflow event on path: " + eventPath); continue; } if (kind.equals(ENTRY_CREATE)) { - LOGGER.finest("Entry created. Path: " + eventPath); + LOGGER.log(Level.DEBUG, "Entry created. Path: " + eventPath); listener.accept(ChangeEvent.create(eventPath, ChangeEventType.CREATED)); } else if (kind == ENTRY_DELETE) { - LOGGER.finest("Entry deleted. Path: " + eventPath); + LOGGER.log(Level.DEBUG, "Entry deleted. Path: " + eventPath); listener.accept(ChangeEvent.create(eventPath, ChangeEventType.DELETED)); } else if (kind == ENTRY_MODIFY) { - LOGGER.finest("Entry changed. Path: " + eventPath); + LOGGER.log(Level.DEBUG, "Entry changed. Path: " + eventPath); listener.accept(ChangeEvent.create(eventPath, ChangeEventType.CHANGED)); } } if (!key.reset()) { - LOGGER.log(Level.FINE, () -> "Directory of '" + target + "' is no more valid to be watched."); + LOGGER.log(Level.TRACE, "Directory of '" + target + "' is no more valid to be watched."); failed = true; } } @@ -305,7 +304,7 @@ private synchronized void register() { oldWatchKey.cancel(); } } catch (IOException e) { - LOGGER.log(Level.FINEST, "Failed to register watch service", e); + LOGGER.log(Level.TRACE, "Failed to register watch service", e); this.failed = true; } @@ -329,7 +328,7 @@ private synchronized void stop() { try { watchService.close(); } catch (IOException e) { - LOGGER.log(Level.FINE, "Failed to close watch service", e); + LOGGER.log(Level.TRACE, "Failed to close watch service", e); } } diff --git a/config/config/src/main/java/io/helidon/config/InMemoryConfigSource.java b/config/config/src/main/java/io/helidon/config/InMemoryConfigSource.java index c523938c44f..c5ac67462f9 100644 --- a/config/config/src/main/java/io/helidon/config/InMemoryConfigSource.java +++ b/config/config/src/main/java/io/helidon/config/InMemoryConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ import java.util.Objects; import java.util.Optional; +import io.helidon.common.media.type.MediaType; import io.helidon.config.spi.ConfigContent.NodeContent; import io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.ConfigSource; @@ -93,7 +94,7 @@ public Optional parser() { } @Override - public Optional mediaType() { + public Optional mediaType() { return Optional.empty(); } } diff --git a/config/config/src/main/java/io/helidon/config/MetaConfig.java b/config/config/src/main/java/io/helidon/config/MetaConfig.java index 299547a5796..d96bd48fc5f 100644 --- a/config/config/src/main/java/io/helidon/config/MetaConfig.java +++ b/config/config/src/main/java/io/helidon/config/MetaConfig.java @@ -15,6 +15,7 @@ */ package io.helidon.config; +import java.lang.System.Logger.Level; import java.util.HashSet; import java.util.LinkedList; import java.util.List; @@ -22,9 +23,9 @@ import java.util.ServiceLoader; import java.util.Set; import java.util.function.Function; -import java.util.logging.Logger; import io.helidon.common.HelidonServiceLoader; +import io.helidon.common.media.type.MediaType; import io.helidon.config.spi.ChangeWatcher; import io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.ConfigSource; @@ -69,12 +70,12 @@ * */ public final class MetaConfig { - private static final Logger LOGGER = Logger.getLogger(MetaConfig.class.getName()); - private static final Set SUPPORTED_MEDIA_TYPES; + private static final System.Logger LOGGER = System.getLogger(MetaConfig.class.getName()); + private static final Set SUPPORTED_MEDIA_TYPES; private static final List SUPPORTED_SUFFIXES; static { - Set supportedMediaTypes = new HashSet<>(); + Set supportedMediaTypes = new HashSet<>(); List supportedSuffixes = new LinkedList<>(); HelidonServiceLoader.create(ServiceLoader.load(ConfigParser.class)) @@ -144,7 +145,10 @@ public static ChangeWatcher changeWatcher(Config metaConfig) { String type = metaConfig.get("type").asString().get(); ChangeWatcher changeWatcher = MetaProviders.changeWatcher(type, metaConfig.get("properties")); - LOGGER.fine(() -> "Loaded change watcher of type \"" + type + "\", class: " + changeWatcher.getClass().getName()); + if (LOGGER.isLoggable(Level.TRACE)) { + LOGGER.log(Level.TRACE, + "Loaded change watcher of type \"" + type + "\", class: " + changeWatcher.getClass().getName()); + } return changeWatcher; } @@ -159,7 +163,9 @@ public static RetryPolicy retryPolicy(Config metaConfig) { String type = metaConfig.get("type").asString().get(); RetryPolicy retryPolicy = MetaProviders.retryPolicy(type, metaConfig.get("properties")); - LOGGER.fine(() -> "Loaded retry policy of type \"" + type + "\", class: " + retryPolicy.getClass().getName()); + if (LOGGER.isLoggable(Level.TRACE)) { + LOGGER.log(Level.TRACE, "Loaded retry policy of type \"" + type + "\", class: " + retryPolicy.getClass().getName()); + } return retryPolicy; } @@ -181,13 +187,17 @@ public static List configSource(Config sourceMetaConfig) { if (multiSource) { List sources = MetaProviders.configSources(type, sourceProperties); - LOGGER.fine(() -> "Loaded sources of type \"" + type + "\", values: " + sources); + if (LOGGER.isLoggable(Level.TRACE)) { + LOGGER.log(Level.TRACE, "Loaded sources of type \"" + type + "\", values: " + sources); + } return sources; } else { ConfigSource source = MetaProviders.configSource(type, sourceProperties); - LOGGER.fine(() -> "Loaded source of type \"" + type + "\", class: " + source.getClass().getName()); + if (LOGGER.isLoggable(Level.TRACE)) { + LOGGER.log(Level.TRACE, "Loaded source of type \"" + type + "\", class: " + source.getClass().getName()); + } return List.of(source); } @@ -200,7 +210,10 @@ static OverrideSource overrideSource(Config sourceMetaConfig) { OverrideSource source = MetaProviders.overrideSource(type, sourceMetaConfig.get("properties")); - LOGGER.fine(() -> "Loaded override source of type \"" + type + "\", class: " + source.getClass().getName()); + if (LOGGER.isLoggable(Level.TRACE)) { + LOGGER.log(Level.TRACE, + "Loaded override source of type \"" + type + "\", class: " + source.getClass().getName()); + } return source; } @@ -216,7 +229,7 @@ static List configSources(Config metaConfig) { } // only interested in config source - static List configSources(Function supportedMediaType, List supportedSuffixes) { + static List configSources(Function supportedMediaType, List supportedSuffixes) { Optional metaConfigOpt = metaConfig(); return metaConfigOpt diff --git a/config/config/src/main/java/io/helidon/config/MetaConfigFinder.java b/config/config/src/main/java/io/helidon/config/MetaConfigFinder.java index e3bb431f687..ee95b176da7 100644 --- a/config/config/src/main/java/io/helidon/config/MetaConfigFinder.java +++ b/config/config/src/main/java/io/helidon/config/MetaConfigFinder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,6 +15,7 @@ */ package io.helidon.config; +import java.lang.System.Logger.Level; import java.net.URL; import java.nio.file.Files; import java.nio.file.Path; @@ -25,8 +26,8 @@ import java.util.Optional; import java.util.Set; import java.util.function.Function; -import java.util.logging.Logger; +import io.helidon.common.media.type.MediaType; import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ConfigNode.ListNode; import io.helidon.config.spi.ConfigNode.ObjectNode; @@ -69,7 +70,8 @@ final class MetaConfigFinder { */ public static final String CONFIG_PROFILE_ENVIRONMENT_VARIABLE = "HELIDON_CONFIG_PROFILE"; - private static final Logger LOGGER = Logger.getLogger(MetaConfigFinder.class.getName()); + private static final MediaType UNKNOWN = MediaTypes.create("unknown", "unknown"); + private static final System.Logger LOGGER = System.getLogger(MetaConfigFinder.class.getName()); private static final List CONFIG_SUFFIXES = List.of("yaml", "conf", "json", "properties"); private static final String META_CONFIG_PREFIX = "meta-config."; private static final String CONFIG_PREFIX = "application."; @@ -81,17 +83,18 @@ final class MetaConfigFinder { private MetaConfigFinder() { } - static Optional findMetaConfig(Function supportedMediaType, List supportedSuffixes) { + static Optional findMetaConfig(Function supportedMediaType, List supportedSuffixes) { return findMetaConfigSource(supportedMediaType, supportedSuffixes) .map(source -> Config.builder(source).build()); } - static Optional findConfigSource(Function supportedMediaType, List supportedSuffixes) { + static Optional findConfigSource(Function supportedMediaType, + List supportedSuffixes) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); return findSource(supportedMediaType, cl, CONFIG_PREFIX, "config source", supportedSuffixes); } - private static Optional findMetaConfigSource(Function supportedMediaType, + private static Optional findMetaConfigSource(Function supportedMediaType, List supportedSuffixes) { ClassLoader cl = Thread.currentThread().getContextClassLoader(); Optional source; @@ -130,7 +133,7 @@ private static Optional findMetaConfigSource(Function findMetaConfigSource(Function findSource(supportedMediaType, cl, "config-profile.", "config profile", supportedSuffixes)); } - private static ConfigSource profileSource(Function supportedMediaType, + private static ConfigSource profileSource(Function supportedMediaType, ClassLoader cl, String profileName, List supportedSuffixes) { @@ -231,7 +234,7 @@ private static void addFile(ListNode.Builder sourceListBuilder, String fileName, .build()); } - private static Optional findSource(Function supportedMediaType, + private static Optional findSource(Function supportedMediaType, ClassLoader cl, String configPrefix, String type, @@ -246,11 +249,11 @@ private static Optional findSource(Function suppo // these are the ones we are interested in Set validSuffixes = new LinkedHashSet<>(); CONFIG_SUFFIXES.stream() - .filter(suffix -> supportedMediaType.apply(MediaTypes.detectExtensionType(suffix).orElse("unknown/unknown"))) + .filter(suffix -> supportedMediaType.apply(MediaTypes.detectExtensionType(suffix).orElse(UNKNOWN))) .forEach(validSuffixes::add); supportedSuffixes.stream() - .filter(suffix -> supportedMediaType.apply(MediaTypes.detectExtensionType(suffix).orElse("unknown/unknown"))) + .filter(suffix -> supportedMediaType.apply(MediaTypes.detectExtensionType(suffix).orElse(UNKNOWN))) .forEach(validSuffixes::add); validSuffixes.forEach(invalidSuffixes::remove); @@ -284,17 +287,17 @@ private static Optional findSource(Function suppo Optional found = findFile(it, type); if (found.isPresent()) { if (FILES_LOGGED.add(it)) { - LOGGER.warning("Configuration file " - + it - + " is on file system, yet there is no parser configured for it"); + LOGGER.log(Level.WARNING, "Configuration file " + + it + + " is on file system, yet there is no parser configured for it"); } } found = MetaConfigFinder.findClasspath(cl, it, type); if (found.isPresent()) { if (CLASSPATH_LOGGED.add(it)) { - LOGGER.warning("Configuration file " - + it - + " is on classpath, yet there is no parser configured for it"); + LOGGER.log(Level.WARNING, "Configuration file " + + it + + " is on classpath, yet there is no parser configured for it"); } } }); @@ -305,7 +308,7 @@ private static Optional findSource(Function suppo private static Optional findFile(String name, String type) { Path path = Paths.get(name); if (Files.exists(path) && Files.isReadable(path) && !Files.isDirectory(path)) { - LOGGER.info("Found " + type + " file: " + path.toAbsolutePath()); + LOGGER.log(Level.INFO, "Found " + type + " file: " + path.toAbsolutePath()); return Optional.of(ConfigSources.file(path).build()); } return Optional.empty(); @@ -315,7 +318,7 @@ private static Optional findClasspath(ClassLoader cl, String name, // so it is a classpath resource? URL resource = cl.getResource(name); if (null != resource) { - LOGGER.fine(() -> "Found " + type + " resource: " + resource.getPath()); + LOGGER.log(Level.TRACE, "Found " + type + " resource: " + resource.getPath()); return Optional.of(ConfigSources.classpath(name).build()); } return Optional.empty(); diff --git a/config/config/src/main/java/io/helidon/config/PropertiesConfigParser.java b/config/config/src/main/java/io/helidon/config/PropertiesConfigParser.java index 38309917fda..d42206e21be 100644 --- a/config/config/src/main/java/io/helidon/config/PropertiesConfigParser.java +++ b/config/config/src/main/java/io/helidon/config/PropertiesConfigParser.java @@ -21,6 +21,8 @@ import io.helidon.common.Weight; import io.helidon.common.Weighted; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ConfigNode; import io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.ConfigParserException; @@ -43,16 +45,16 @@ public class PropertiesConfigParser implements ConfigParser { /** - * A String constant representing {@value} media type. + * Java properties media type. */ - public static final String MEDIA_TYPE_TEXT_JAVA_PROPERTIES = "text/x-java-properties"; + public static final MediaType MEDIA_TYPE_TEXT_JAVA_PROPERTIES = MediaTypes.create("text/x-java-properties"); /** * Priority of the parser used if registered by {@link io.helidon.config.Config.Builder} automatically. */ public static final double WEIGHT = Weighted.DEFAULT_WEIGHT - 10; - private static final Set SUPPORTED_MEDIA_TYPES = Set.of(MEDIA_TYPE_TEXT_JAVA_PROPERTIES); + private static final Set SUPPORTED_MEDIA_TYPES = Set.of(MEDIA_TYPE_TEXT_JAVA_PROPERTIES); /** * Required public constructor for {@link java.util.ServiceLoader}. @@ -61,7 +63,7 @@ public PropertiesConfigParser() { } @Override - public Set supportedMediaTypes() { + public Set supportedMediaTypes() { return SUPPORTED_MEDIA_TYPES; } diff --git a/config/config/src/main/java/io/helidon/config/ProviderImpl.java b/config/config/src/main/java/io/helidon/config/ProviderImpl.java index 87d3f433a0c..3bf20d10f1e 100644 --- a/config/config/src/main/java/io/helidon/config/ProviderImpl.java +++ b/config/config/src/main/java/io/helidon/config/ProviderImpl.java @@ -16,6 +16,7 @@ package io.helidon.config; +import java.lang.System.Logger.Level; import java.time.Instant; import java.util.ArrayList; import java.util.Arrays; @@ -29,8 +30,6 @@ import java.util.concurrent.Executor; import java.util.function.Consumer; import java.util.function.Function; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.stream.Collectors; import java.util.stream.Stream; @@ -42,7 +41,7 @@ */ class ProviderImpl implements Config.Context { - private static final Logger LOGGER = Logger.getLogger(ConfigFactory.class.getName()); + private static final System.Logger LOGGER = System.getLogger(ConfigFactory.class.getName()); private final List> listeners = new LinkedList<>(); @@ -215,7 +214,7 @@ private synchronized void rebuild(Optional objectNode, boolean force lastConfig = newConfig; } - LOGGER.log(Level.FINER, "Change event is not fired, there is no change from the last load."); + LOGGER.log(Level.TRACE, "Change event is not fired, there is no change from the last load."); } } @@ -227,7 +226,7 @@ private void fireLastChangeEvent() { } if (configDiffs != null) { - LOGGER.log(Level.FINER, String.format("Firing last event %s (again)", configDiffs)); + LOGGER.log(Level.TRACE, String.format("Firing last event %s (again)", configDiffs)); changesExecutor.execute(() -> { for (Consumer listener : listeners) { diff --git a/config/config/src/main/java/io/helidon/config/ScheduledPollingStrategy.java b/config/config/src/main/java/io/helidon/config/ScheduledPollingStrategy.java index 33e40882325..1a6d9e319f7 100644 --- a/config/config/src/main/java/io/helidon/config/ScheduledPollingStrategy.java +++ b/config/config/src/main/java/io/helidon/config/ScheduledPollingStrategy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.helidon.config; +import java.lang.System.Logger.Level; import java.time.Duration; import java.time.Instant; import java.util.concurrent.Executors; @@ -25,8 +26,6 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.BiFunction; -import java.util.logging.Level; -import java.util.logging.Logger; import io.helidon.config.spi.ChangeEventType; import io.helidon.config.spi.PollingStrategy; @@ -35,7 +34,7 @@ * A strategy which allows the user to schedule periodically fired polling event. */ public final class ScheduledPollingStrategy implements PollingStrategy { - private static final Logger LOGGER = Logger.getLogger(ScheduledPollingStrategy.class.getName()); + private static final System.Logger LOGGER = System.getLogger(ScheduledPollingStrategy.class.getName()); /* * This class will trigger checks in a periodic manner. @@ -120,10 +119,10 @@ private void scheduleNext() { } catch (RejectedExecutionException e) { if (executor.isShutdown()) { // intentional shutdown of an executor service - LOGGER.log(Level.FINEST, "Executor service is shut down, polling is terminated for " + this, e); + LOGGER.log(Level.TRACE, "Executor service is shut down, polling is terminated for " + this, e); } else { // exceptional condition - LOGGER.log(Level.SEVERE, "Failed to schedule next polling for " + this + ", polling will stop", e); + LOGGER.log(Level.ERROR, "Failed to schedule next polling for " + this + ", polling will stop", e); } } } @@ -373,7 +372,6 @@ public AdaptiveBuilder max(Duration max) { /** * Sets the function that will be used to shorten the interval between ticking. - *

* * @param shortenFunction a function that shorts the interval * @return a modified builder instance @@ -386,7 +384,6 @@ public AdaptiveBuilder shorten(BiFunction shortenFu /** * Sets the function that will be used to lengthen the interval between ticking. - *

* * @param lengthenFunction a function that prolongs the interval * @return a modified builder instance diff --git a/config/config/src/main/java/io/helidon/config/SimpleRetryPolicy.java b/config/config/src/main/java/io/helidon/config/SimpleRetryPolicy.java index 74f13c74629..de199fcbde6 100644 --- a/config/config/src/main/java/io/helidon/config/SimpleRetryPolicy.java +++ b/config/config/src/main/java/io/helidon/config/SimpleRetryPolicy.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.helidon.config; +import java.lang.System.Logger.Level; import java.time.Duration; import java.util.concurrent.CancellationException; import java.util.concurrent.Executors; @@ -23,7 +24,6 @@ import java.util.concurrent.ScheduledFuture; import java.util.concurrent.TimeoutException; import java.util.function.Supplier; -import java.util.logging.Logger; import io.helidon.config.spi.RetryPolicy; @@ -44,7 +44,7 @@ */ public final class SimpleRetryPolicy implements RetryPolicy { - private static final Logger LOGGER = Logger.getLogger(SimpleRetryPolicy.class.getName()); + private static final System.Logger LOGGER = System.getLogger(SimpleRetryPolicy.class.getName()); private final int retries; private final Duration delay; @@ -107,10 +107,10 @@ public T execute(Supplier call) throws ConfigException { Throwable last = null; for (int i = 0; i <= retries; i++) { try { - LOGGER.finest("next delay: " + currentDelay); + LOGGER.log(Level.DEBUG, "next delay: " + currentDelay); overallTimeoutsLeft -= currentDelay.toMillis(); if (overallTimeoutsLeft < 0) { - LOGGER.finest("overall timeout left [ms]: " + overallTimeoutsLeft); + LOGGER.log(Level.DEBUG, "overall timeout left [ms]: " + overallTimeoutsLeft); throw new ConfigException( "Cannot schedule the next call, the current delay would exceed the overall timeout."); } @@ -203,6 +203,9 @@ public static final class Builder implements io.helidon.common.Builder, ParsableSource, PollableSource { - private static final Logger LOGGER = Logger.getLogger(UrlConfigSource.class.getName()); + private static final System.Logger LOGGER = System.getLogger(UrlConfigSource.class.getName()); private static final String GET_METHOD = "GET"; private static final String URL_KEY = "url"; @@ -115,7 +115,7 @@ public Optional parser() { } @Override - public Optional mediaType() { + public Optional mediaType() { return super.mediaType(); } @@ -209,7 +209,7 @@ private Optional httpStream(URLConnection urlConnection) throws IOE connection.connect(); } catch (IOException e) { // considering this to be unavailable - LOGGER.log(Level.FINEST, "Failed to connect to " + url + ", considering this source to be missing", e); + LOGGER.log(Level.TRACE, "Failed to connect to " + url + ", considering this source to be missing", e); return Optional.empty(); } @@ -227,7 +227,7 @@ private Optional httpContent(HttpURLConnection connection) throws IOExc connection.connect(); } catch (IOException e) { // considering this to be unavailable - LOGGER.log(Level.FINEST, "Failed to connect to " + url + ", considering this source to be missing", e); + LOGGER.log(Level.TRACE, "Failed to connect to " + url + ", considering this source to be missing", e); return Optional.empty(); } @@ -235,12 +235,12 @@ private Optional httpContent(HttpURLConnection connection) throws IOExc return Optional.empty(); } - Optional mediaType = mediaType(connection.getContentType()); + Optional mediaType = mediaType(connection.getContentType()); final Instant timestamp; if (connection.getLastModified() == 0) { timestamp = Instant.now(); - LOGGER.fine("Missing GET '" + url + "' response header 'Last-Modified'. Used current time '" - + timestamp + "' as a content timestamp."); + LOGGER.log(Level.TRACE, "Missing GET '" + url + "' response header 'Last-Modified'. Used current time '" + + timestamp + "' as a content timestamp."); } else { timestamp = Instant.ofEpochMilli(connection.getLastModified()); } @@ -258,19 +258,20 @@ private Optional httpContent(HttpURLConnection connection) throws IOExc return Optional.of(builder.build()); } - private Optional mediaType(String responseMediaType) { + private Optional mediaType(String responseMediaType) { return mediaType() - .or(() -> Optional.ofNullable(responseMediaType)) + .or(() -> Optional.ofNullable(responseMediaType).map(MediaTypes::create)) .or(() -> { - Optional mediaType = probeContentType(); - if (LOGGER.isLoggable(Level.FINE)) { - LOGGER.fine("HTTP response does not contain content-type, used guessed one: " + mediaType + "."); + Optional mediaType = probeContentType(); + if (LOGGER.isLoggable(Level.TRACE)) { + LOGGER.log(Level.TRACE, + "HTTP response does not contain content-type, used guessed one: " + mediaType + "."); } return mediaType; }); } - private Optional probeContentType() { + private Optional probeContentType() { return MediaTypes.detectType(url); } @@ -347,7 +348,7 @@ public Builder parser(ConfigParser parser) { } @Override - public Builder mediaType(String mediaType) { + public Builder mediaType(MediaType mediaType) { return super.mediaType(mediaType); } diff --git a/config/config/src/main/java/io/helidon/config/UrlHelper.java b/config/config/src/main/java/io/helidon/config/UrlHelper.java index 2810fc5079d..1267390e555 100644 --- a/config/config/src/main/java/io/helidon/config/UrlHelper.java +++ b/config/config/src/main/java/io/helidon/config/UrlHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,21 +16,20 @@ package io.helidon.config; import java.io.IOException; +import java.lang.System.Logger.Level; import java.net.HttpURLConnection; import java.net.URL; import java.net.URLConnection; import java.time.Instant; import java.util.Optional; -import java.util.logging.Level; -import java.util.logging.Logger; /** * Utility for URL sources. */ final class UrlHelper { - private static final Logger LOGGER = Logger.getLogger(UrlHelper.class.getName()); - private static final String HEAD_METHOD = "HEAD"; static final int STATUS_NOT_FOUND = 404; + private static final System.Logger LOGGER = System.getLogger(UrlHelper.class.getName()); + private static final String HEAD_METHOD = "HEAD"; private UrlHelper() { } @@ -60,12 +59,12 @@ static Optional dataStamp(URL url) { } } } catch (IOException ex) { - LOGGER.log(Level.FINE, ex, () -> "Configuration at url '" + url + "' HEAD is not accessible."); + LOGGER.log(Level.TRACE, () -> "Configuration at url '" + url + "' HEAD is not accessible.", ex); return Optional.empty(); } Instant timestamp = Instant.MIN; - LOGGER.finer("Missing HEAD '" + url + "' response header 'Last-Modified'. Used time '" + LOGGER.log(Level.TRACE, "Missing HEAD '" + url + "' response header 'Last-Modified'. Used time '" + timestamp + "' as a content timestamp."); return Optional.of(timestamp); } diff --git a/config/config/src/main/java/io/helidon/config/ValueResolvingFilter.java b/config/config/src/main/java/io/helidon/config/ValueResolvingFilter.java index 30d3ba73977..3e029b588c2 100644 --- a/config/config/src/main/java/io/helidon/config/ValueResolvingFilter.java +++ b/config/config/src/main/java/io/helidon/config/ValueResolvingFilter.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,11 +16,10 @@ package io.helidon.config; +import java.lang.System.Logger.Level; import java.util.HashSet; import java.util.Optional; import java.util.Set; -import java.util.logging.Level; -import java.util.logging.Logger; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -92,7 +91,7 @@ */ public class ValueResolvingFilter implements ConfigFilter { - private static final Logger LOGGER = Logger.getLogger(ValueResolvingFilter.class.getName()); + private static final System.Logger LOGGER = System.getLogger(ValueResolvingFilter.class.getName()); private static final boolean DEFAULT_FAIL_ON_MISSING_REFERENCE_BEHAVIOR = false; @@ -163,7 +162,9 @@ public String apply(Config.Key key, String stringValue) { if (failOnMissingReference) { throw new ConfigException(String.format(MISSING_REFERENCE_ERROR, key.name()), e); } else { - LOGGER.log(Level.FINER, e, () -> String.format(MISSING_REFERENCE_ERROR, key.name())); + if (LOGGER.isLoggable(Level.TRACE)) { + LOGGER.log(Level.TRACE, String.format(MISSING_REFERENCE_ERROR, key.name()), e); + } return stringValue; } } finally { diff --git a/config/config/src/main/java/io/helidon/config/spi/ConfigContent.java b/config/config/src/main/java/io/helidon/config/spi/ConfigContent.java index a55fa3add26..f2a8445da59 100644 --- a/config/config/src/main/java/io/helidon/config/spi/ConfigContent.java +++ b/config/config/src/main/java/io/helidon/config/spi/ConfigContent.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -36,7 +36,7 @@ default void close() { /** * A modification stamp of the content. - *

+ * * @return a stamp of the content */ default Optional stamp() { @@ -70,6 +70,9 @@ class Builder extends ConfigContent.Builder implements io.helidon.commo // override data private OverrideSource.OverrideData data; + private Builder() { + } + /** * Data of this override source. * @param data the data of this source @@ -118,6 +121,9 @@ class Builder extends ConfigContent.Builder implements io.helidon.commo // node based config source data private ConfigNode.ObjectNode rootNode; + private Builder() { + } + /** * Node with the configuration of this content. * diff --git a/config/config/src/main/java/io/helidon/config/spi/ConfigParser.java b/config/config/src/main/java/io/helidon/config/spi/ConfigParser.java index d8f4a74461e..1aec5bbdcc2 100644 --- a/config/config/src/main/java/io/helidon/config/spi/ConfigParser.java +++ b/config/config/src/main/java/io/helidon/config/spi/ConfigParser.java @@ -25,7 +25,7 @@ import java.util.Set; import java.util.function.Function; -import io.helidon.config.ConfigParsers; +import io.helidon.common.media.type.MediaType; import io.helidon.config.spi.ConfigNode.ObjectNode; /** @@ -47,7 +47,7 @@ * * @see io.helidon.config.Config.Builder#addParser(ConfigParser) * @see io.helidon.config.spi.ParsableSource - * @see ConfigParsers ConfigParsers - access built-in implementations. + * @see io.helidon.config.ConfigParsers ConfigParsers - access built-in implementations. */ public interface ConfigParser { /** @@ -58,11 +58,11 @@ public interface ConfigParser { *

* {@link io.helidon.config.spi.ParsableSource} implementations can use {@link io.helidon.common.media.type.MediaTypes} * to probe for media type of content to provide it to config system through - * {@link io.helidon.config.spi.ConfigParser.Content.Builder#mediaType(String)}. + * {@link io.helidon.config.spi.ConfigParser.Content.Builder#mediaType(io.helidon.common.media.type.MediaType)}. * * @return supported media types by the parser */ - Set supportedMediaTypes(); + Set supportedMediaTypes(); /** * Parses a specified {@link ConfigContent} into a {@link ObjectNode hierarchical configuration representation}. @@ -117,7 +117,7 @@ interface Content extends ConfigContent { * * @return content media type if known, {@code empty} otherwise */ - Optional mediaType(); + Optional mediaType(); /** * Data of this config source. @@ -151,7 +151,7 @@ static Builder builder() { * @param stamp stamp of the content * @return content built from provided information */ - static Content create(InputStream data, String mediaType, Object stamp) { + static Content create(InputStream data, MediaType mediaType, Object stamp) { return builder().data(data) .mediaType(mediaType) .stamp(stamp) @@ -163,7 +163,7 @@ static Content create(InputStream data, String mediaType, Object stamp) { */ class Builder extends ConfigContent.Builder implements io.helidon.common.Builder { private InputStream data; - private String mediaType; + private MediaType mediaType; private Charset charset = StandardCharsets.UTF_8; private Builder() { @@ -189,7 +189,7 @@ public Builder data(InputStream data) { * @param mediaType media type of the content as understood by the config source * @return updated builder instance */ - public Builder mediaType(String mediaType) { + public Builder mediaType(MediaType mediaType) { Objects.requireNonNull(mediaType, "Media type must be provided, or this method should not be called"); this.mediaType = mediaType; return this; @@ -202,7 +202,7 @@ public Builder mediaType(String mediaType) { * @param mediaType optional of media type * @return updated builder instance */ - public Builder mediaType(Optional mediaType) { + public Builder mediaType(Optional mediaType) { mediaType.ifPresent(this::mediaType); return this; } @@ -223,7 +223,7 @@ InputStream data() { return data; } - String mediaType() { + MediaType mediaType() { return mediaType; } diff --git a/config/config/src/main/java/io/helidon/config/spi/ContentImpl.java b/config/config/src/main/java/io/helidon/config/spi/ContentImpl.java index 6ef054a438e..e06e2363be2 100644 --- a/config/config/src/main/java/io/helidon/config/spi/ContentImpl.java +++ b/config/config/src/main/java/io/helidon/config/spi/ContentImpl.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,14 @@ import java.io.IOException; import java.io.InputStream; +import java.lang.System.Logger.Level; import java.nio.charset.Charset; import java.util.Optional; -import java.util.logging.Level; -import java.util.logging.Logger; + +import io.helidon.common.media.type.MediaType; abstract class ContentImpl implements ConfigContent { - private static final Logger LOGGER = Logger.getLogger(ContentImpl.class.getName()); + private static final System.Logger LOGGER = System.getLogger(ContentImpl.class.getName()); private final Object stamp; @@ -38,7 +39,7 @@ public Optional stamp() { } static class ParsableContentImpl extends ContentImpl implements ConfigParser.Content { - private final String mediaType; + private final MediaType mediaType; private final InputStream data; private final Charset charset; @@ -54,12 +55,12 @@ public void close() { try { data.close(); } catch (IOException e) { - LOGGER.log(Level.FINE, "Failed to close input stream", e); + LOGGER.log(Level.TRACE, "Failed to close input stream", e); } } @Override - public Optional mediaType() { + public Optional mediaType() { return Optional.ofNullable(mediaType); } diff --git a/config/config/src/main/java/io/helidon/config/spi/OverrideSource.java b/config/config/src/main/java/io/helidon/config/spi/OverrideSource.java index 91753c209fc..9b6f149098c 100644 --- a/config/config/src/main/java/io/helidon/config/spi/OverrideSource.java +++ b/config/config/src/main/java/io/helidon/config/spi/OverrideSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -111,7 +111,7 @@ public static OverrideData create(List, String>> /** * Creates {@code OverrideData} from a {@code List} of {@code String} * pairs. - *

+ * * @param wildcards {@code List} of pairs of * wildcard expressions and corresponding * replacement values diff --git a/config/config/src/main/java/io/helidon/config/spi/ParsableSource.java b/config/config/src/main/java/io/helidon/config/spi/ParsableSource.java index 0effbbac3e1..e0ed9420eab 100644 --- a/config/config/src/main/java/io/helidon/config/spi/ParsableSource.java +++ b/config/config/src/main/java/io/helidon/config/spi/ParsableSource.java @@ -20,6 +20,7 @@ import java.util.Optional; import java.util.function.Function; +import io.helidon.common.media.type.MediaType; import io.helidon.config.ConfigException; /** @@ -53,7 +54,7 @@ public interface ParsableSource extends Source { * * @return media type if configured or detected from content */ - Optional mediaType(); + Optional mediaType(); /** * Resolve relative resource to the current resource. @@ -87,6 +88,6 @@ interface Builder> extends ConfigSource.Builder { * @param mediaType media type to use * @return updated builder instance */ - B mediaType(String mediaType); + B mediaType(MediaType mediaType); } } diff --git a/config/config/src/test/java/io/helidon/config/AbstractConfigSourceBuilderTest.java b/config/config/src/test/java/io/helidon/config/AbstractConfigSourceBuilderTest.java index 4a941790a33..86a659d1d34 100644 --- a/config/config/src/test/java/io/helidon/config/AbstractConfigSourceBuilderTest.java +++ b/config/config/src/test/java/io/helidon/config/AbstractConfigSourceBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ import java.util.Optional; import java.util.function.Function; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ChangeWatcher; import io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.ParsableSource; @@ -50,11 +52,11 @@ void testDefaults() { @Test void testConfigured() { - String mediaType = "application/json"; + MediaType mediaType = MediaTypes.APPLICATION_JSON; FileSystemWatcher watcher = FileSystemWatcher.create(); PollingStrategy pollingStrategy = PollingStrategies.nop(); ConfigParser parser = ConfigParsers.properties(); - Function> mediaTypeMapping = key -> Optional.empty(); + Function> mediaTypeMapping = key -> Optional.empty(); Function> parserMapping = key -> Optional.empty(); RetryPolicy retryPolicy = RetryPolicies.justCall(); @@ -90,7 +92,7 @@ public FullSourceBuilder parser(ConfigParser parser) { } @Override - public FullSourceBuilder mediaType(String mediaType) { + public FullSourceBuilder mediaType(MediaType mediaType) { return super.mediaType(mediaType); } diff --git a/config/config/src/test/java/io/helidon/config/AbstractConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/AbstractConfigSourceTest.java index 742ebd5580a..6f717236bdd 100644 --- a/config/config/src/test/java/io/helidon/config/AbstractConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/AbstractConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,8 @@ import java.util.Set; import java.util.function.Function; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ChangeWatcher; import io.helidon.config.spi.ConfigContent; import io.helidon.config.spi.ConfigNode; @@ -39,6 +41,8 @@ import org.junit.jupiter.api.Test; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static io.helidon.config.PropertiesConfigParser.MEDIA_TYPE_TEXT_JAVA_PROPERTIES; import static io.helidon.config.ValueNodeMatcher.valueNode; import static org.hamcrest.MatcherAssert.assertThat; @@ -175,7 +179,7 @@ public void testMediaTypeAndParserMapping() { when(context.findParser(MEDIA_TYPE_TEXT_JAVA_PROPERTIES)) .thenReturn(Optional.of(new ConfigParser() { //NOT used parser @Override - public Set supportedMediaTypes() { + public Set supportedMediaTypes() { return Set.of(MEDIA_TYPE_TEXT_JAVA_PROPERTIES); } @@ -208,10 +212,10 @@ public void testInitAll() { "media-type-mapping.password", "application/base64")))); //media-type-mapping - Function> mapping = builder.mediaTypeMapping().get(); - assertThat(mapping.apply(Config.Key.create("yaml")), is(Optional.of("application/x-yaml"))); - assertThat(mapping.apply(Config.Key.create("password")), is(Optional.of("application/base64"))); - assertThat(mapping.apply(Config.Key.create("unknown")), is(Optional.empty())); + Function> mapping = builder.mediaTypeMapping().get(); + assertThat(mapping.apply(Config.Key.create("yaml")), optionalValue(is(MediaTypes.APPLICATION_X_YAML))); + assertThat(mapping.apply(Config.Key.create("password")), optionalValue(is(MediaTypes.create("application/base64")))); + assertThat(mapping.apply(Config.Key.create("unknown")), is(optionalEmpty())); } @Test @@ -267,7 +271,7 @@ public Optional parser() { } @Override - public Optional mediaType() { + public Optional mediaType() { return super.mediaType(); } diff --git a/config/config/src/test/java/io/helidon/config/BuilderImplParsersTest.java b/config/config/src/test/java/io/helidon/config/BuilderImplParsersTest.java index 7cb2561be5c..1d44ec26193 100644 --- a/config/config/src/test/java/io/helidon/config/BuilderImplParsersTest.java +++ b/config/config/src/test/java/io/helidon/config/BuilderImplParsersTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,8 @@ import java.util.concurrent.Executor; import java.util.concurrent.Executors; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ConfigNode.ObjectNode; import io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.ConfigParser.Content; @@ -29,6 +31,9 @@ import org.junit.jupiter.api.Test; +import static io.helidon.common.media.type.MediaTypes.APPLICATION_HOCON; +import static io.helidon.common.media.type.MediaTypes.APPLICATION_JSON; +import static io.helidon.common.media.type.MediaTypes.APPLICATION_X_YAML; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.Matchers.instanceOf; @@ -41,7 +46,7 @@ */ public class BuilderImplParsersTest { - private static final String TEST_MEDIA_TYPE = "my/media/type"; + private static final MediaType TEST_MEDIA_TYPE = MediaTypes.create("my/media/type"); private final Executor changesExecutor = Executors.newSingleThreadExecutor(new ConfigThreadFactory("unit-tests")); @Test @@ -72,7 +77,7 @@ public void testUserDefinedHasPrecedence() { public void testContextFindParserEmpty() { BuilderImpl.ConfigContextImpl context = new BuilderImpl.ConfigContextImpl(changesExecutor, List.of()); - assertThat(context.findParser("_WHATEVER_"), is(Optional.empty())); + assertThat(context.findParser(MediaTypes.create("_WHATEVER_/_WHATEVER_")), is(Optional.empty())); } @Test @@ -81,9 +86,9 @@ public void testContextFindParserNotAvailable() { when(content.mediaType()).thenReturn(Optional.of(TEST_MEDIA_TYPE)); BuilderImpl.ConfigContextImpl context = new BuilderImpl.ConfigContextImpl(changesExecutor, List.of( - mockParser("application/hocon", "application/json"), + mockParser(APPLICATION_HOCON, APPLICATION_JSON), mockParser(), - mockParser("application/x-yaml") + mockParser(APPLICATION_X_YAML) )); assertThat(context.findParser(content.mediaType().get()), is(Optional.empty())); @@ -97,16 +102,16 @@ public void testContextFindParserFindFirst() { ConfigParser firstParser = mockParser(TEST_MEDIA_TYPE); BuilderImpl.ConfigContextImpl context = new BuilderImpl.ConfigContextImpl(changesExecutor, List.of( - mockParser("application/hocon", "application/json"), + mockParser(APPLICATION_HOCON, APPLICATION_JSON), firstParser, mockParser(TEST_MEDIA_TYPE), - mockParser("application/x-yaml") + mockParser(APPLICATION_X_YAML) )); assertThat(context.findParser(content.mediaType().get()).get(), is(firstParser)); } - private ConfigParser mockParser(String... supportedMediaTypes) { + private ConfigParser mockParser(MediaType... supportedMediaTypes) { ConfigParser parser = mock(ConfigParser.class); when(parser.supportedMediaTypes()).thenReturn(Set.of(supportedMediaTypes)); @@ -120,7 +125,7 @@ private ConfigParser mockParser(String... supportedMediaTypes) { private static class MyConfigParser implements ConfigParser { @Override - public Set supportedMediaTypes() { + public Set supportedMediaTypes() { return Set.of(); } diff --git a/config/config/src/test/java/io/helidon/config/BuilderImplTest.java b/config/config/src/test/java/io/helidon/config/BuilderImplTest.java index 0986791cda7..d74ceafb0fc 100644 --- a/config/config/src/test/java/io/helidon/config/BuilderImplTest.java +++ b/config/config/src/test/java/io/helidon/config/BuilderImplTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ import java.util.concurrent.Executor; import java.util.function.Supplier; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.spi.ConfigNode; import io.helidon.config.spi.ConfigSource; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/config/src/test/java/io/helidon/config/ClasspathConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/ClasspathConfigSourceTest.java index 5a9959b7dec..f67bb819b6e 100644 --- a/config/config/src/test/java/io/helidon/config/ClasspathConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/ClasspathConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,13 @@ import java.util.Optional; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ConfigSource; import org.junit.jupiter.api.Test; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; @@ -31,7 +34,7 @@ */ public class ClasspathConfigSourceTest { - private static final String TEST_MEDIA_TYPE = "my/media/type"; + private static final MediaType TEST_MEDIA_TYPE = MediaTypes.create("my/media/type"); @Test public void testDescriptionMandatory() { @@ -63,12 +66,13 @@ public void testGetMediaTypeGuessed() { .optional() .build(); - assertThat(configSource.load().get().mediaType(), is(Optional.of("text/x-java-properties"))); + assertThat(configSource.load().get().mediaType(), + optionalValue(is(PropertiesConfigParser.MEDIA_TYPE_TEXT_JAVA_PROPERTIES))); } @Test public void testGetMediaTypeUnknown() { - ClasspathConfigSource configSource = (ClasspathConfigSource) ConfigSources.classpath("application.unknown") + ClasspathConfigSource configSource = ConfigSources.classpath("application.unknown") .optional() .build(); @@ -77,7 +81,7 @@ public void testGetMediaTypeUnknown() { @Test public void testLoadNotExists() { - ClasspathConfigSource configSource = (ClasspathConfigSource) ConfigSources.classpath("application.unknown") + ClasspathConfigSource configSource = ConfigSources.classpath("application.unknown") .build(); assertThat(configSource.load(), is(Optional.empty())); @@ -86,7 +90,7 @@ public void testLoadNotExists() { @Test public void testLoadExists() { ConfigSource configSource = ConfigSources.classpath("io/helidon/config/application.conf") - .mediaType("application/hocon") + .mediaType(MediaTypes.APPLICATION_HOCON) .build(); } @@ -100,7 +104,7 @@ public void testBuilder() { @Test public void testBuilderWithMediaType() { ConfigSource configSource = ConfigSources.classpath("io/helidon/config/application.conf") - .mediaType("application/hocon") + .mediaType(MediaTypes.APPLICATION_HOCON) .build(); assertThat(configSource, notNullValue()); diff --git a/config/config/src/test/java/io/helidon/config/ConfigSourceMetaConfigTest.java b/config/config/src/test/java/io/helidon/config/ConfigSourceMetaConfigTest.java index e31d05028ff..cd12819cd64 100644 --- a/config/config/src/test/java/io/helidon/config/ConfigSourceMetaConfigTest.java +++ b/config/config/src/test/java/io/helidon/config/ConfigSourceMetaConfigTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,12 +22,13 @@ import java.nio.file.Paths; import java.util.List; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; +import io.helidon.common.testing.junit5.TemporaryFolderExt; import io.helidon.config.spi.ConfigNode.ObjectNode; import io.helidon.config.spi.ConfigSource; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; -import io.helidon.config.test.infra.TemporaryFolderExt; import com.xebialabs.restito.server.StubServer; +import org.glassfish.grizzly.http.util.HttpStatus; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.RegisterExtension; @@ -36,7 +37,6 @@ import static com.xebialabs.restito.semantics.Action.status; import static com.xebialabs.restito.semantics.Action.stringContent; import static com.xebialabs.restito.semantics.Condition.get; -import static org.glassfish.grizzly.http.util.HttpStatus.OK_200; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.instanceOf; @@ -162,7 +162,7 @@ public void testUrl() { try { whenHttp(server) .match(get("/application.properties")) - .then(status(OK_200), + .then(status(HttpStatus.OK_200), stringContent("greeting = Hello")); Config metaConfig = builderFrom(ConfigSources.create( diff --git a/config/config/src/test/java/io/helidon/config/ConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/ConfigSourceTest.java index 70c9282edcc..09d79bbfbbb 100644 --- a/config/config/src/test/java/io/helidon/config/ConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/ConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,10 +18,10 @@ import java.util.Optional; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.BuilderImpl.ConfigContextImpl; import io.helidon.config.spi.ConfigNode.ObjectNode; import io.helidon.config.spi.ConfigSource; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/config/src/test/java/io/helidon/config/ConfigSourcesTest.java b/config/config/src/test/java/io/helidon/config/ConfigSourcesTest.java index 8d5acbda0ac..8f51ffd09af 100644 --- a/config/config/src/test/java/io/helidon/config/ConfigSourcesTest.java +++ b/config/config/src/test/java/io/helidon/config/ConfigSourcesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,12 +21,12 @@ import java.util.Optional; import java.util.Properties; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.spi.ConfigContext; import io.helidon.config.spi.ConfigNode.ListNode; import io.helidon.config.spi.ConfigNode.ObjectNode; import io.helidon.config.spi.ConfigSource; import io.helidon.config.spi.NodeConfigSource; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/config/src/test/java/io/helidon/config/ConfigTest.java b/config/config/src/test/java/io/helidon/config/ConfigTest.java index 9317dc5c11c..a6b40f37a4d 100644 --- a/config/config/src/test/java/io/helidon/config/ConfigTest.java +++ b/config/config/src/test/java/io/helidon/config/ConfigTest.java @@ -26,10 +26,10 @@ import java.util.stream.Collectors; import io.helidon.common.GenericType; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.Config.Key; import io.helidon.config.spi.ConfigNode.ListNode; import io.helidon.config.spi.ConfigNode.ObjectNode; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.hamcrest.Matcher; import org.junit.jupiter.api.Test; diff --git a/config/config/src/test/java/io/helidon/config/DirectoryConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/DirectoryConfigSourceTest.java index bbf79bc23ed..ddebdd2f938 100644 --- a/config/config/src/test/java/io/helidon/config/DirectoryConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/DirectoryConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,10 +22,10 @@ import java.time.Instant; import java.util.Optional; +import io.helidon.common.testing.junit5.TemporaryFolderExt; import io.helidon.config.spi.ConfigContent; import io.helidon.config.spi.ConfigNode.ObjectNode; import io.helidon.config.spi.ConfigSource; -import io.helidon.config.test.infra.TemporaryFolderExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; diff --git a/config/config/src/test/java/io/helidon/config/FileConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/FileConfigSourceTest.java index 12889ce2c67..c03d865ab19 100644 --- a/config/config/src/test/java/io/helidon/config/FileConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/FileConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,13 +23,16 @@ import java.nio.file.Paths; import java.util.Optional; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; +import io.helidon.common.testing.junit5.TemporaryFolderExt; import io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.ConfigSource; -import io.helidon.config.test.infra.TemporaryFolderExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -42,7 +45,7 @@ */ public class FileConfigSourceTest { - private static final String TEST_MEDIA_TYPE = "my/media/type"; + private static final MediaType TEST_MEDIA_TYPE = MediaTypes.create("my/media/type"); private static final String RELATIVE_PATH_TO_RESOURCE = "/src/test/resources/"; @RegisterExtension @@ -94,10 +97,10 @@ public void testLoadNotExists() { public void testLoadExists() throws IOException { Path path = Paths.get(getDir() + "io/helidon/config/application.conf"); FileConfigSource configSource = ConfigSources.file(path) - .mediaType("application/hocon") + .mediaType(MediaTypes.APPLICATION_HOCON) .build(); - assertThat(configSource.mediaType(), is(Optional.of("application/hocon"))); + assertThat(configSource.mediaType(), optionalValue(is(MediaTypes.APPLICATION_HOCON))); assertThat(configSource.target(), is(path)); assertThat(configSource.targetType(), is(typeCompatibleWith(Path.class))); assertThat(configSource.exists(), is(true)); @@ -132,7 +135,7 @@ public void testBuilder() { @Test public void testBuilderWithMediaType() { ConfigSource configSource = ConfigSources.file("application.conf") - .mediaType("application/hocon") + .mediaType(MediaTypes.APPLICATION_HOCON) .build(); assertThat(configSource, notNullValue()); diff --git a/config/config/src/test/java/io/helidon/config/FileOverrideSourceTest.java b/config/config/src/test/java/io/helidon/config/FileOverrideSourceTest.java index ee8b5a2ecb3..69ebc3965b8 100644 --- a/config/config/src/test/java/io/helidon/config/FileOverrideSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/FileOverrideSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,9 +19,9 @@ import java.nio.file.Paths; import java.util.Optional; +import io.helidon.common.testing.junit5.TemporaryFolderExt; import io.helidon.config.spi.ConfigContent; import io.helidon.config.spi.OverrideSource; -import io.helidon.config.test.infra.TemporaryFolderExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; diff --git a/config/config/src/test/java/io/helidon/config/FileSourceHelperTest.java b/config/config/src/test/java/io/helidon/config/FileSourceHelperTest.java index e9a0d5f2599..71b0913cafa 100644 --- a/config/config/src/test/java/io/helidon/config/FileSourceHelperTest.java +++ b/config/config/src/test/java/io/helidon/config/FileSourceHelperTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,7 +19,7 @@ import java.io.File; import java.nio.file.Files; -import io.helidon.config.test.infra.TemporaryFolderExt; +import io.helidon.common.testing.junit5.TemporaryFolderExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.RegisterExtension; diff --git a/config/config/src/test/java/io/helidon/config/FileSystemWatcherTest.java b/config/config/src/test/java/io/helidon/config/FileSystemWatcherTest.java index deaf6f2d8b5..da12f93a10f 100644 --- a/config/config/src/test/java/io/helidon/config/FileSystemWatcherTest.java +++ b/config/config/src/test/java/io/helidon/config/FileSystemWatcherTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicInteger; -import io.helidon.config.test.infra.TemporaryFolderExt; +import io.helidon.common.testing.junit5.TemporaryFolderExt; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; diff --git a/config/config/src/test/java/io/helidon/config/MapConfigSourcePropertiesTest.java b/config/config/src/test/java/io/helidon/config/MapConfigSourcePropertiesTest.java index a331e18d860..ae8f302638e 100644 --- a/config/config/src/test/java/io/helidon/config/MapConfigSourcePropertiesTest.java +++ b/config/config/src/test/java/io/helidon/config/MapConfigSourcePropertiesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,9 +20,9 @@ import java.util.Properties; import java.util.stream.Collectors; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.spi.ConfigNode; import io.helidon.config.spi.ConfigSource; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; diff --git a/config/config/src/test/java/io/helidon/config/TestCustomDefaultFile.java b/config/config/src/test/java/io/helidon/config/TestCustomDefaultFile.java index 449c215c977..6622f6ce9e2 100644 --- a/config/config/src/test/java/io/helidon/config/TestCustomDefaultFile.java +++ b/config/config/src/test/java/io/helidon/config/TestCustomDefaultFile.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,8 @@ import java.util.regex.Matcher; import java.util.regex.Pattern; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ConfigNode; import io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.ConfigParserException; @@ -53,8 +55,8 @@ private static class YmlParser implements ConfigParser { private static final Pattern PROPERTY_PATTERN = Pattern.compile("(.*?): (.*)"); @Override - public Set supportedMediaTypes() { - return Set.of("application/x-yaml"); + public Set supportedMediaTypes() { + return Set.of(MediaTypes.APPLICATION_X_YAML); } @Override diff --git a/config/config/src/test/java/io/helidon/config/TestHelper.java b/config/config/src/test/java/io/helidon/config/TestHelper.java index 40a83d9b240..0e702e0dc0a 100644 --- a/config/config/src/test/java/io/helidon/config/TestHelper.java +++ b/config/config/src/test/java/io/helidon/config/TestHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.io.InputStream; import java.nio.charset.StandardCharsets; +import io.helidon.common.media.type.MediaType; import io.helidon.config.spi.ConfigParser; /** @@ -50,7 +51,7 @@ public static String inputStreamToString(InputStream inputStream) throws IOExcep return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); } - public static ConfigParser.Content parsableContent(String data, String mediaType, Object stamp) { + public static ConfigParser.Content parsableContent(String data, MediaType mediaType, Object stamp) { return ConfigParser.Content.builder() .data(toInputStream(data)) .mediaType(mediaType) diff --git a/config/config/src/test/java/io/helidon/config/UrlConfigSourceServerMockTest.java b/config/config/src/test/java/io/helidon/config/UrlConfigSourceServerMockTest.java index e810139c2b4..3d4a95990fa 100644 --- a/config/config/src/test/java/io/helidon/config/UrlConfigSourceServerMockTest.java +++ b/config/config/src/test/java/io/helidon/config/UrlConfigSourceServerMockTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2020 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.time.Instant; import java.util.Optional; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ConfigParser.Content; import com.xebialabs.restito.server.StubServer; @@ -49,7 +51,7 @@ */ public class UrlConfigSourceServerMockTest { - private static final String TEST_MEDIA_TYPE = "my/media/type"; + private static final MediaType TEST_MEDIA_TYPE = MediaTypes.create("my/media/type"); private static final String TEST_CONFIG = "test-key = test-value"; private StubServer server; @@ -70,7 +72,7 @@ public void testDataTimestamp() throws IOException { .match(method(GET), uri("/application.properties")) .then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), header("Last-Modified", "Sat, 10 Jun 2017 10:14:02 GMT"), stringContent(TEST_CONFIG) ); @@ -96,7 +98,7 @@ public void testDoNotReloadSameContent() throws IOException { match(method(HEAD), uri("/application.properties")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), header("Last-Modified", "Sat, 10 Jun 2017 10:14:02 GMT"), stringContent(TEST_CONFIG) ); @@ -105,7 +107,7 @@ public void testDoNotReloadSameContent() throws IOException { match(get("/application.properties")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), header("Last-Modified", "Sat, 10 Jun 2017 10:14:02 GMT"), stringContent(TEST_CONFIG) ); @@ -137,7 +139,7 @@ public void testDoReloadChangedContent() throws IOException { match(method(HEAD), uri("/application.properties")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), header("Last-Modified", "Sat, 10 Jun 2017 10:14:02 GMT"), stringContent(TEST_CONFIG) ); @@ -146,7 +148,7 @@ public void testDoReloadChangedContent() throws IOException { match(get("/application.properties")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), header("Last-Modified", "Sat, 10 Jun 2017 10:14:02 GMT"), stringContent(TEST_CONFIG) ); @@ -164,7 +166,7 @@ public void testDoReloadChangedContent() throws IOException { match(method(HEAD), uri("/application.properties")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), header("Last-Modified", "Sat, 10 Jun 2017 10:14:03 GMT"), stringContent(TEST_CONFIG) ); @@ -190,7 +192,7 @@ public void testContentMediaTypeSet() throws IOException { match(get("/application.properties")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(TEST_CONFIG) ); @@ -216,7 +218,7 @@ public void testContentMediaTypeFromResponse() throws IOException { match(get("/application.properties")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(TEST_CONFIG) ); @@ -241,7 +243,7 @@ public void testContentMediaTypeGuessed() throws IOException { match(get("/application.properties")). then( status(OK_200), - contentType(TEST_MEDIA_TYPE), + contentType(TEST_MEDIA_TYPE.fullType()), stringContent(TEST_CONFIG) ); diff --git a/config/config/src/test/java/io/helidon/config/UrlConfigSourceTest.java b/config/config/src/test/java/io/helidon/config/UrlConfigSourceTest.java index f1b95d79757..725e7e1040b 100644 --- a/config/config/src/test/java/io/helidon/config/UrlConfigSourceTest.java +++ b/config/config/src/test/java/io/helidon/config/UrlConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.time.Duration; import java.util.Optional; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ConfigSource; import org.junit.jupiter.api.Test; @@ -36,7 +38,7 @@ */ public class UrlConfigSourceTest { - private static final String TEST_MEDIA_TYPE = "my/media/type"; + private static final MediaType TEST_MEDIA_TYPE = MediaTypes.create("my/media/type"); @Test public void testDescriptionMandatory() throws MalformedURLException { @@ -56,7 +58,7 @@ public void testDescriptionOptional() throws MalformedURLException { @Test public void testGetMediaTypeSet() throws MalformedURLException { - UrlConfigSource configSource = (UrlConfigSource) ConfigSources + UrlConfigSource configSource = ConfigSources .url(new URL("http://config-service/application.json")) .optional() .mediaType(TEST_MEDIA_TYPE) @@ -67,7 +69,7 @@ public void testGetMediaTypeSet() throws MalformedURLException { @Test public void testGetMediaTypeGuessed() throws MalformedURLException { - UrlConfigSource configSource = (UrlConfigSource) ConfigSources + UrlConfigSource configSource = ConfigSources .url(new URL("http://config-service/application.json")) .optional() .build(); @@ -77,7 +79,7 @@ public void testGetMediaTypeGuessed() throws MalformedURLException { @Test public void testGetMediaTypeUnknown() throws MalformedURLException { - UrlConfigSource configSource = (UrlConfigSource) ConfigSources + UrlConfigSource configSource = ConfigSources .url(new URL("http://config-service/application.unknown")) .optional() .build(); @@ -87,7 +89,7 @@ public void testGetMediaTypeUnknown() throws MalformedURLException { @Test public void testLoadNotExists() throws MalformedURLException { - UrlConfigSource configSource = (UrlConfigSource) ConfigSources + UrlConfigSource configSource = ConfigSources .url(new URL("http://config-service/application.unknown")) .build(); diff --git a/config/config/src/test/java/io/helidon/config/UrlOverrideSourceServerMockTest.java b/config/config/src/test/java/io/helidon/config/UrlOverrideSourceServerMockTest.java index 063915a0ad2..0cfa45dd18f 100644 --- a/config/config/src/test/java/io/helidon/config/UrlOverrideSourceServerMockTest.java +++ b/config/config/src/test/java/io/helidon/config/UrlOverrideSourceServerMockTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020 Oracle and/or its affiliates. + * Copyright (c) 2020, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -84,14 +84,14 @@ public void testWildcards() throws MalformedURLException, InterruptedException { match(method(HEAD), uri("/override")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), header("Last-Modified", "Sat, 10 Jun 2017 10:14:02 GMT") ); whenHttp(server). match(method(GET), uri("/override")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), header("Last-Modified", "Sat, 10 Jun 2017 10:14:02 GMT"), stringContent(WILDCARDS) ); @@ -116,14 +116,14 @@ public void testMultipleMatchingWildcards() throws MalformedURLException, Interr match(method(HEAD), uri("/override")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), header("Last-Modified", "Sat, 10 Jun 2017 10:14:02 GMT") ); whenHttp(server). match(method(GET), uri("/override")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), header("Last-Modified", "Sat, 10 Jun 2017 10:14:02 GMT"), stringContent(MULTIPLE_WILDCARDS) ); @@ -148,14 +148,14 @@ public void testMultipleMatchingWildcardsAnotherOrdering() throws MalformedURLEx match(method(HEAD), uri("/override")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), header("Last-Modified", "Sat, 10 Jun 2017 10:14:02 GMT") ); whenHttp(server). match(method(GET), uri("/override")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), header("Last-Modified", "Sat, 10 Jun 2017 10:14:02 GMT"), stringContent(MULTIPLE_WILDCARDS_ANOTHER_ORDERING) ); @@ -180,7 +180,7 @@ public void testWildcardsChanges() throws MalformedURLException { match(method(GET), uri("/override")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(WILDCARDS) ); @@ -188,7 +188,7 @@ public void testWildcardsChanges() throws MalformedURLException { match(method(GET), uri("/config")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(CONFIG) ); @@ -211,7 +211,7 @@ public void testWildcardsChanges() throws MalformedURLException { match(method(GET), uri("/override")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(NEW_WILDCARDS) ); @@ -230,7 +230,7 @@ public void testWildcardsSupplier() throws MalformedURLException, InterruptedExc match(method(GET), uri("/override")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(WILDCARDS) ); @@ -238,7 +238,7 @@ public void testWildcardsSupplier() throws MalformedURLException, InterruptedExc match(method(GET), uri("/config")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(CONFIG) ); @@ -257,7 +257,7 @@ public void testWildcardsSupplier() throws MalformedURLException, InterruptedExc match(custom(call -> call.getMethod().equals(GET) || call.getMethod().equals(HEAD)), uri("/override")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(NO_WILDCARDS) ); @@ -272,7 +272,7 @@ public void testConfigChangingWithOverrideSource() throws MalformedURLException, match(method(GET), uri("/override")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(WILDCARDS) ); @@ -280,7 +280,7 @@ public void testConfigChangingWithOverrideSource() throws MalformedURLException, match(method(GET), uri("/config")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(CONFIG) ); @@ -301,7 +301,7 @@ public void testConfigChangingWithOverrideSource() throws MalformedURLException, match(method(GET), uri("/config")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(CONFIG2) ); @@ -316,7 +316,7 @@ public void testConfigChangingWithFilters() throws MalformedURLException, Interr match(method(GET), uri("/override")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(WILDCARDS) ); @@ -324,7 +324,7 @@ public void testConfigChangingWithFilters() throws MalformedURLException, Interr match(method(GET), uri("/config")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(CONFIG) ); @@ -348,7 +348,7 @@ public void testConfigChangingWithFilters() throws MalformedURLException, Interr match(method(GET), uri("/config")). then( status(OK_200), - contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES), + contentType(MEDIA_TYPE_TEXT_JAVA_PROPERTIES.fullType()), stringContent(CONFIG2) ); diff --git a/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSource.java b/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSource.java index a130a9e6750..a29c42131c0 100644 --- a/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSource.java +++ b/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,6 +25,7 @@ import java.util.logging.Level; import java.util.logging.Logger; +import io.helidon.common.media.type.MediaType; import io.helidon.common.media.type.MediaTypes; import io.helidon.config.AbstractConfigSource; import io.helidon.config.Config; @@ -81,7 +82,7 @@ public Optional parser() { } @Override - public Optional mediaType() { + public Optional mediaType() { return super.mediaType(); } diff --git a/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSourceBuilder.java b/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSourceBuilder.java index 1bedd5e453c..21af8d71410 100644 --- a/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSourceBuilder.java +++ b/config/etcd/src/main/java/io/helidon/config/etcd/EtcdConfigSourceBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import java.util.stream.Collectors; import io.helidon.common.Builder; +import io.helidon.common.media.type.MediaType; import io.helidon.config.AbstractConfigSourceBuilder; import io.helidon.config.Config; import io.helidon.config.etcd.EtcdConfigSourceBuilder.EtcdEndpoint; @@ -120,7 +121,7 @@ public EtcdConfigSourceBuilder parser(ConfigParser parser) { } @Override - public EtcdConfigSourceBuilder mediaType(String mediaType) { + public EtcdConfigSourceBuilder mediaType(MediaType mediaType) { return super.mediaType(mediaType); } diff --git a/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceBuilderTest.java b/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceBuilderTest.java index 03a19c589a9..85f0f0bcf17 100644 --- a/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceBuilderTest.java +++ b/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,8 @@ import java.util.Map; import java.util.Set; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.ConfigParsers; import io.helidon.config.ConfigSources; @@ -42,6 +44,7 @@ * Tests {@link EtcdConfigSourceBuilder}. */ public class EtcdConfigSourceBuilderTest { + private static final MediaType MY_MEDIA_TYPE = MediaTypes.create("my/media/type"); @Test public void testBuilderSuccessful() { @@ -49,7 +52,7 @@ public void testBuilderSuccessful() { .uri(URI.create("http://localhost:2379")) .key("/registry") .api(EtcdApi.v2) - .mediaType("my/media/type") + .mediaType(MY_MEDIA_TYPE) .build(); assertThat(etcdConfigSource, notNullValue()); @@ -62,7 +65,7 @@ public void testBuilderWithoutUri() { .uri() .key("/registry") .api(EtcdApi.v2) - .mediaType("my/media/type") + .mediaType(MY_MEDIA_TYPE) .parser(ConfigParsers.properties()) .build(); }); @@ -75,7 +78,7 @@ public void testBuilderWithoutKey() { .uri(URI.create("http://localhost:2379")) .key(null) .api(EtcdApi.v2) - .mediaType("my/media/type") + .mediaType(MY_MEDIA_TYPE) .parser(ConfigParsers.properties()) .build(); }); @@ -88,7 +91,7 @@ public void testBuilderWithoutVersion() { .uri(URI.create("http://localhost:2379")) .key("/registry") .api(null) - .mediaType("my/media/type") + .mediaType(MY_MEDIA_TYPE) .parser(ConfigParsers.properties()) .build(); }); @@ -100,7 +103,7 @@ public void testEtcdConfigSourceDescription() { .uri(URI.create("http://localhost:2379")) .key("/registry") .api(EtcdApi.v2) - .mediaType("my/media/type") + .mediaType(MY_MEDIA_TYPE) .parser(ConfigParsers.properties()) .build().description(), is("EtcdConfig[http://localhost:2379#/registry]")); diff --git a/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceIT.java b/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceIT.java index bec270e373a..59c944136ea 100644 --- a/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceIT.java +++ b/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceIT.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.etcd.EtcdConfigSourceBuilder.EtcdApi; import io.helidon.config.etcd.internal.client.EtcdClient; @@ -31,7 +32,6 @@ import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.EnumSource; -import static io.helidon.config.etcd.EtcdConfigSourceTest.MEDIA_TYPE_APPLICATION_HOCON; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.hasSize; import static org.hamcrest.core.Is.is; @@ -52,7 +52,7 @@ public void testConfig(EtcdApi version) throws Exception { .uri(DEFAULT_URI) .key("configuration") .api(version) - .mediaType(MEDIA_TYPE_APPLICATION_HOCON) + .mediaType(MediaTypes.APPLICATION_HOCON) .build()) .addParser(HoconConfigParser.create()) .build(); @@ -69,7 +69,7 @@ public void testConfigChanges(EtcdApi version) throws Exception { .uri(DEFAULT_URI) .key("configuration") .api(version) - .mediaType(MEDIA_TYPE_APPLICATION_HOCON) + .mediaType(MediaTypes.APPLICATION_HOCON) .changeWatcher(EtcdWatcher.create()) .build()) .addParser(HoconConfigParser.create()) diff --git a/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceTest.java b/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceTest.java index d7d31196c1e..4d803bea251 100644 --- a/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceTest.java +++ b/config/etcd/src/test/java/io/helidon/config/etcd/EtcdConfigSourceTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,6 +26,8 @@ import java.util.Optional; import java.util.concurrent.atomic.AtomicLong; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.ConfigException; import io.helidon.config.etcd.EtcdConfigSourceBuilder.EtcdApi; @@ -49,9 +51,6 @@ * Tests {@link EtcdConfigSource} with {@link MockEtcdClient}. */ public class EtcdConfigSourceTest { - - static final String MEDIA_TYPE_APPLICATION_HOCON = "application/hocon"; - private static final URI DEFAULT_URI = URI.create("http://localhost:2379"); private EtcdClient etcdClient; @@ -67,7 +66,7 @@ public void testConfigSourceBuilder() { EtcdConfigSource etcdConfigSource = EtcdConfigSource.builder() .key("key") .api(EtcdApi.v2) - .mediaType(MEDIA_TYPE_APPLICATION_HOCON) + .mediaType(MediaTypes.APPLICATION_HOCON) .build(); assertThat(etcdConfigSource, notNullValue()); @@ -80,7 +79,7 @@ public void testBadUri() { .uri(URI.create("http://localhost:1111")) .key("configuration") .api(EtcdApi.v2) - .mediaType(MEDIA_TYPE_APPLICATION_HOCON) + .mediaType(MediaTypes.APPLICATION_HOCON) .build(); etcdConfigSource.load(); @@ -94,7 +93,7 @@ public void testBadKey() { .uri(DEFAULT_URI) .key("non-existing-key-23323423424234") .api(EtcdApi.v2) - .mediaType(MEDIA_TYPE_APPLICATION_HOCON) + .mediaType(MediaTypes.APPLICATION_HOCON) .build(); etcdConfigSource.load(); @@ -109,15 +108,15 @@ public void testConfig() { .uri(DEFAULT_URI) .key("configuration") .api(EtcdApi.v2) - .mediaType(MEDIA_TYPE_APPLICATION_HOCON) + .mediaType(MediaTypes.APPLICATION_HOCON) .build(); EtcdConfigSource mockedConfigSource = spy(configSource); when(mockedConfigSource.etcdClient()).thenReturn(etcdClient); when(mockedConfigSource.load()).thenReturn(Optional.of(new ConfigParser.Content() { @Override - public Optional mediaType() { - return Optional.of(MEDIA_TYPE_APPLICATION_HOCON); + public Optional mediaType() { + return Optional.of(MediaTypes.APPLICATION_HOCON); } @Override diff --git a/config/git/pom.xml b/config/git/pom.xml index c6dab2e96c7..8cf4bc24d37 100644 --- a/config/git/pom.xml +++ b/config/git/pom.xml @@ -78,12 +78,6 @@ org.mockito mockito-core test - - - io.helidon.config - helidon-config-test-infrastructure - ${project.version} - test org.slf4j diff --git a/config/git/src/main/java/io/helidon/config/git/GitConfigSource.java b/config/git/src/main/java/io/helidon/config/git/GitConfigSource.java index 17b3ed01346..efe225dcb43 100644 --- a/config/git/src/main/java/io/helidon/config/git/GitConfigSource.java +++ b/config/git/src/main/java/io/helidon/config/git/GitConfigSource.java @@ -34,6 +34,7 @@ import java.util.logging.Level; import java.util.logging.Logger; +import io.helidon.common.media.type.MediaType; import io.helidon.common.media.type.MediaTypes; import io.helidon.config.AbstractConfigSource; import io.helidon.config.Config; @@ -207,7 +208,7 @@ public Function> relativeResolver() { } @Override - public Optional mediaType() { + public Optional mediaType() { return super.mediaType(); } diff --git a/config/git/src/main/java/io/helidon/config/git/GitConfigSourceBuilder.java b/config/git/src/main/java/io/helidon/config/git/GitConfigSourceBuilder.java index b8572e80b8a..3021beffb4d 100644 --- a/config/git/src/main/java/io/helidon/config/git/GitConfigSourceBuilder.java +++ b/config/git/src/main/java/io/helidon/config/git/GitConfigSourceBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -23,6 +23,7 @@ import java.util.Objects; import io.helidon.common.Builder; +import io.helidon.common.media.type.MediaType; import io.helidon.config.AbstractConfigSourceBuilder; import io.helidon.config.Config; import io.helidon.config.spi.ConfigParser; @@ -145,7 +146,7 @@ public GitConfigSourceBuilder parser(ConfigParser parser) { } @Override - public GitConfigSourceBuilder mediaType(String mediaType) { + public GitConfigSourceBuilder mediaType(MediaType mediaType) { return super.mediaType(mediaType); } diff --git a/config/git/src/test/java/io/helidon/config/git/GitConfigSourceBuilderTest.java b/config/git/src/test/java/io/helidon/config/git/GitConfigSourceBuilderTest.java index 472fd568666..6047d116109 100644 --- a/config/git/src/test/java/io/helidon/config/git/GitConfigSourceBuilderTest.java +++ b/config/git/src/test/java/io/helidon/config/git/GitConfigSourceBuilderTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -25,13 +25,13 @@ import java.util.Map; import java.util.Optional; +import io.helidon.common.testing.junit5.TemporaryFolderExt; import io.helidon.config.Config; import io.helidon.config.ConfigException; import io.helidon.config.ConfigParsers; import io.helidon.config.ConfigSources; import io.helidon.config.MetaConfig; import io.helidon.config.spi.ConfigNode.ObjectNode; -import io.helidon.config.test.infra.TemporaryFolderExt; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.errors.GitAPIException; diff --git a/config/hocon/src/main/java/io/helidon/config/hocon/HoconConfigParser.java b/config/hocon/src/main/java/io/helidon/config/hocon/HoconConfigParser.java index 8f6524e375e..b09abb65678 100644 --- a/config/hocon/src/main/java/io/helidon/config/hocon/HoconConfigParser.java +++ b/config/hocon/src/main/java/io/helidon/config/hocon/HoconConfigParser.java @@ -26,6 +26,8 @@ import io.helidon.common.Weight; import io.helidon.common.Weighted; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.ConfigException; import io.helidon.config.spi.ConfigNode.ListNode; import io.helidon.config.spi.ConfigNode.ObjectNode; @@ -41,8 +43,8 @@ /** * Typesafe (Lightbend) Config (HOCON) {@link ConfigParser} implementation that supports following media types: - * {@value #MEDIA_TYPE_APPLICATION_HOCON} and - * {@value #MEDIA_TYPE_APPLICATION_JSON}. + * {@link io.helidon.common.media.type.MediaTypes#APPLICATION_HOCON} and + * {@link io.helidon.common.media.type.MediaTypes#APPLICATION_JSON}. *

* The parser implementation supports {@link java.util.ServiceLoader}, i.e. {@link io.helidon.config.Config.Builder} * can automatically load and register {@code HoconConfigParser} instance, @@ -58,22 +60,14 @@ @Weight(HoconConfigParser.WEIGHT) public class HoconConfigParser implements ConfigParser { - /** - * A String constant representing {@value} media type. - */ - public static final String MEDIA_TYPE_APPLICATION_HOCON = "application/hocon"; - /** - * A String constant representing {@value} media type. - */ - public static final String MEDIA_TYPE_APPLICATION_JSON = "application/json"; /** * Priority of the parser used if registered by {@link io.helidon.config.Config.Builder} automatically. */ public static final double WEIGHT = Weighted.DEFAULT_WEIGHT - 10; private static final List SUPPORTED_SUFFIXES = List.of("json", "conf"); - private static final Set SUPPORTED_MEDIA_TYPES = - Set.of(MEDIA_TYPE_APPLICATION_HOCON, MEDIA_TYPE_APPLICATION_JSON); + private static final Set SUPPORTED_MEDIA_TYPES = + Set.of(MediaTypes.APPLICATION_HOCON, MediaTypes.APPLICATION_JSON); private final boolean resolvingEnabled; private final ConfigResolveOptions resolveOptions; @@ -122,7 +116,7 @@ public static HoconConfigParserBuilder builder() { } @Override - public Set supportedMediaTypes() { + public Set supportedMediaTypes() { return SUPPORTED_MEDIA_TYPES; } diff --git a/config/hocon/src/main/java/io/helidon/config/hocon/package-info.java b/config/hocon/src/main/java/io/helidon/config/hocon/package-info.java index 51548d11426..c01654392ee 100644 --- a/config/hocon/src/main/java/io/helidon/config/hocon/package-info.java +++ b/config/hocon/src/main/java/io/helidon/config/hocon/package-info.java @@ -17,8 +17,8 @@ /** * HOCON format ConfigParser implementation using Typesafe (Lightbend) Config library. *

- * It supports {@value io.helidon.config.hocon.HoconConfigParser#MEDIA_TYPE_APPLICATION_HOCON} and - * {@value io.helidon.config.hocon.HoconConfigParser#MEDIA_TYPE_APPLICATION_JSON} formats. + * It supports {@link io.helidon.common.media.type.MediaTypes#APPLICATION_HOCON} and + * {@link io.helidon.common.media.type.MediaTypes#APPLICATION_JSON} formats. *

* The parser implementation supports {@link java.util.ServiceLoader}, i.e. {@link io.helidon.config.Config.Builder} * can automatically load and register HOCON ConfigParser instance, diff --git a/config/hocon/src/test/java/io/helidon/config/hocon/HoconConfigParserTest.java b/config/hocon/src/test/java/io/helidon/config/hocon/HoconConfigParserTest.java index 670193c8356..274ed9dd494 100644 --- a/config/hocon/src/test/java/io/helidon/config/hocon/HoconConfigParserTest.java +++ b/config/hocon/src/test/java/io/helidon/config/hocon/HoconConfigParserTest.java @@ -29,6 +29,8 @@ import java.util.function.Function; import java.util.stream.Collectors; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.ConfigMappingException; import io.helidon.config.ConfigSources; @@ -38,6 +40,7 @@ import io.helidon.config.spi.ConfigNode.ObjectNode; import io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.ConfigParser.Content; + import org.junit.jupiter.api.Test; import static io.helidon.config.ConfigValues.simpleValue; @@ -167,7 +170,7 @@ public void testConfigKeyEscapedNameComplex() { + "}\n"; Config config = Config - .builder(ConfigSources.create(JSON, HoconConfigParser.MEDIA_TYPE_APPLICATION_JSON)) + .builder(ConfigSources.create(JSON, MediaTypes.APPLICATION_JSON)) .addParser(HoconConfigParser.create()) .disableEnvironmentVariablesSource() .disableSystemPropertiesSource() @@ -221,7 +224,7 @@ public void testGetSupportedMediaTypes() { @Test public void testCustomTypeMapping() { Config config = Config - .builder(ConfigSources.create(AppType.DEF, HoconConfigParser.MEDIA_TYPE_APPLICATION_JSON)) + .builder(ConfigSources.create(AppType.DEF, MediaTypes.APPLICATION_JSON)) .addParser(HoconConfigParser.create()) .addMapper(AppType.class, new AppTypeMapper()) .disableEnvironmentVariablesSource() @@ -265,8 +268,8 @@ void testParserFromJson() { @FunctionalInterface private interface StringContent extends Content { @Override - default Optional mediaType() { - return Optional.of(HoconConfigParser.MEDIA_TYPE_APPLICATION_HOCON); + default Optional mediaType() { + return Optional.of(MediaTypes.APPLICATION_HOCON); } @Override diff --git a/config/hocon/src/test/java/io/helidon/config/hocon/HoconMediaTypeDetectorTest.java b/config/hocon/src/test/java/io/helidon/config/hocon/HoconMediaTypeDetectorTest.java index f801662f296..c8a42bc0451 100644 --- a/config/hocon/src/test/java/io/helidon/config/hocon/HoconMediaTypeDetectorTest.java +++ b/config/hocon/src/test/java/io/helidon/config/hocon/HoconMediaTypeDetectorTest.java @@ -22,6 +22,8 @@ import org.junit.jupiter.api.Test; +import static io.helidon.common.media.type.MediaTypes.APPLICATION_HOCON; +import static io.helidon.common.media.type.MediaTypes.APPLICATION_JSON; import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -34,11 +36,11 @@ public class HoconMediaTypeDetectorTest { @Test public void testProbeContentTypeHocon() { - assertThat(MediaTypes.detectType(Paths.get("config.conf")), optionalValue(is("application/hocon"))); + assertThat(MediaTypes.detectType(Paths.get("config.conf")), optionalValue(is(APPLICATION_HOCON))); } @Test public void testProbeContentTypeJson() { - assertThat(MediaTypes.detectType(Paths.get("config.json")), optionalValue(is("application/json"))); + assertThat(MediaTypes.detectType(Paths.get("config.json")), optionalValue(is(APPLICATION_JSON))); } } diff --git a/config/metadata-processor/pom.xml b/config/metadata-processor/pom.xml index 3c2331865ba..3de1dd45393 100644 --- a/config/metadata-processor/pom.xml +++ b/config/metadata-processor/pom.xml @@ -36,7 +36,7 @@ diff --git a/config/pom.xml b/config/pom.xml index 4998292e7d2..a6391009ce0 100644 --- a/config/pom.xml +++ b/config/pom.xml @@ -44,7 +44,6 @@ hocon encryption testing - test-infrastructure tests config-mp yaml-mp diff --git a/config/test-infrastructure/pom.xml b/config/test-infrastructure/pom.xml deleted file mode 100644 index 89092e5cbdf..00000000000 --- a/config/test-infrastructure/pom.xml +++ /dev/null @@ -1,43 +0,0 @@ - - - - - 4.0.0 - - io.helidon.config - helidon-config-project - 4.0.0-SNAPSHOT - - helidon-config-test-infrastructure - Helidon Config Test Infrastructure - - - Configuration test infrastructure module - - - - - org.junit.jupiter - junit-jupiter-api - compile - - - diff --git a/config/test-infrastructure/src/main/java/io/helidon/config/test/infra/RestoreSystemPropertiesExt.java b/config/test-infrastructure/src/main/java/io/helidon/config/test/infra/RestoreSystemPropertiesExt.java deleted file mode 100644 index 770d26a6256..00000000000 --- a/config/test-infrastructure/src/main/java/io/helidon/config/test/infra/RestoreSystemPropertiesExt.java +++ /dev/null @@ -1,60 +0,0 @@ -/* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.config.test.infra; - -import java.util.Properties; - -import org.junit.jupiter.api.extension.AfterTestExecutionCallback; -import org.junit.jupiter.api.extension.BeforeTestExecutionCallback; -import org.junit.jupiter.api.extension.ExtensionContext; -import org.junit.jupiter.api.extension.ExtensionContext.Namespace; -import org.junit.jupiter.api.extension.ExtensionContext.Store; - -/** - * JUnit 5 extension for preserving and restoring system properties around - * test executions. - *

- * Annotate each test method that modifies system properties using - * @ExtendWith(RestoreSystemPropertiesExt.class) - * - */ -public class RestoreSystemPropertiesExt implements BeforeTestExecutionCallback, AfterTestExecutionCallback { - - private static final String SYSPROPS_KEY = "systemProps"; - - @Override - public void beforeTestExecution(ExtensionContext ec) throws Exception { - getStore(ec).put(SYSPROPS_KEY, System.getProperties()); - Properties copy = new Properties(); - copy.putAll(System.getProperties()); - System.setProperties(copy); - } - - @Override - public void afterTestExecution(ExtensionContext ec) throws Exception { - System.setProperties(getSavedProps(ec)); - } - - private Store getStore(ExtensionContext context) { - return context.getStore(Namespace.create(getClass(), context.getRequiredTestMethod())); - } - - private Properties getSavedProps(ExtensionContext ec) throws Exception { - Object o = getStore(ec).get(SYSPROPS_KEY); - return Properties.class.cast(o); - } -} diff --git a/config/test-infrastructure/src/main/java/io/helidon/config/test/infra/TemporaryFolderExt.java b/config/test-infrastructure/src/main/java/io/helidon/config/test/infra/TemporaryFolderExt.java deleted file mode 100644 index a2c6a744154..00000000000 --- a/config/test-infrastructure/src/main/java/io/helidon/config/test/infra/TemporaryFolderExt.java +++ /dev/null @@ -1,147 +0,0 @@ -/* - * Copyright (c) 2018, 2020 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.config.test.infra; - -import java.io.File; -import java.io.IOException; -import java.nio.file.FileVisitResult; -import java.nio.file.Files; -import java.nio.file.Path; -import java.nio.file.SimpleFileVisitor; -import java.nio.file.attribute.BasicFileAttributes; - -import org.junit.jupiter.api.extension.AfterEachCallback; -import org.junit.jupiter.api.extension.BeforeEachCallback; -import org.junit.jupiter.api.extension.ExtensionContext; - - -/** - * JUnit 5 extension for temporary folder operations. - *

- * Declare the extension in a test using - *

- * @RegisterExtension - *
static TemporaryFolderExt folder = TemporaryFolderExt.build(); - *
- *

- * The static is important. - *

- * When a test needs a temporary folder it invokes folder.newFolder(). - *

- * The extension automatically deletes the temporary files after all tests in - * the test class have finished. - * - */ -public class TemporaryFolderExt implements BeforeEachCallback, AfterEachCallback { - - private Path root; - - private TemporaryFolderExt() { - } - - /** - * Builds an instance of TemporaryFolderExt. - * @return a TemporaryFolderExt - */ - public static TemporaryFolderExt build() { - return new TemporaryFolderExt(); - } - - /** - * Creates a new temporary folder with a unique generated name. - * @return File for the newly-created temporary folder - * @throws IOException in case of error creating the new folder - */ - public File newFolder() throws IOException { - final Path tempPath = Files.createTempDirectory(root, "test"); - return tempPath.toFile(); - } - - /** - * Creates a new temporary folder with the specified name. - * @param name of the folder to create - * @return File for the new folder - * @throws IOException in case of error creating the new folder - */ - public File newFolder(String name) throws IOException { - int nameStart = (name.startsWith("/") ? 1 : 0); - return Files.createDirectory(root.resolve(name.substring(nameStart))).toFile(); - } - - /** - * Creates a new temporary file with a generated unique name. - * @return the new File - * @throws IOException in case of error creating the new file - */ - public File newFile() throws IOException { - return Files.createTempFile(root, "test", "file").toFile(); - } - - /** - * Creates a new temporary file with the specified name. - * @param name name to be used for the new file - * @return File for the newly-created file - * @throws IOException in case of error creating the new file - */ - public File newFile(String name) throws IOException { - int nameStart = (name.startsWith("/") ? 1 : 0); - return Files.createFile(root.resolve(name.substring(nameStart))).toFile(); - } - - /** - * The root for this test's temporary files. - * @return the root File - */ - public File getRoot() { - return root.toFile(); - } - - @Override - public void beforeEach(ExtensionContext ec) throws Exception { - root = Files.createTempDirectory("test"); - } - @Override - public void afterEach(ExtensionContext ec) throws Exception { - deleteDir(root); - } - - private static void deleteDir(Path dir) throws IOException { - Files.walkFileTree(dir, new SimpleFileVisitor() { - - @Override - public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { - if (!Files.isWritable(path)) { - //When you try to delete the file on Windows and it is marked as read-only - //it would fail unless this change - path.toFile().setWritable(true); - } - Files.delete(path); - return FileVisitResult.CONTINUE; - } - - @Override - public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException { - if (exc != null) { - throw exc; - } else { - Files.delete(dir); - return FileVisitResult.CONTINUE; - } - } - }); - } -} diff --git a/config/test-infrastructure/src/main/java/io/helidon/config/test/infra/package-info.java b/config/test-infrastructure/src/main/java/io/helidon/config/test/infra/package-info.java deleted file mode 100644 index ef6da90c67f..00000000000 --- a/config/test-infrastructure/src/main/java/io/helidon/config/test/infra/package-info.java +++ /dev/null @@ -1,20 +0,0 @@ -/* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Common infrastructure useful in JUnit tests across multiple projects. - */ -package io.helidon.config.test.infra; diff --git a/config/test-infrastructure/src/main/java/module-info.java b/config/test-infrastructure/src/main/java/module-info.java deleted file mode 100644 index b56fc90cdce..00000000000 --- a/config/test-infrastructure/src/main/java/module-info.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2018, 2021 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -/** - * Common infrastructure useful in JUnit tests across multiple projects. - */ -module io.helidon.config.test.infra { - requires org.junit.jupiter.api; - - exports io.helidon.config.test.infra; -} diff --git a/config/testing/src/main/java/io/helidon/config/testing/ValueNodeMatcher.java b/config/testing/src/main/java/io/helidon/config/testing/ValueNodeMatcher.java index 6df269c9eb0..3cddc6ac366 100644 --- a/config/testing/src/main/java/io/helidon/config/testing/ValueNodeMatcher.java +++ b/config/testing/src/main/java/io/helidon/config/testing/ValueNodeMatcher.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,42 +18,56 @@ import io.helidon.config.spi.ConfigNode; -import org.hamcrest.BaseMatcher; import org.hamcrest.Description; +import org.hamcrest.TypeSafeMatcher; /** - * Hamcrest {@link org.hamcrest.Matcher} implementation that matches {@link ConfigNode.ValueNode} value. + * Hamcrest {@link org.hamcrest.Matcher} implementation that matches {@link io.helidon.config.spi.ConfigNode.ValueNode} value. */ -public final class ValueNodeMatcher extends BaseMatcher { +public final class ValueNodeMatcher extends TypeSafeMatcher { - private String expectedValue; + private final String expectedValue; private ValueNodeMatcher(String expectedValue) { this.expectedValue = expectedValue; } + /** + * Creates new instance of {@link io.helidon.config.testing.ValueNodeMatcher} that matches + * {@link io.helidon.config.spi.ConfigNode.ValueNode} + * with spacified {@code expectedValue}. + * + * @param expectedValue expected value holded by {@link io.helidon.config.spi.ConfigNode.ValueNode} + * @return new instance of {@link io.helidon.config.testing.ValueNodeMatcher} + */ + public static ValueNodeMatcher valueNode(String expectedValue) { + return new ValueNodeMatcher(expectedValue); + } + @Override public void describeTo(Description description) { - description.appendValue(this.expectedValue); + description.appendText("ValueNode with value ").appendValue(this.expectedValue); } @Override - public boolean matches(Object actualValue) { - if (actualValue instanceof ConfigNode.ValueNode) { - return expectedValue.equals(((ConfigNode.ValueNode) actualValue).get()); + public boolean matchesSafely(ConfigNode actualValue) { + if (actualValue instanceof ConfigNode.ValueNode valueNode) { + return expectedValue.equals(valueNode.get()); } return false; } - /** - * Creates new instance of {@link ValueNodeMatcher} that matches {@link ConfigNode.ValueNode} - * with spacified {@code expectedValue}. - * - * @param expectedValue expected value holded by {@link ConfigNode.ValueNode} - * @return new instance of {@link ValueNodeMatcher} - */ - public static ValueNodeMatcher valueNode(String expectedValue) { - return new ValueNodeMatcher(expectedValue); + @Override + public void describeMismatchSafely(ConfigNode item, Description description) { + if (item instanceof ConfigNode.ValueNode valueNode) { + description.appendText("got ") + .appendValue(valueNode.get()); + } else { + description.appendText("got ") + .appendValue(item.getClass().getName()) + .appendText(" ") + .appendValue(item); + } } } diff --git a/config/tests/module-parsers-1-override/src/main/java/io/helidon/config/tests/module/parsers1/AbstractParsers1ConfigParser.java b/config/tests/module-parsers-1-override/src/main/java/io/helidon/config/tests/module/parsers1/AbstractParsers1ConfigParser.java index 8f0ce921b87..5ca034f3678 100644 --- a/config/tests/module-parsers-1-override/src/main/java/io/helidon/config/tests/module/parsers1/AbstractParsers1ConfigParser.java +++ b/config/tests/module-parsers-1-override/src/main/java/io/helidon/config/tests/module/parsers1/AbstractParsers1ConfigParser.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -21,6 +21,8 @@ import java.util.Properties; import java.util.Set; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ConfigNode; import io.helidon.config.spi.ConfigNode.ValueNode; import io.helidon.config.spi.ConfigParser; @@ -31,10 +33,10 @@ */ public abstract class AbstractParsers1ConfigParser implements ConfigParser { - private static final String MEDIA_TYPE_TEXT_JAVA_PROPERTIES = "text/x-java-properties"; + private static final MediaType MEDIA_TYPE_TEXT_JAVA_PROPERTIES = MediaTypes.create("text/x-java-properties"); @Override - public Set supportedMediaTypes() { + public Set supportedMediaTypes() { return Set.of(MEDIA_TYPE_TEXT_JAVA_PROPERTIES); } diff --git a/config/tests/test-bundle/src/test/java/io/helidon/config/tests/bundle/SmokeTest.java b/config/tests/test-bundle/src/test/java/io/helidon/config/tests/bundle/SmokeTest.java index 42fb977c644..1510b6b457a 100644 --- a/config/tests/test-bundle/src/test/java/io/helidon/config/tests/bundle/SmokeTest.java +++ b/config/tests/test-bundle/src/test/java/io/helidon/config/tests/bundle/SmokeTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.helidon.config.tests.bundle; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.ConfigSources; import io.helidon.config.ConfigValues; @@ -36,7 +37,7 @@ public class SmokeTest { @Test public void testPropertiesParser() { Config config = Config.builder() - .sources(ConfigSources.create("key=value", "text/x-java-properties")) + .sources(ConfigSources.create("key=value", MediaTypes.create("text/x-java-properties"))) .build(); assertThat(config.get("key").asString(), is(ConfigValues.simpleValue("value"))); } diff --git a/config/tests/test-default_config-1-properties/pom.xml b/config/tests/test-default_config-1-properties/pom.xml index c116bad4d7c..ff4893b011c 100644 --- a/config/tests/test-default_config-1-properties/pom.xml +++ b/config/tests/test-default_config-1-properties/pom.xml @@ -38,6 +38,11 @@ io.helidon.config helidon-config + + io.helidon.common.testing + helidon-common-testing-junit5 + test + org.junit.jupiter junit-jupiter-api @@ -48,11 +53,5 @@ hamcrest-all test - - io.helidon.config - helidon-config-test-infrastructure - ${project.version} - test - diff --git a/config/tests/test-default_config-1-properties/src/test/java/io/helidon/config/tests/default1/ConfigCreateDefaultFromPropertiesTest.java b/config/tests/test-default_config-1-properties/src/test/java/io/helidon/config/tests/default1/ConfigCreateDefaultFromPropertiesTest.java index 62c5f011bbe..f91fb28cec4 100644 --- a/config/tests/test-default_config-1-properties/src/test/java/io/helidon/config/tests/default1/ConfigCreateDefaultFromPropertiesTest.java +++ b/config/tests/test-default_config-1-properties/src/test/java/io/helidon/config/tests/default1/ConfigCreateDefaultFromPropertiesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package io.helidon.config.tests.default1; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.Config; import io.helidon.config.ConfigValues; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/tests/test-default_config-2-hocon-json/pom.xml b/config/tests/test-default_config-2-hocon-json/pom.xml index 68bf47d2ea8..12112f08339 100644 --- a/config/tests/test-default_config-2-hocon-json/pom.xml +++ b/config/tests/test-default_config-2-hocon-json/pom.xml @@ -42,6 +42,11 @@ io.helidon.config helidon-config-hocon + + io.helidon.common.testing + helidon-common-testing-junit5 + test + org.junit.jupiter junit-jupiter-api @@ -51,12 +56,6 @@ hamcrest-all test - - io.helidon.config - helidon-config-test-infrastructure - ${project.version} - test - jakarta.annotation jakarta.annotation-api diff --git a/config/tests/test-default_config-2-hocon-json/src/test/java/io/helidon/config/tests/default2/ConfigCreateDefaultFromJsonTest.java b/config/tests/test-default_config-2-hocon-json/src/test/java/io/helidon/config/tests/default2/ConfigCreateDefaultFromJsonTest.java index 885e410041e..98237e13e79 100644 --- a/config/tests/test-default_config-2-hocon-json/src/test/java/io/helidon/config/tests/default2/ConfigCreateDefaultFromJsonTest.java +++ b/config/tests/test-default_config-2-hocon-json/src/test/java/io/helidon/config/tests/default2/ConfigCreateDefaultFromJsonTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package io.helidon.config.tests.default2; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.Config; import io.helidon.config.ConfigValues; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/tests/test-default_config-3-hocon/pom.xml b/config/tests/test-default_config-3-hocon/pom.xml index 3172745bbbd..ef7d9b7b6b6 100644 --- a/config/tests/test-default_config-3-hocon/pom.xml +++ b/config/tests/test-default_config-3-hocon/pom.xml @@ -42,6 +42,11 @@ io.helidon.config helidon-config-hocon + + io.helidon.common.testing + helidon-common-testing-junit5 + test + org.junit.jupiter junit-jupiter-api @@ -62,11 +67,5 @@ hamcrest-all test - - io.helidon.config - helidon-config-test-infrastructure - ${project.version} - test - diff --git a/config/tests/test-default_config-3-hocon/src/test/java/io/helidon/config/tests/default3/ConfigCreateDefaultFromHoconTest.java b/config/tests/test-default_config-3-hocon/src/test/java/io/helidon/config/tests/default3/ConfigCreateDefaultFromHoconTest.java index 01fec863553..779854eaf0a 100644 --- a/config/tests/test-default_config-3-hocon/src/test/java/io/helidon/config/tests/default3/ConfigCreateDefaultFromHoconTest.java +++ b/config/tests/test-default_config-3-hocon/src/test/java/io/helidon/config/tests/default3/ConfigCreateDefaultFromHoconTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package io.helidon.config.tests.default3; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.Config; import io.helidon.config.ConfigValues; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/tests/test-default_config-4-yaml/pom.xml b/config/tests/test-default_config-4-yaml/pom.xml index c7411f4fbed..ddef51b3056 100644 --- a/config/tests/test-default_config-4-yaml/pom.xml +++ b/config/tests/test-default_config-4-yaml/pom.xml @@ -46,6 +46,11 @@ io.helidon.config helidon-config-yaml + + io.helidon.common.testing + helidon-common-testing-junit5 + test + org.hamcrest hamcrest-all @@ -56,12 +61,6 @@ junit-jupiter-api test - - io.helidon.config - helidon-config-test-infrastructure - ${project.version} - test - com.typesafe config diff --git a/config/tests/test-default_config-4-yaml/src/test/java/io/helidon/config/tests/default4/ConfigCreateDefaultFromYamlTest.java b/config/tests/test-default_config-4-yaml/src/test/java/io/helidon/config/tests/default4/ConfigCreateDefaultFromYamlTest.java index f8090eff92c..0ca5c7ff1e2 100644 --- a/config/tests/test-default_config-4-yaml/src/test/java/io/helidon/config/tests/default4/ConfigCreateDefaultFromYamlTest.java +++ b/config/tests/test-default_config-4-yaml/src/test/java/io/helidon/config/tests/default4/ConfigCreateDefaultFromYamlTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package io.helidon.config.tests.default4; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.Config; import io.helidon.config.ConfigValues; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/tests/test-default_config-5-env_vars/pom.xml b/config/tests/test-default_config-5-env_vars/pom.xml index e6c5b35e16d..1309084e04a 100644 --- a/config/tests/test-default_config-5-env_vars/pom.xml +++ b/config/tests/test-default_config-5-env_vars/pom.xml @@ -47,14 +47,13 @@ helidon-config-yaml - org.junit.jupiter - junit-jupiter-api + io.helidon.common.testing + helidon-common-testing-junit5 test - io.helidon.config - helidon-config-test-infrastructure - ${project.version} + org.junit.jupiter + junit-jupiter-api test diff --git a/config/tests/test-default_config-5-env_vars/src/test/java/io/helidon/config/tests/default5/ConfigCreateDefaultFromEnvVarsTest.java b/config/tests/test-default_config-5-env_vars/src/test/java/io/helidon/config/tests/default5/ConfigCreateDefaultFromEnvVarsTest.java index b755dfd67e7..8ec12e67ef5 100644 --- a/config/tests/test-default_config-5-env_vars/src/test/java/io/helidon/config/tests/default5/ConfigCreateDefaultFromEnvVarsTest.java +++ b/config/tests/test-default_config-5-env_vars/src/test/java/io/helidon/config/tests/default5/ConfigCreateDefaultFromEnvVarsTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,8 +16,8 @@ package io.helidon.config.tests.default5; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.Config; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/tests/test-default_config-6-meta-properties/pom.xml b/config/tests/test-default_config-6-meta-properties/pom.xml index 95011c0cce6..669717d07b5 100644 --- a/config/tests/test-default_config-6-meta-properties/pom.xml +++ b/config/tests/test-default_config-6-meta-properties/pom.xml @@ -38,6 +38,11 @@ io.helidon.config helidon-config + + io.helidon.common.testing + helidon-common-testing-junit5 + test + org.junit.jupiter junit-jupiter-api @@ -48,11 +53,5 @@ hamcrest-all test - - io.helidon.config - helidon-config-test-infrastructure - ${project.version} - test - diff --git a/config/tests/test-default_config-6-meta-properties/src/test/java/io/helidon/config/tests/default6/ConfigCreateDefaultFromMetaPropertiesTest.java b/config/tests/test-default_config-6-meta-properties/src/test/java/io/helidon/config/tests/default6/ConfigCreateDefaultFromMetaPropertiesTest.java index 2558d888480..0b3a6e7c0d7 100644 --- a/config/tests/test-default_config-6-meta-properties/src/test/java/io/helidon/config/tests/default6/ConfigCreateDefaultFromMetaPropertiesTest.java +++ b/config/tests/test-default_config-6-meta-properties/src/test/java/io/helidon/config/tests/default6/ConfigCreateDefaultFromMetaPropertiesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package io.helidon.config.tests.default6; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.Config; import io.helidon.config.ConfigValues; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/tests/test-default_config-7-meta-hocon-json/pom.xml b/config/tests/test-default_config-7-meta-hocon-json/pom.xml index abb74ecb729..5ac877118ba 100644 --- a/config/tests/test-default_config-7-meta-hocon-json/pom.xml +++ b/config/tests/test-default_config-7-meta-hocon-json/pom.xml @@ -42,6 +42,11 @@ io.helidon.config helidon-config-hocon + + io.helidon.common.testing + helidon-common-testing-junit5 + test + org.junit.jupiter junit-jupiter-api @@ -56,12 +61,6 @@ org.hamcrest hamcrest-all test - - - io.helidon.config - helidon-config-test-infrastructure - ${project.version} - test jakarta.annotation diff --git a/config/tests/test-default_config-7-meta-hocon-json/src/test/java/io/helidon/config/tests/default7/ConfigCreateDefaultFromMetaJsonTest.java b/config/tests/test-default_config-7-meta-hocon-json/src/test/java/io/helidon/config/tests/default7/ConfigCreateDefaultFromMetaJsonTest.java index 6641d1b8e48..4f979c44ba5 100644 --- a/config/tests/test-default_config-7-meta-hocon-json/src/test/java/io/helidon/config/tests/default7/ConfigCreateDefaultFromMetaJsonTest.java +++ b/config/tests/test-default_config-7-meta-hocon-json/src/test/java/io/helidon/config/tests/default7/ConfigCreateDefaultFromMetaJsonTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package io.helidon.config.tests.default7; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.Config; import io.helidon.config.ConfigValues; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/tests/test-default_config-8-meta-hocon/pom.xml b/config/tests/test-default_config-8-meta-hocon/pom.xml index 8233f0595a9..cbc2de3cfed 100644 --- a/config/tests/test-default_config-8-meta-hocon/pom.xml +++ b/config/tests/test-default_config-8-meta-hocon/pom.xml @@ -43,14 +43,13 @@ helidon-config-hocon - org.junit.jupiter - junit-jupiter-api + io.helidon.common.testing + helidon-common-testing-junit5 test - io.helidon.config - helidon-config-test-infrastructure - ${project.version} + org.junit.jupiter + junit-jupiter-api test diff --git a/config/tests/test-default_config-8-meta-hocon/src/test/java/io/helidon/config/tests/default8/ConfigCreateDefaultFromMetaHoconTest.java b/config/tests/test-default_config-8-meta-hocon/src/test/java/io/helidon/config/tests/default8/ConfigCreateDefaultFromMetaHoconTest.java index a528b57c73e..38fe522dfad 100644 --- a/config/tests/test-default_config-8-meta-hocon/src/test/java/io/helidon/config/tests/default8/ConfigCreateDefaultFromMetaHoconTest.java +++ b/config/tests/test-default_config-8-meta-hocon/src/test/java/io/helidon/config/tests/default8/ConfigCreateDefaultFromMetaHoconTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package io.helidon.config.tests.default8; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.Config; import io.helidon.config.ConfigValues; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/tests/test-default_config-9-meta-yaml/pom.xml b/config/tests/test-default_config-9-meta-yaml/pom.xml index ec60da975b8..571a99abe20 100644 --- a/config/tests/test-default_config-9-meta-yaml/pom.xml +++ b/config/tests/test-default_config-9-meta-yaml/pom.xml @@ -46,6 +46,11 @@ io.helidon.config helidon-config-yaml + + io.helidon.common.testing + helidon-common-testing-junit5 + test + org.junit.jupiter junit-jupiter-api @@ -61,12 +66,6 @@ config test - - io.helidon.config - helidon-config-test-infrastructure - ${project.version} - test - jakarta.annotation jakarta.annotation-api diff --git a/config/tests/test-default_config-9-meta-yaml/src/test/java/io/helidon/config/tests/default9/ConfigCreateDefaultFromMetaYamlTest.java b/config/tests/test-default_config-9-meta-yaml/src/test/java/io/helidon/config/tests/default9/ConfigCreateDefaultFromMetaYamlTest.java index f7c924ae201..7704c865484 100644 --- a/config/tests/test-default_config-9-meta-yaml/src/test/java/io/helidon/config/tests/default9/ConfigCreateDefaultFromMetaYamlTest.java +++ b/config/tests/test-default_config-9-meta-yaml/src/test/java/io/helidon/config/tests/default9/ConfigCreateDefaultFromMetaYamlTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,9 +16,9 @@ package io.helidon.config.tests.default9; +import io.helidon.common.testing.junit5.RestoreSystemPropertiesExt; import io.helidon.config.Config; import io.helidon.config.ConfigValues; -import io.helidon.config.test.infra.RestoreSystemPropertiesExt; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; diff --git a/config/tests/test-parsers-1-complex/src/test/java/io/helidon/config/tests/parsers1/AbstractParserServicesTest.java b/config/tests/test-parsers-1-complex/src/test/java/io/helidon/config/tests/parsers1/AbstractParserServicesTest.java index 3fe072aa6f3..976082775a6 100644 --- a/config/tests/test-parsers-1-complex/src/test/java/io/helidon/config/tests/parsers1/AbstractParserServicesTest.java +++ b/config/tests/test-parsers-1-complex/src/test/java/io/helidon/config/tests/parsers1/AbstractParserServicesTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2021 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package io.helidon.config.tests.parsers1; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.config.ConfigSources; @@ -29,7 +30,7 @@ public abstract class AbstractParserServicesTest { protected Config.Builder configBuilder() { return Config.builder() - .sources(ConfigSources.create(KEY + "=" + VALUE, "text/x-java-properties")); + .sources(ConfigSources.create(KEY + "=" + VALUE, MediaTypes.create("text/x-java-properties"))); } } diff --git a/config/yaml/src/main/java/io/helidon/config/yaml/YamlConfigParser.java b/config/yaml/src/main/java/io/helidon/config/yaml/YamlConfigParser.java index 793379d79a8..686e18eaf81 100644 --- a/config/yaml/src/main/java/io/helidon/config/yaml/YamlConfigParser.java +++ b/config/yaml/src/main/java/io/helidon/config/yaml/YamlConfigParser.java @@ -24,6 +24,8 @@ import io.helidon.common.Weight; import io.helidon.common.Weighted; +import io.helidon.common.media.type.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.ConfigException; import io.helidon.config.spi.ConfigNode.ListNode; import io.helidon.config.spi.ConfigNode.ObjectNode; @@ -34,7 +36,7 @@ import org.yaml.snakeyaml.constructor.SafeConstructor; /** - * YAML {@link ConfigParser} implementation that supports {@value #MEDIA_TYPE_APPLICATION_YAML}. + * YAML {@link ConfigParser} implementation that supports {@link io.helidon.common.media.type.MediaTypes#APPLICATION_YAML}. *

* The parser implementation supports {@link java.util.ServiceLoader}, i.e. {@link io.helidon.config.Config.Builder} * can automatically load and register {@code YamlConfigParser} instance, @@ -50,16 +52,13 @@ @Weight(YamlConfigParser.WEIGHT) public class YamlConfigParser implements ConfigParser { - /** - * A String constant representing {@value} media type. - */ - public static final String MEDIA_TYPE_APPLICATION_YAML = "application/x-yaml"; /** * Priority of the parser used if registered by {@link io.helidon.config.Config.Builder} automatically. */ public static final double WEIGHT = Weighted.DEFAULT_WEIGHT - 10; - private static final Set SUPPORTED_MEDIA_TYPES = Set.of(MEDIA_TYPE_APPLICATION_YAML); + private static final Set SUPPORTED_MEDIA_TYPES = Set.of(MediaTypes.APPLICATION_YAML, + MediaTypes.APPLICATION_X_YAML); private static final List SUPPORTED_SUFFIXES = List.of("yml", "yaml"); /** @@ -86,7 +85,7 @@ public static YamlConfigParser create() { } @Override - public Set supportedMediaTypes() { + public Set supportedMediaTypes() { return SUPPORTED_MEDIA_TYPES; } diff --git a/config/yaml/src/main/java/io/helidon/config/yaml/package-info.java b/config/yaml/src/main/java/io/helidon/config/yaml/package-info.java index dea4dd7ce5c..7de7888212f 100644 --- a/config/yaml/src/main/java/io/helidon/config/yaml/package-info.java +++ b/config/yaml/src/main/java/io/helidon/config/yaml/package-info.java @@ -17,7 +17,8 @@ /** * YAML format ConfigParser implementation. *

- * It supports {@value io.helidon.config.yaml.YamlConfigParser#MEDIA_TYPE_APPLICATION_YAML} format. + * It supports {@link io.helidon.common.media.type.MediaTypes#APPLICATION_YAML} + * adn {@link io.helidon.common.media.type.MediaTypes#APPLICATION_X_YAML} media types. *

* The parser implementation supports {@link java.util.ServiceLoader}, i.e. {@link io.helidon.config.Config.Builder} * can automatically load and register YAML ConfigParser instance, diff --git a/config/yaml/src/test/java/io/helidon/config/yaml/YamlConfigParserTest.java b/config/yaml/src/test/java/io/helidon/config/yaml/YamlConfigParserTest.java index a5f1008bb32..4f629afe83f 100644 --- a/config/yaml/src/test/java/io/helidon/config/yaml/YamlConfigParserTest.java +++ b/config/yaml/src/test/java/io/helidon/config/yaml/YamlConfigParserTest.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2017, 2020 Oracle and/or its affiliates. + * Copyright (c) 2017, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -20,6 +20,7 @@ import java.nio.charset.StandardCharsets; import java.util.List; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.spi.ConfigNode; import io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.ConfigParser.Content; @@ -31,7 +32,7 @@ import static org.hamcrest.Matchers.hasSize; /** - * Tests {@link ConfigParser}. + * Tests {@link io.helidon.config.spi.ConfigParser}. */ public class YamlConfigParserTest { @@ -103,7 +104,7 @@ public void testStringListValue() { private Content toContent(String yaml) { return Content.builder() .data(new ByteArrayInputStream(yaml.getBytes(StandardCharsets.UTF_8))) - .mediaType(YamlConfigParser.MEDIA_TYPE_APPLICATION_YAML) + .mediaType(MediaTypes.APPLICATION_X_YAML) .build(); } } diff --git a/config/yaml/src/test/java/io/helidon/config/yaml/YamlMediaTypeDetectorTest.java b/config/yaml/src/test/java/io/helidon/config/yaml/YamlMediaTypeDetectorTest.java deleted file mode 100644 index caeb1f5708b..00000000000 --- a/config/yaml/src/test/java/io/helidon/config/yaml/YamlMediaTypeDetectorTest.java +++ /dev/null @@ -1,39 +0,0 @@ -/* - * Copyright (c) 2020, 2022 Oracle and/or its affiliates. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.helidon.config.yaml; - -import java.nio.file.Paths; - -import io.helidon.common.media.type.MediaTypes; - -import org.junit.jupiter.api.Test; - -import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; -import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.is; - -/** - * Test left in to make sure we correctly evaluate yaml file type suffix. - */ -public class YamlMediaTypeDetectorTest { - - @Test - public void testProbeContentType() { - assertThat(MediaTypes.detectType(Paths.get("config.yaml")), optionalValue(is("application/x-yaml"))); - } - -} From 6a3ec8e53fed2d69b3ebe30b7fa67f4c0c5908c3 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 11 Aug 2022 03:25:11 +0200 Subject: [PATCH 42/54] Build fixes (tests, copyright, checkstyle, spotbugs) --- CHANGELOG.md | 6 +- bom/pom.xml | 5 + .../common/http/HeadersServerRequest.java | 19 +- .../java/io/helidon/common/pki/PkiUtil.java | 1 - .../src/main/java/module-info.java | 4 +- .../io/helidon/common/uri/UriQueryImpl.java | 2 +- .../io/helidon/health/HealthServerTest.java | 3 +- .../extension/HelidonReflectionFeature.java | 2 +- .../common/MessageBodyWriterContext.java | 4 +- .../jackson/JacksonEsBodyStreamWriter.java | 2 +- .../jackson/JacksonNdBodyStreamWriter.java | 2 +- .../media/jsonp/JsonpEsBodyStreamWriter.java | 2 +- .../java/io/helidon/metrics/TestServer.java | 6 +- microprofile/cdi/pom.xml | 4 - .../cdi/HelidonContainerImpl.java | 4 +- .../cdi/src/main/java/module-info.java | 1 - microprofile/config/pom.xml | 6 - .../microprofile/openapi/BasicServerTest.java | 2 +- .../io/helidon/openapi/OpenAPISupport.java | 16 +- .../java/io/helidon/openapi/TestUtil.java | 2 +- pom.xml | 2 +- .../webserver/SecurityHandler.java | 3 +- .../webclient/tracing/WebClientTracing.java | 8 +- webclient/webclient/pom.xml | 2 +- .../webclient/src/main/java/module-info.java | 1 - .../ClientRequestHeadersImplTest.java | 4 +- .../staticcontent/StaticContentSupport.java | 3 +- .../ClassPathContentHandlerTest.java | 16 +- .../FileSystemContentHandlerTest.java | 12 +- webserver/webserver/pom.xml | 6 +- .../helidon/webserver/ForwardingHandler.java | 4 +- .../webserver/NettyRequestHeaders.java | 2 - .../io/helidon/webserver/NettyWebServer.java | 4 +- .../java/io/helidon/webserver/Response.java | 329 ++++++++++-------- .../webserver/src/main/java/module-info.java | 1 - .../webserver/BytesReuseV2ApiTest.java | 2 +- .../java/io/helidon/webserver/Gh1893Test.java | 18 +- .../io/helidon/webserver/Gh1893V2ApiTest.java | 4 +- .../helidon/webserver/KeepAliveV2ApiTest.java | 5 +- .../io/helidon/webserver/MultiPortTest.java | 4 +- .../io/helidon/webserver/PlainV2ApiTest.java | 6 +- .../io/helidon/webserver/ResponseTest.java | 4 +- .../webserver/ServerConfigurationTest.java | 2 +- .../webserver/websocket/WebSocketHandler.java | 9 +- .../webserver/websocket/test/WSTest.java | 9 +- 45 files changed, 307 insertions(+), 246 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5905d92e2d4..82f0d6faf88 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,4 +25,8 @@ We are pleased to announce Helidon 4.0.0 a major release that includes significa - moved priority related types to MP config (as that is the lowest level MP module) - replaces all instances in SE that use priority with weight (no dependency on Jakarta, predictible and easy to understand behavior) - Introduction of `MediaType` as the abstraction of any media type, as used by Config, static content and HTTP in general. See `MediaType` and `MediaTypes` -- `MapperManager` now supports mapping qualifiers \ No newline at end of file +- `MapperManager` now supports mapping qualifiers +- new `helidon-common-parameters` module contains an abstraction of a container that has named values (one or more); this is used in path parameters, query parameters, form parameters etc. +- new `helidon-common-uri` module contains URI abstraction (path with possible parameters, query, and fragment) +- Header processing now uses `HeaderName` and `HeaderValue` types. This allows you to prepare constants with custom names and values that + are often reused. It also allows us to improve parsing speed of HTTP requests. \ No newline at end of file diff --git a/bom/pom.xml b/bom/pom.xml index 846960dce10..7f9ffddaf93 100644 --- a/bom/pom.xml +++ b/bom/pom.xml @@ -656,6 +656,11 @@ helidon-common-testing-junit5 ${helidon.version} + + io.helidon.common.testing + helidon-common-testing-http-junit5 + ${helidon.version} + diff --git a/common/http/src/main/java/io/helidon/common/http/HeadersServerRequest.java b/common/http/src/main/java/io/helidon/common/http/HeadersServerRequest.java index 8c941a66039..aa36e949aba 100644 --- a/common/http/src/main/java/io/helidon/common/http/HeadersServerRequest.java +++ b/common/http/src/main/java/io/helidon/common/http/HeadersServerRequest.java @@ -132,14 +132,23 @@ default Optional bestAccepted(MediaType... mediaTypes) { if (accepted.isEmpty()) { return Optional.of(mediaTypes[0]); } - for (HttpMediaType acceptedType : accepted) { - for (MediaType mediaType : mediaTypes) { - if (acceptedType.test(mediaType)) { - return Optional.of(mediaType); + double best = 0; + MediaType result = null; + for (MediaType mt : mediaTypes) { + for (HttpMediaType acc : accepted) { + double q = acc.qualityFactor(); + if (q > best && acc.test(mt)) { + if (q == 1) { + return Optional.of(mt); + } else { + best = q; + result = mt; + } } } } - return Optional.empty(); + + return Optional.ofNullable(result); } /** diff --git a/common/key-util/src/main/java/io/helidon/common/pki/PkiUtil.java b/common/key-util/src/main/java/io/helidon/common/pki/PkiUtil.java index 3cfe8f54227..cad9da77810 100644 --- a/common/key-util/src/main/java/io/helidon/common/pki/PkiUtil.java +++ b/common/key-util/src/main/java/io/helidon/common/pki/PkiUtil.java @@ -17,7 +17,6 @@ package io.helidon.common.pki; import java.io.InputStream; -import java.lang.System.Logger.Level; import java.security.Key; import java.security.KeyStore; import java.security.KeyStoreException; diff --git a/common/testing/http-junit5/src/main/java/module-info.java b/common/testing/http-junit5/src/main/java/module-info.java index 803d76c8e9a..360c563f7d7 100644 --- a/common/testing/http-junit5/src/main/java/module-info.java +++ b/common/testing/http-junit5/src/main/java/module-info.java @@ -17,8 +17,8 @@ /** * Hamcrest matchers for HTTP. */ -module io.helidon.common.testing.http { - requires transitive io.helidon.common.testing; +module io.helidon.common.testing.http.junit5 { + requires transitive io.helidon.common.testing.junit5; requires io.helidon.common.http; requires hamcrest.all; requires org.junit.jupiter.api; diff --git a/common/uri/src/main/java/io/helidon/common/uri/UriQueryImpl.java b/common/uri/src/main/java/io/helidon/common/uri/UriQueryImpl.java index ae916047efa..e402b477929 100644 --- a/common/uri/src/main/java/io/helidon/common/uri/UriQueryImpl.java +++ b/common/uri/src/main/java/io/helidon/common/uri/UriQueryImpl.java @@ -136,7 +136,7 @@ private void ensureDecoded() { if (decodedQueryParams == null) { Map> newQueryParams = new HashMap<>(); - if (query == null) { + if (query == null || query.isEmpty()) { decodedQueryParams = newQueryParams; return; } diff --git a/health/health/src/test/java/io/helidon/health/HealthServerTest.java b/health/health/src/test/java/io/helidon/health/HealthServerTest.java index 31a6aeeed03..c008fcef3fd 100644 --- a/health/health/src/test/java/io/helidon/health/HealthServerTest.java +++ b/health/health/src/test/java/io/helidon/health/HealthServerTest.java @@ -25,6 +25,7 @@ import io.helidon.common.http.Http; import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.media.common.MessageBodyReadableContent; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; @@ -120,7 +121,7 @@ void testCacheSuppression(String pathSuffix) { try { response = webClient.get() .path(requestPath) - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .request() .await(); assertThat("Header cache settings for /health" + pathSuffix + ": " + response.headers().toMap(), diff --git a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java index 3bb1a0abd25..d7d3b368ea8 100644 --- a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java +++ b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java @@ -34,7 +34,7 @@ import io.helidon.common.LogConfig; import io.helidon.common.Reflected; -import io.helidon.common.features.HelidonFeatures; +import io.helidon.common.HelidonFeatures; import io.helidon.config.mp.MpConfigProviderResolver; import com.oracle.svm.core.jdk.Resources; diff --git a/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java b/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java index 65a0465ad3d..d8911cc52e4 100644 --- a/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java +++ b/media/common/src/main/java/io/helidon/media/common/MessageBodyWriterContext.java @@ -530,7 +530,9 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont } HttpMediaType ct = context.contentType().orElse(null); if (!(contentType != null && ct != null && !ct.test(contentType))) { - context.contentType(contentType); + if (contentType != null) { + context.contentType(contentType); + } return PredicateResult.SUPPORTED; } return PredicateResult.NOT_SUPPORTED; diff --git a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonEsBodyStreamWriter.java b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonEsBodyStreamWriter.java index f3f6cbb9be0..8d9761b6ad5 100644 --- a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonEsBodyStreamWriter.java +++ b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonEsBodyStreamWriter.java @@ -60,7 +60,7 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont } return context.contentType() .or(() -> findMediaType(context)) - .filter(mediaType -> mediaType.equals(TEXT_EVENT_STREAM_JSON) || mediaType.equals(MediaTypes.TEXT_EVENT_STREAM)) + .filter(mediaType -> mediaType.test(TEXT_EVENT_STREAM_JSON) || mediaType.test(MediaTypes.TEXT_EVENT_STREAM)) .map(it -> PredicateResult.COMPATIBLE) .orElse(PredicateResult.NOT_SUPPORTED); } diff --git a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonNdBodyStreamWriter.java b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonNdBodyStreamWriter.java index 1196b3e331c..9ff2caef27f 100644 --- a/media/jackson/src/main/java/io/helidon/media/jackson/JacksonNdBodyStreamWriter.java +++ b/media/jackson/src/main/java/io/helidon/media/jackson/JacksonNdBodyStreamWriter.java @@ -58,7 +58,7 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont } return context.contentType() .or(() -> findMediaType(context)) - .filter(mediaType -> mediaType.equals(MediaTypes.APPLICATION_X_NDJSON)) + .filter(mediaType -> mediaType.test(MediaTypes.APPLICATION_X_NDJSON)) .map(it -> PredicateResult.COMPATIBLE) .orElse(PredicateResult.NOT_SUPPORTED); } diff --git a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpEsBodyStreamWriter.java b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpEsBodyStreamWriter.java index fba2b5a771c..220bec7a83d 100644 --- a/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpEsBodyStreamWriter.java +++ b/media/jsonp/src/main/java/io/helidon/media/jsonp/JsonpEsBodyStreamWriter.java @@ -57,7 +57,7 @@ public PredicateResult accept(GenericType type, MessageBodyWriterContext cont } return context.contentType() .or(() -> findMediaType(context)) - .filter(mediaType -> mediaType.equals(TEXT_EVENT_STREAM_JSON) || mediaType.equals(MediaTypes.TEXT_EVENT_STREAM)) + .filter(mediaType -> mediaType.test(TEXT_EVENT_STREAM_JSON) || mediaType.test(MediaTypes.TEXT_EVENT_STREAM)) .map(it -> PredicateResult.COMPATIBLE) .orElse(PredicateResult.NOT_SUPPORTED); } diff --git a/metrics/metrics/src/test/java/io/helidon/metrics/TestServer.java b/metrics/metrics/src/test/java/io/helidon/metrics/TestServer.java index 7cce1625b3d..c99dafd79b4 100644 --- a/metrics/metrics/src/test/java/io/helidon/metrics/TestServer.java +++ b/metrics/metrics/src/test/java/io/helidon/metrics/TestServer.java @@ -23,6 +23,7 @@ import java.util.logging.Level; import java.util.logging.Logger; +import io.helidon.common.http.Http; import io.helidon.common.media.type.MediaTypes; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.webclient.WebClient; @@ -47,7 +48,6 @@ import static org.hamcrest.Matchers.greaterThan; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.is; -import static org.hamcrest.Matchers.notNullValue; import static org.hamcrest.Matchers.not; public class TestServer { @@ -229,7 +229,7 @@ void testCacheSuppression(String pathSuffix) { WebClientResponse response = webClientBuilder .build() .get() - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path(requestPath) .submit() .await(); @@ -241,7 +241,7 @@ void testCacheSuppression(String pathSuffix) { response = webClientBuilder .build() .options() - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .path(requestPath) .submit() .await(); diff --git a/microprofile/cdi/pom.xml b/microprofile/cdi/pom.xml index 1438f581919..9702b6804fe 100644 --- a/microprofile/cdi/pom.xml +++ b/microprofile/cdi/pom.xml @@ -38,10 +38,6 @@ weld-se-core ${project.version} - - io.helidon.common - helidon-common-features - org.jboss.logging jboss-logging diff --git a/microprofile/cdi/src/main/java/io/helidon/microprofile/cdi/HelidonContainerImpl.java b/microprofile/cdi/src/main/java/io/helidon/microprofile/cdi/HelidonContainerImpl.java index 291bff329fc..c3b73a41c19 100644 --- a/microprofile/cdi/src/main/java/io/helidon/microprofile/cdi/HelidonContainerImpl.java +++ b/microprofile/cdi/src/main/java/io/helidon/microprofile/cdi/HelidonContainerImpl.java @@ -31,13 +31,13 @@ import java.util.logging.LogRecord; import java.util.logging.Logger; +import io.helidon.common.HelidonFeatures; +import io.helidon.common.HelidonFlavor; import io.helidon.common.LogConfig; import io.helidon.common.SerializationConfig; import io.helidon.common.Version; import io.helidon.common.context.Context; import io.helidon.common.context.Contexts; -import io.helidon.common.features.HelidonFeatures; -import io.helidon.common.features.HelidonFlavor; import io.helidon.config.mp.MpConfig; import io.helidon.config.mp.MpConfigProviderResolver; diff --git a/microprofile/cdi/src/main/java/module-info.java b/microprofile/cdi/src/main/java/module-info.java index 02cdc0a8f10..a25fe06fdd1 100644 --- a/microprofile/cdi/src/main/java/module-info.java +++ b/microprofile/cdi/src/main/java/module-info.java @@ -30,7 +30,6 @@ requires io.helidon.common; requires io.helidon.config; requires io.helidon.config.mp; - requires io.helidon.common.features; requires weld.core.impl; requires weld.spi; diff --git a/microprofile/config/pom.xml b/microprofile/config/pom.xml index 926fff9b744..203be169469 100644 --- a/microprofile/config/pom.xml +++ b/microprofile/config/pom.xml @@ -84,12 +84,6 @@ hamcrest-all test - - io.helidon.config - helidon-config-test-infrastructure - ${project.version} - test - io.helidon.microprofile.tests helidon-microprofile-tests-junit5 diff --git a/microprofile/openapi/src/test/java/io/helidon/microprofile/openapi/BasicServerTest.java b/microprofile/openapi/src/test/java/io/helidon/microprofile/openapi/BasicServerTest.java index 036618a6831..1e6365490ef 100644 --- a/microprofile/openapi/src/test/java/io/helidon/microprofile/openapi/BasicServerTest.java +++ b/microprofile/openapi/src/test/java/io/helidon/microprofile/openapi/BasicServerTest.java @@ -49,7 +49,7 @@ public class BasicServerTest { private static Map retrieveYaml(WebTarget webTarget) { try (Response response = webTarget .path(OpenAPISupport.DEFAULT_WEB_CONTEXT) - .request(OpenAPISupport.DEFAULT_RESPONSE_MEDIA_TYPE.toString()) + .request(OpenAPISupport.DEFAULT_RESPONSE_MEDIA_TYPE.text()) .get()) { assertThat("Fetch of OpenAPI document from server status", response.getStatus(), is(equalTo(Http.Status.OK_200.code()))); diff --git a/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java b/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java index 0ff19b432f8..e8c9fc90a67 100644 --- a/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java +++ b/openapi/src/main/java/io/helidon/openapi/OpenAPISupport.java @@ -384,7 +384,7 @@ private static String typeFromPath(Path path) { final Path staticFileNamePath = path.getFileName(); if (staticFileNamePath == null) { throw new IllegalArgumentException("File path " - + path.toAbsolutePath().toString() + + path.toAbsolutePath() + " does not seem to have a file name value but one is expected"); } final String pathText = staticFileNamePath.toString(); @@ -398,7 +398,7 @@ private void prepareResponse(ServerRequest req, ServerResponse resp) { final MediaType resultMediaType = chooseResponseMediaType(req); final String openAPIDocument = prepareDocument(resultMediaType); resp.status(Http.Status.OK_200); - resp.headers().add(Http.Header.CONTENT_TYPE, resultMediaType.toString()); + resp.headers().add(Http.Header.CONTENT_TYPE, resultMediaType.text()); resp.send(openAPIDocument); } catch (Exception ex) { resp.status(Http.Status.INTERNAL_SERVER_ERROR_500); @@ -422,7 +422,7 @@ String prepareDocument(MediaType resultMediaType) throws IOException { LOGGER.log(Level.FINER, () -> String.format( "Requested media type %s not supported; using default", - resultMediaType.toString())); + resultMediaType.text())); return OpenAPIMediaType.DEFAULT_TYPE; }); @@ -478,7 +478,7 @@ private MediaType chooseResponseMediaType(ServerRequest req) { LOGGER.log(Level.FINER, () -> String.format("Did not recognize requested media type %s; responding with default %s", req.headers().acceptedTypes(), - DEFAULT_RESPONSE_MEDIA_TYPE.toString())); + DEFAULT_RESPONSE_MEDIA_TYPE.text())); return DEFAULT_RESPONSE_MEDIA_TYPE; }); return resultMediaType; @@ -863,7 +863,7 @@ private OpenApiStaticFile getExplicitStaticFile() { if (specifiedMediaType == null) { throw new IllegalArgumentException("OpenAPI file path " - + path.toAbsolutePath().toString() + + path.toAbsolutePath() + " is not one of recognized types: " + OpenAPIMediaType.recognizedFileTypes()); } @@ -872,7 +872,7 @@ private OpenApiStaticFile getExplicitStaticFile() { is = new BufferedInputStream(Files.newInputStream(path)); } catch (IOException ex) { throw new IllegalArgumentException("OpenAPI file " - + path.toAbsolutePath().toString() + + path.toAbsolutePath() + " was specified but was not found", ex); } @@ -880,7 +880,7 @@ private OpenApiStaticFile getExplicitStaticFile() { LOGGER.log(Level.FINE, () -> String.format( OPENAPI_EXPLICIT_STATIC_FILE_LOG_MESSAGE_FORMAT, - path.toAbsolutePath().toString())); + path.toAbsolutePath())); return new OpenApiStaticFile(is, specifiedMediaType.format()); } catch (Exception ex) { try { @@ -904,7 +904,7 @@ private OpenApiStaticFile getDefaultStaticFile() { Path path = Paths.get(candidatePath); LOGGER.log(Level.FINE, () -> String.format( OPENAPI_DEFAULTED_STATIC_FILE_LOG_MESSAGE_FORMAT, - path.toAbsolutePath().toString())); + path.toAbsolutePath())); return new OpenApiStaticFile(is, candidate.format()); } if (candidatePaths != null) { diff --git a/openapi/src/test/java/io/helidon/openapi/TestUtil.java b/openapi/src/test/java/io/helidon/openapi/TestUtil.java index ab3e8592a13..80a1c09b1c5 100644 --- a/openapi/src/test/java/io/helidon/openapi/TestUtil.java +++ b/openapi/src/test/java/io/helidon/openapi/TestUtil.java @@ -300,7 +300,7 @@ public static HttpURLConnection getURLConnection( HttpURLConnection conn = (HttpURLConnection) url.openConnection(); conn.setRequestMethod(method); if (mediaType != null) { - conn.setRequestProperty("Accept", mediaType.toString()); + conn.setRequestProperty("Accept", mediaType.text()); } System.out.println("Connecting: " + method + " " + url); return conn; diff --git a/pom.xml b/pom.xml index 551f5a3c1ac..093231a27ca 100644 --- a/pom.xml +++ b/pom.xml @@ -1197,7 +1197,7 @@ false -helidon-parent,helidon-dependencies,helidon-bom,helidon-se,helidon-mp,io.grpc,helidon-mp-graal-native-image-extension,helidon-graal-native-image-extension,helidon-config-test-infrastructure,helidon-webserver-test-support +helidon-parent,helidon-dependencies,helidon-bom,helidon-se,helidon-mp,io.grpc,helidon-mp-graal-native-image-extension,helidon-graal-native-image-extension,helidon-webserver-test-support io.grpc diff --git a/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java b/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java index 882a3771eb1..bcdc808d6c3 100644 --- a/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java +++ b/security/integration/webserver/src/main/java/io/helidon/security/integration/webserver/SecurityHandler.java @@ -442,7 +442,8 @@ private CompletionStage processAuthentication(ServerResponse res, clientBuilder.explicitProvider(explicitAuthenticator.orElse(null)).submit().thenAccept(response -> { // copy headers to be returned with the current response - response.responseHeaders().forEach(res.headers()::put); + response.responseHeaders() + .forEach((key, value) -> res.headers().set(Http.HeaderValue.create(Http.Header.create(key), value))); switch (response.status()) { case SUCCESS: diff --git a/webclient/tracing/src/main/java/io/helidon/webclient/tracing/WebClientTracing.java b/webclient/tracing/src/main/java/io/helidon/webclient/tracing/WebClientTracing.java index e9fb09654ee..a2e76bccacc 100644 --- a/webclient/tracing/src/main/java/io/helidon/webclient/tracing/WebClientTracing.java +++ b/webclient/tracing/src/main/java/io/helidon/webclient/tracing/WebClientTracing.java @@ -19,8 +19,8 @@ import java.util.Map; import java.util.Optional; -import io.helidon.common.http.Http; import io.helidon.common.LazyValue; +import io.helidon.common.http.Http; import io.helidon.common.reactive.Single; import io.helidon.tracing.HeaderConsumer; import io.helidon.tracing.HeaderProvider; @@ -115,12 +115,12 @@ private ClientHeaderConsumer(WebClientRequestHeaders headers) { @Override public void setIfAbsent(String key, String... values) { - headers.putIfAbsent(key, values); + headers.setIfAbsent(Http.HeaderValue.create(Http.Header.create(key), values)); } @Override public void set(String key, String... values) { - headers.put(key, values); + headers.set(Http.HeaderValue.create(Http.Header.create(key), values)); } @Override @@ -130,7 +130,7 @@ public Iterable keys() { @Override public Optional get(String key) { - return headers.first(key); + return headers.first(Http.Header.create(key)); } @Override diff --git a/webclient/webclient/pom.xml b/webclient/webclient/pom.xml index 2121002935c..56061a7ce04 100644 --- a/webclient/webclient/pom.xml +++ b/webclient/webclient/pom.xml @@ -102,7 +102,7 @@ io.helidon.common.testing - helidon-common-testing-http + helidon-common-testing-http-junit5 test diff --git a/webclient/webclient/src/main/java/module-info.java b/webclient/webclient/src/main/java/module-info.java index bd1f48d3bf3..aa4ee2697ac 100644 --- a/webclient/webclient/src/main/java/module-info.java +++ b/webclient/webclient/src/main/java/module-info.java @@ -27,7 +27,6 @@ requires transitive io.helidon.common.reactive; requires transitive io.helidon.config; requires transitive io.helidon.media.common; - requires transitive io.helidon.common.reactive; requires transitive io.helidon.common.parameters; requires io.helidon.common.pki; diff --git a/webclient/webclient/src/test/java/io/helidon/webclient/ClientRequestHeadersImplTest.java b/webclient/webclient/src/test/java/io/helidon/webclient/ClientRequestHeadersImplTest.java index 28f908064a5..306f62f47f5 100644 --- a/webclient/webclient/src/test/java/io/helidon/webclient/ClientRequestHeadersImplTest.java +++ b/webclient/webclient/src/test/java/io/helidon/webclient/ClientRequestHeadersImplTest.java @@ -36,10 +36,10 @@ import static io.helidon.common.http.Http.Header.IF_MODIFIED_SINCE; import static io.helidon.common.http.Http.Header.IF_NONE_MATCH; import static io.helidon.common.http.Http.Header.IF_UNMODIFIED_SINCE; -import static io.helidon.common.testing.OptionalMatcher.optionalEmpty; -import static io.helidon.common.testing.OptionalMatcher.optionalValue; import static io.helidon.common.testing.http.HttpHeaderMatcher.hasHeader; import static io.helidon.common.testing.http.HttpHeaderMatcher.noHeader; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.is; import static org.junit.jupiter.api.Assertions.assertThrows; diff --git a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentSupport.java b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentSupport.java index 78a8daa1264..a990a2c213b 100644 --- a/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentSupport.java +++ b/webserver/static-content/src/main/java/io/helidon/webserver/staticcontent/StaticContentSupport.java @@ -23,7 +23,6 @@ import java.util.TreeMap; import java.util.function.Function; -import io.helidon.common.http.HttpMediaType; import io.helidon.common.media.type.MediaType; import io.helidon.webserver.Service; @@ -204,7 +203,7 @@ abstract class FileBasedBuilder> extends StaticCon * @throws NullPointerException if any parameter is {@code null} * @throws IllegalArgumentException if {@code filenameExtension} is empty */ - public T contentType(String filenameExtension, HttpMediaType contentType) { + public T contentType(String filenameExtension, MediaType contentType) { Objects.requireNonNull(filenameExtension, "Parameter 'filenameExtension' is null!"); Objects.requireNonNull(contentType, "Parameter 'contentType' is null!"); filenameExtension = filenameExtension.trim(); diff --git a/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/ClassPathContentHandlerTest.java b/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/ClassPathContentHandlerTest.java index 1edc30c8381..4a7701169cb 100644 --- a/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/ClassPathContentHandlerTest.java +++ b/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/ClassPathContentHandlerTest.java @@ -25,7 +25,7 @@ import java.util.stream.Collectors; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.webserver.Routing; import io.helidon.webserver.testsupport.TestClient; import io.helidon.webserver.testsupport.TestResponse; @@ -90,14 +90,14 @@ public void serveFromFiles() throws Exception { .get(); assertThat(response.status(), is(Http.Status.OK_200)); assertThat(filterResponse(response), is("- root A TXT")); - assertThat(response.headers().first(Http.Header.CONTENT_TYPE), is(Optional.of(MediaType.TEXT_PLAIN.toString()))); + assertThat(response.headers().first(Http.Header.CONTENT_TYPE), is(Optional.of(MediaTypes.TEXT_PLAIN.text()))); // /some/bar/root-a.txt response = TestClient.create(routing) .path("/some/bar/root-b.txt") .get(); assertThat(response.status(), is(Http.Status.OK_200)); assertThat(filterResponse(response), is("- root B TXT")); - assertThat(response.headers().first(Http.Header.CONTENT_TYPE), is(Optional.of(MediaType.TEXT_PLAIN.toString()))); + assertThat(response.headers().first(Http.Header.CONTENT_TYPE), is(Optional.of(MediaTypes.TEXT_PLAIN.text()))); // /some/bar/not.exist response = TestClient.create(routing) .path("/some/bar/not.exist") @@ -117,7 +117,7 @@ public void serveFromFilesWithWelcome() throws Exception { .get(); assertThat(response.status(), is(Http.Status.OK_200)); assertThat(filterResponse(response), is("- index TXT")); - assertThat(response.headers().first(Http.Header.CONTENT_TYPE), is(Optional.of(MediaType.TEXT_PLAIN.toString()))); + assertThat(response.headers().first(Http.Header.CONTENT_TYPE), is(Optional.of(MediaTypes.TEXT_PLAIN.text()))); // /bar/ response = TestClient.create(routing) .path("/bar/") @@ -136,14 +136,14 @@ public void serveFromJar() throws Exception { .get(); assertThat(response.status(), is(Http.Status.OK_200)); assertThat(filterResponse(response), is("Example A TXT")); - assertThat(response.headers().first(Http.Header.CONTENT_TYPE), is(Optional.of(MediaType.TEXT_PLAIN.toString()))); + assertThat(response.headers().first(Http.Header.CONTENT_TYPE), is(Optional.of(MediaTypes.TEXT_PLAIN.text()))); // /some/example-a.txt response = TestClient.create(routing) .path("/some/a/example-a.txt") .get(); assertThat(response.status(), is(Http.Status.OK_200)); assertThat(filterResponse(response), is("A / Example A TXT")); - assertThat(response.headers().first(Http.Header.CONTENT_TYPE), is(Optional.of(MediaType.TEXT_PLAIN.toString()))); + assertThat(response.headers().first(Http.Header.CONTENT_TYPE), is(Optional.of(MediaTypes.TEXT_PLAIN.text()))); // /some/a/not.exist response = TestClient.create(routing) .path("/some/a/not.exist") @@ -163,7 +163,7 @@ public void serveFromJarWithWelcome() throws Exception { .get(); assertThat(response.status(), is(Http.Status.OK_200)); assertThat(filterResponse(response), is("Example A TXT")); - assertThat(response.headers().first(Http.Header.CONTENT_TYPE), is(Optional.of(MediaType.TEXT_PLAIN.toString()))); + assertThat(response.headers().first(Http.Header.CONTENT_TYPE), is(Optional.of(MediaTypes.TEXT_PLAIN.text()))); // /a response = TestClient.create(routing) .path("/a/") @@ -175,7 +175,7 @@ public void serveFromJarWithWelcome() throws Exception { .path("/a") .get(); assertThat(response.status(), is(Http.Status.MOVED_PERMANENTLY_301)); - assertThat(response.headers().first("Location"), is(Optional.of("/a/"))); + assertThat(response.headers().first(Http.Header.LOCATION), is(Optional.of("/a/"))); // another index routing = Routing.builder() diff --git a/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/FileSystemContentHandlerTest.java b/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/FileSystemContentHandlerTest.java index 5784ef54608..93c2b334c6e 100644 --- a/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/FileSystemContentHandlerTest.java +++ b/webserver/static-content/src/test/java/io/helidon/webserver/staticcontent/FileSystemContentHandlerTest.java @@ -25,7 +25,7 @@ import java.util.concurrent.TimeoutException; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.webserver.Routing; import io.helidon.webserver.testsupport.TemporaryFolder; import io.helidon.webserver.testsupport.TemporaryFolderExtension; @@ -82,7 +82,7 @@ public void serveFile() throws Exception { .get(); assertThat(response.status(), is(Http.Status.OK_200)); assertThat(responseToString(response), is("Foo TXT")); - assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), is(MediaType.TEXT_PLAIN.toString())); + assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), is(MediaTypes.TEXT_PLAIN.text())); // /some/css/b.css response = TestClient.create(routing) .path("/some/css/b.css") @@ -115,7 +115,7 @@ public void serveIndex() throws Exception { Routing routing = Routing.builder() .register(StaticContentSupport.builder(folder.root().toPath()) .welcomeFileName("index.html") - .contentType("css", MediaType.TEXT_PLAIN) + .contentType("css", MediaTypes.TEXT_PLAIN) .build()) .build(); // / @@ -124,7 +124,7 @@ public void serveIndex() throws Exception { .get(); assertThat(response.status(), is(Http.Status.OK_200)); assertThat(responseToString(response), is("Index HTML")); - assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), is(MediaType.TEXT_HTML.toString())); + assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), is(MediaTypes.TEXT_HTML.text())); // /other response = TestClient.create(routing) .path("/other") @@ -137,7 +137,7 @@ public void serveIndex() throws Exception { .get(); assertThat(response.status(), is(Http.Status.OK_200)); assertThat(responseToString(response), is("Index HTML")); - assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), is(MediaType.TEXT_HTML.toString())); + assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), is(MediaTypes.TEXT_HTML.text())); // /css/ response = TestClient.create(routing) .path("/css/") @@ -149,6 +149,6 @@ public void serveIndex() throws Exception { .get(); assertThat(response.status(), is(Http.Status.OK_200)); assertThat(responseToString(response), is("A CSS")); - assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), is(MediaType.TEXT_PLAIN.toString())); + assertThat(response.headers().first(Http.Header.CONTENT_TYPE).orElse(null), is(MediaTypes.TEXT_PLAIN.text())); } } diff --git a/webserver/webserver/pom.xml b/webserver/webserver/pom.xml index b2dbec8d8d7..40f50a35cad 100644 --- a/webserver/webserver/pom.xml +++ b/webserver/webserver/pom.xml @@ -43,10 +43,6 @@ io.helidon.common helidon-common - - io.helidon.common - helidon-common-features - io.helidon.common helidon-common-http @@ -157,7 +153,7 @@ io.helidon.common.testing - helidon-common-testing-http + helidon-common-testing-http-junit5 test diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/ForwardingHandler.java b/webserver/webserver/src/main/java/io/helidon/webserver/ForwardingHandler.java index 01327769906..ca42030f936 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/ForwardingHandler.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/ForwardingHandler.java @@ -286,7 +286,7 @@ private boolean channelReadHttpRequest(ChannelHandlerContext ctx, Context reques } // Certificate management - request.headers().remove(Http.Header.X_HELIDON_CN).defaultCase(); + request.headers().remove(Http.Header.X_HELIDON_CN.defaultCase()); String cn = ctx.channel().attr(CLIENT_CERTIFICATE_NAME).get(); if (cn != null) { request.headers().set(Http.Header.X_HELIDON_CN.defaultCase(), cn); @@ -353,7 +353,7 @@ private boolean channelReadHttpRequest(ChannelHandlerContext ctx, Context reques LOGGER.finest(log("Request id: %s", ctx, bareRequest.requestId())); } - String contentLength = request.headers().get(HttpHeaderNames.CONTENT_LENGTH); + String contentLength = request.headers().get(Http.Header.CONTENT_LENGTH.defaultCase()); // HTTP WebSocket client sends a content length of 0 together with Connection: Upgrade if ("0".equals(contentLength) diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/NettyRequestHeaders.java b/webserver/webserver/src/main/java/io/helidon/webserver/NettyRequestHeaders.java index 1798e8cfc41..3f4036c736a 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/NettyRequestHeaders.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/NettyRequestHeaders.java @@ -30,8 +30,6 @@ class NettyRequestHeaders implements RequestHeaders { private final HeadersServerRequest delegate; - private volatile List cachedAccepted; - NettyRequestHeaders(HttpHeaders nettyHeaders) { HeadersWritable hw = HeadersWritable.create(); for (String name : nettyHeaders.names()) { diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/NettyWebServer.java b/webserver/webserver/src/main/java/io/helidon/webserver/NettyWebServer.java index 332bf5c69da..22420d0ea69 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/NettyWebServer.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/NettyWebServer.java @@ -40,11 +40,11 @@ import javax.net.ssl.SSLContext; +import io.helidon.common.HelidonFeatures; +import io.helidon.common.HelidonFlavor; import io.helidon.common.SerializationConfig; import io.helidon.common.Version; import io.helidon.common.context.Context; -import io.helidon.common.features.HelidonFeatures; -import io.helidon.common.features.HelidonFlavor; import io.helidon.common.reactive.Single; import io.helidon.media.common.MessageBodyReaderContext; import io.helidon.media.common.MessageBodyWriterContext; diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/Response.java b/webserver/webserver/src/main/java/io/helidon/webserver/Response.java index 6d589548d61..43ef9b73df6 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/Response.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/Response.java @@ -40,8 +40,6 @@ import io.helidon.tracing.config.SpanTracingConfig; import io.helidon.tracing.config.TracingConfigUtil; -import io.netty.handler.codec.http.HttpHeaderNames; - /** * The basic implementation of {@link ServerResponse}. */ @@ -66,7 +64,7 @@ abstract class Response implements ServerResponse { /** * Creates new instance. * - * @param webServer a web server. + * @param webServer a web server. * @param bareResponse an implementation of the response SPI. */ Response(WebServer webServer, BareResponse bareResponse, List acceptedTypes) { @@ -94,17 +92,6 @@ abstract class Response implements ServerResponse { this.eventListener = response.eventListener; } - /** - * Returns a span context related to the current request. - *

- * {@code SpanContext} is a tracing component from - * opentracing.io standard. - *

- * - * @return the related span context or empty if not enabled - */ - abstract Optional spanContext(); - @Override public WebServer webServer() { return webServer; @@ -133,31 +120,6 @@ public MessageBodyWriterContext writerContext() { return writerContext; } - private Span createWriteSpan(GenericType type) { - Optional parentSpan = spanContext(); - if (!parentSpan.isPresent()) { - // we only trace write span if there is a parent - // (parent is either webserver HTTP Request span, or inherited span - // from request - return null; - } - - SpanTracingConfig spanConfig = TracingConfigUtil.spanConfig( - NettyWebServer.TRACING_COMPONENT, TRACING_CONTENT_WRITE); - - if (spanConfig.enabled()) { - String spanName = spanConfig.newName().orElse(TRACING_CONTENT_WRITE); - Span.Builder spanBuilder = WebTracingConfig.tracer(webServer()) - .spanBuilder(spanName) - .parent(parentSpan.get()); - if (type != null) { - spanBuilder.tag("response.type", type.getTypeName()); - } - return spanBuilder.start(); - } - return null; - } - @Override public Void send(Throwable content) { if (headers.httpStatus() == null) { @@ -188,20 +150,11 @@ public Single send(T content) { } @Override - public Single send(Publisher content) { - return send(content, true); - } - - @Override - public Single send(Publisher content, boolean applyFilters) { + public Single send(Publisher content, Class itemClass) { try { - final Publisher sendPublisher; - if (applyFilters) { - sendPublisher = writerContext.applyFilters(content); - } else { - sendPublisher = content; - } sendLockSupport.execute(() -> { + GenericType type = GenericType.create(itemClass); + Publisher sendPublisher = writerContext.marshallStream(content, type); sendLockSupport.contentSend = true; sendPublisher.subscribe(bareResponse); }, content == null); @@ -213,16 +166,20 @@ public Single send(Publisher content, boolean applyFi } @Override - public Single send() { - return send((Publisher) null); + public Single send(Publisher content) { + return send(content, true); } @Override - public Single send(Publisher content, Class itemClass) { + public Single send(Publisher content, boolean applyFilters) { try { + final Publisher sendPublisher; + if (applyFilters) { + sendPublisher = writerContext.applyFilters(content); + } else { + sendPublisher = content; + } sendLockSupport.execute(() -> { - GenericType type = GenericType.create(itemClass); - Publisher sendPublisher = writerContext.marshallStream(content, type); sendLockSupport.contentSend = true; sendPublisher.subscribe(bareResponse); }, content == null); @@ -239,15 +196,8 @@ public Single send(Function writer) { - writerContext.registerWriter(writer); - return this; - } - - @Override - public Response registerWriter(MessageBodyStreamWriter writer) { - writerContext.registerWriter(writer); - return this; + public Single send() { + return send((Publisher) null); } @Override @@ -257,36 +207,14 @@ public Response registerFilter(MessageBodyFilter filter) { } @Override - public Response registerFilter(Function, Publisher> function) { - writerContext.registerFilter(function::apply); - return this; - } - - @Override - public Response registerWriter(Class type, Function> function) { - writerContext.registerWriter(type, function); - return this; - } - - @Override - public Response registerWriter(Predicate accept, Function> function) { - writerContext.registerWriter(accept, function); - return this; - } - - @Override - public Response registerWriter(Class type, MediaType contentType, - Function> function) { - - writerContext.registerWriter(type, contentType, function); + public Response registerWriter(MessageBodyWriter writer) { + writerContext.registerWriter(writer); return this; } @Override - public Response registerWriter(Predicate accept, MediaType contentType, - Function> function) { - - writerContext.registerWriter(accept, contentType, function); + public Response registerWriter(MessageBodyStreamWriter writer) { + writerContext.registerWriter(writer); return this; } @@ -300,62 +228,133 @@ public long requestId() { return bareResponse.requestId(); } - private final class MessageBodyEventListener implements MessageBodyContext.EventListener { + /** + * Backward compatibility. + * + * @param type type to support + * @param function writer + * @param type to support + * @return updated response + * @deprecated use {@link #registerWriter(io.helidon.media.common.MessageBodyWriter)} instead + */ + @Deprecated + public Response registerWriter(Class type, Function> function) { + return registerWriter(new MessageBodyWriter() { + @Override + public Publisher write(Single single, + GenericType type, + MessageBodyWriterContext context) { + return single.flatMap(function); + } - private Span span; - private volatile boolean sent; + @Override + public PredicateResult accept(GenericType gType, MessageBodyWriterContext context) { + if (type.isAssignableFrom(gType.rawType())) { + return PredicateResult.SUPPORTED; + } + return PredicateResult.NOT_SUPPORTED; + } + }); + } - private synchronized void sendErrorHeadersIfNeeded() { - if (headers != null && !sent) { - status(500); - //We are not using CombinedHttpHeaders - headers().add(Http.HeaderValue.create(Http.Header.TRAILER, STREAM_STATUS + "," + STREAM_RESULT)); - sent = true; - headers.send(); + /** + * Backward compatibility. + * + * @param accept predicate for type to support + * @param function writer + * @param type to support + * @return updated response + * @deprecated use {@link #registerWriter(io.helidon.media.common.MessageBodyWriter)} instead + */ + @Deprecated + public Response registerWriter(Predicate> accept, Function> function) { + return registerWriter(new MessageBodyWriter() { + @Override + public Publisher write(Single single, + GenericType type, + MessageBodyWriterContext context) { + return single.flatMap(function); } - } - private synchronized void sendHeadersIfNeeded() { - if (headers != null && !sent) { - sent = true; - headers.send(); + @Override + public PredicateResult accept(GenericType gType, MessageBodyWriterContext context) { + if (accept.test(gType.rawType())) { + return PredicateResult.SUPPORTED; + } + return PredicateResult.NOT_SUPPORTED; } - } + }); + } - void finish() { - if (span != null) { - span.end(); + /** + * Backward compatibility. + * + * @param type type to support + * @param mediaType media type expected + * @param function writer + * @param type to support + * @return updated response + * @deprecated use {@link #registerWriter(io.helidon.media.common.MessageBodyWriter)} instead + */ + @Deprecated + public Response registerWriter(Class type, MediaType mediaType, Function> function) { + return registerWriter(new MessageBodyWriter() { + @Override + public Publisher write(Single single, + GenericType type, + MessageBodyWriterContext context) { + context.contentType(mediaType); + return single.flatMap(function); } + + @Override + public PredicateResult accept(GenericType gType, MessageBodyWriterContext context) { + if (type.isAssignableFrom(gType.rawType())) { + return context.contentType() + .map(it -> it.test(mediaType)) + .filter(it -> !it) + .map(it -> PredicateResult.NOT_SUPPORTED) + .orElse(PredicateResult.SUPPORTED); + } + return PredicateResult.NOT_SUPPORTED; + } + }); + } + + /** + * Returns a span context related to the current request. + *

+ * {@code SpanContext} is a tracing component from + * opentracing.io standard. + *

+ * + * @return the related span context or empty if not enabled + */ + abstract Optional spanContext(); + + private Span createWriteSpan(GenericType type) { + Optional parentSpan = spanContext(); + if (!parentSpan.isPresent()) { + // we only trace write span if there is a parent + // (parent is either webserver HTTP Request span, or inherited span + // from request + return null; } - @Override - public void onEvent(MessageBodyContext.Event event) { - switch (event.eventType()) { - case BEFORE_ONSUBSCRIBE: - GenericType type = event.entityType().orElse(null); - span = createWriteSpan(type); - break; - case BEFORE_ONNEXT: - sendHeadersIfNeeded(); - break; - case BEFORE_ONERROR: - sendErrorHeadersIfNeeded(); - break; - case AFTER_ONERROR: - if (span != null) { - span.end(); - } - break; - case BEFORE_ONCOMPLETE: - sendHeadersIfNeeded(); - break; - case AFTER_ONCOMPLETE: - finish(); - break; - default: - // do nothing + SpanTracingConfig spanConfig = TracingConfigUtil.spanConfig( + NettyWebServer.TRACING_COMPONENT, TRACING_CONTENT_WRITE); + + if (spanConfig.enabled()) { + String spanName = spanConfig.newName().orElse(TRACING_CONTENT_WRITE); + Span.Builder spanBuilder = WebTracingConfig.tracer(webServer()) + .spanBuilder(spanName) + .parent(parentSpan.get()); + if (type != null) { + spanBuilder.tag("response.type", type.getTypeName()); } + return spanBuilder.start(); } + return null; } private static class SendLockSupport { @@ -374,4 +373,62 @@ private synchronized void execute(Runnable runnable, boolean silentSendStatus) { runnable.run(); } } + + private final class MessageBodyEventListener implements MessageBodyContext.EventListener { + + private Span span; + private volatile boolean sent; + + @Override + public void onEvent(MessageBodyContext.Event event) { + switch (event.eventType()) { + case BEFORE_ONSUBSCRIBE: + GenericType type = event.entityType().orElse(null); + span = createWriteSpan(type); + break; + case BEFORE_ONNEXT: + sendHeadersIfNeeded(); + break; + case BEFORE_ONERROR: + sendErrorHeadersIfNeeded(); + break; + case AFTER_ONERROR: + if (span != null) { + span.end(); + } + break; + case BEFORE_ONCOMPLETE: + sendHeadersIfNeeded(); + break; + case AFTER_ONCOMPLETE: + finish(); + break; + default: + // do nothing + } + } + + void finish() { + if (span != null) { + span.end(); + } + } + + private synchronized void sendErrorHeadersIfNeeded() { + if (headers != null && !sent) { + status(500); + //We are not using CombinedHttpHeaders + headers().add(Http.HeaderValue.create(Http.Header.TRAILER, STREAM_STATUS + "," + STREAM_RESULT)); + sent = true; + headers.send(); + } + } + + private synchronized void sendHeadersIfNeeded() { + if (headers != null && !sent) { + sent = true; + headers.send(); + } + } + } } diff --git a/webserver/webserver/src/main/java/module-info.java b/webserver/webserver/src/main/java/module-info.java index 6ea9509a91a..50c0620f41a 100644 --- a/webserver/webserver/src/main/java/module-info.java +++ b/webserver/webserver/src/main/java/module-info.java @@ -32,7 +32,6 @@ requires transitive io.helidon.tracing; requires io.helidon.logging.common; requires static io.helidon.config.metadata; - requires io.helidon.common.features; requires java.logging; requires io.netty.handler; diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/BytesReuseV2ApiTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/BytesReuseV2ApiTest.java index 1a514d798c4..57ff7c82a59 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/BytesReuseV2ApiTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/BytesReuseV2ApiTest.java @@ -89,7 +89,7 @@ private static void startServer(int port) throws Exception { } return chunk; })); - res.headers().add("Transfer-Encoding", "chunked"); + res.headers().add(Http.HeaderValues.TRANSFER_ENCODING_CHUNKED); req.next(); }) .post("/subscriber", (req, res) -> { diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/Gh1893Test.java b/webserver/webserver/src/test/java/io/helidon/webserver/Gh1893Test.java index 6db5b1f86d0..6ff7a57948a 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/Gh1893Test.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/Gh1893Test.java @@ -57,12 +57,14 @@ * Sample request: curl -g "http://hostname/tnt/page2{ */ class Gh1893Test { - public static final Duration TIMEOUT = Duration.ofSeconds(10); - public static final String CUSTOM_REASON_PHRASE = "Custom-bad-request"; - public static final String CUSTOM_ENTITY = "There we go"; + private static final Duration TIMEOUT = Duration.ofSeconds(10); + private static final String CUSTOM_REASON_PHRASE = "Custom-bad-request"; + private static final String CUSTOM_ENTITY = "There we go"; + private static WebServer webServer; private static WebClient webClient; + @BeforeAll static void startServer() { webServer = WebServer.builder() @@ -82,16 +84,16 @@ static void startServer() { private static TransportResponse badRequestHandler(DirectHandler.TransportRequest request, DirectHandler.EventType eventType, - Http.ResponseStatus defaultStatus, + Http.Status defaultStatus, String message) { if (request.uri().equals("/redirect")) { return TransportResponse.builder() .status(Http.Status.TEMPORARY_REDIRECT_307) - .header(Http.Header.LOCATION, "/errorPage") + .header(Http.Header.LOCATION.defaultCase(), "/errorPage") .build(); } return TransportResponse.builder() - .status(Http.ResponseStatus.create(Http.Status.BAD_REQUEST_400.code(), + .status(Http.Status.create(Http.Status.BAD_REQUEST_400.code(), CUSTOM_REASON_PHRASE)) .entity(CUSTOM_ENTITY) .build(); @@ -122,7 +124,7 @@ void testInvalidRequest() throws Exception { String response = SocketHttpClient.sendAndReceive("/", Http.Method.GET, null, - List.of(Http.Header.CONTENT_LENGTH + ": 47a"), + List.of(Http.Header.CONTENT_LENGTH.defaultCase() + ": 47a"), webServer); assertThat(response, containsString("400 " + CUSTOM_REASON_PHRASE)); @@ -135,7 +137,7 @@ void testInvalidRequestWithRedirect() throws Exception { String response = SocketHttpClient.sendAndReceive("/redirect", Http.Method.GET, null, - List.of(Http.Header.CONTENT_LENGTH + ": 47a"), + List.of(Http.Header.CONTENT_LENGTH.defaultCase() + ": 47a"), webServer); assertThat(SocketHttpClient.statusFromResponse(response), is(Http.Status.TEMPORARY_REDIRECT_307)); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/Gh1893V2ApiTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/Gh1893V2ApiTest.java index a48d9995b38..82da6e144b6 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/Gh1893V2ApiTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/Gh1893V2ApiTest.java @@ -122,7 +122,7 @@ void testInvalidRequest() throws Exception { String response = SocketHttpClient.sendAndReceive("/", Http.Method.GET, null, - List.of(Http.Header.CONTENT_LENGTH + ": 47a"), + List.of(Http.Header.CONTENT_LENGTH.defaultCase() + ": 47a"), webServer); assertThat(response, containsString("400 " + CUSTOM_REASON_PHRASE)); @@ -135,7 +135,7 @@ void testInvalidRequestWithRedirect() throws Exception { String response = SocketHttpClient.sendAndReceive("/redirect", Http.Method.GET, null, - List.of(Http.Header.CONTENT_LENGTH + ": 47a"), + List.of(Http.Header.CONTENT_LENGTH.defaultCase() + ": 47a"), webServer); assertThat(SocketHttpClient.statusFromResponse(response), is(Http.Status.TEMPORARY_REDIRECT_307)); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/KeepAliveV2ApiTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/KeepAliveV2ApiTest.java index 3ad7705e512..01dfedf339e 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/KeepAliveV2ApiTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/KeepAliveV2ApiTest.java @@ -35,6 +35,8 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.contains; +import static org.hamcrest.Matchers.equalToIgnoringCase; import static org.hamcrest.Matchers.hasEntry; import static org.hamcrest.Matchers.hasKey; import static org.hamcrest.Matchers.not; @@ -121,7 +123,8 @@ private static void testCall(WebClient webClient, assertThat(res.status().code(), is(expectedStatus)); if (expectedConnectionHeader != null) { assertThat(res.headers().toMap(), - hasEntry(HttpHeaderNames.CONNECTION.toString(), List.of(expectedConnectionHeader.toString()))); + hasEntry(equalToIgnoringCase(HttpHeaderNames.CONNECTION.toString()), + contains(expectedConnectionHeader.toString()))); } else { assertThat(res.headers().toMap(), not(hasKey(HttpHeaderNames.CONNECTION.toString()))); } diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/MultiPortTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/MultiPortTest.java index 1e8fe62e1fc..fafc2ab4a12 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/MultiPortTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/MultiPortTest.java @@ -213,8 +213,8 @@ public void compositeRedirectWebServer() throws Exception { .headers() .add(Http.Header.LOCATION, String.format("https://%s:%d%s", - req.headers() - .first(Http.Header.HOST) + Optional.of(req.headers() + .get(Http.Header.HOST)) .map(Http.HeaderValue::value) .map(s -> s.contains(":") ? s .subSequence(0, s.indexOf(":")) : s) diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/PlainV2ApiTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/PlainV2ApiTest.java index af3a99aa0d9..8725e6d36f0 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/PlainV2ApiTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/PlainV2ApiTest.java @@ -89,7 +89,7 @@ static void startServer() { res.send("In trace!"); }) .get("/force-chunked", (req, res) -> { - res.headers().put(Http.Header.TRANSFER_ENCODING, "chunked"); + res.headers().set(Http.HeaderValues.TRANSFER_ENCODING_CHUNKED); res.send("abcd"); }) .get("/multi", (req, res) -> { @@ -407,7 +407,7 @@ public void testForcedChunkedWithConnectionCloseHeader() throws Exception { webServer); Map headers = headersFromResponse(s); assertThat(headers, not(hasKey(equalToIgnoringCase("connection")))); - assertThat(headers, hasEntry(equalToIgnoringCase(Http.Header.TRANSFER_ENCODING), is("chunked"))); + assertThat(headers, hasEntry(equalToIgnoringCase(Http.Header.TRANSFER_ENCODING.defaultCase()), is("chunked"))); assertThat(entityFromResponse(s, false), is("4\nabcd\n0\n\n")); } @@ -496,7 +496,7 @@ public void testMultiFirstError() throws Exception { System.out.println(s); assertThat(s, startsWith("HTTP/1.1 500 Internal Server Error\n")); - assertThat(headersFromResponse(s), hasKey(equalToIgnoringCase(Http.Header.TRAILER))); + assertThat(headersFromResponse(s), hasKey(equalToIgnoringCase(Http.Header.TRAILER.defaultCase()))); Map trailerHeaders = cutTrailerHeaders(s); assertThat(trailerHeaders, hasEntry(equalToIgnoringCase("stream-status"), is("500"))); assertThat(trailerHeaders, hasEntry(equalToIgnoringCase("stream-result"), is(TEST_EXCEPTION.toString()))); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/ResponseTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/ResponseTest.java index 3d7c9e1a52e..91d2381be7d 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/ResponseTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/ResponseTest.java @@ -184,12 +184,12 @@ public void classRelatedWriters() throws Exception { public void writerByPredicate() throws Exception { StringBuilder sb = new StringBuilder(); Response response = new ResponseImpl(new NoOpBareResponse(null)); - response.registerWriter(o -> Integer.class.isAssignableFrom((Class)o), + response.registerWriter(Integer.class::isAssignableFrom, o -> { sb.append("1"); return Single.empty(); }); - response.registerWriter(o -> Long.class.isAssignableFrom((Class)o), + response.registerWriter(Long.class::isAssignableFrom, o -> { sb.append("2"); return Single.empty(); diff --git a/webserver/webserver/src/test/java/io/helidon/webserver/ServerConfigurationTest.java b/webserver/webserver/src/test/java/io/helidon/webserver/ServerConfigurationTest.java index 6672eda5fdb..a72d541ef1b 100644 --- a/webserver/webserver/src/test/java/io/helidon/webserver/ServerConfigurationTest.java +++ b/webserver/webserver/src/test/java/io/helidon/webserver/ServerConfigurationTest.java @@ -24,7 +24,7 @@ import org.junit.jupiter.api.Test; -import static io.helidon.common.testing.OptionalMatcher.optionalPresent; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalPresent; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.CoreMatchers.nullValue; diff --git a/webserver/websocket/src/main/java/io/helidon/webserver/websocket/WebSocketHandler.java b/webserver/websocket/src/main/java/io/helidon/webserver/websocket/WebSocketHandler.java index 971aa2d6b42..939d0105edf 100644 --- a/webserver/websocket/src/main/java/io/helidon/webserver/websocket/WebSocketHandler.java +++ b/webserver/websocket/src/main/java/io/helidon/webserver/websocket/WebSocketHandler.java @@ -27,10 +27,9 @@ import java.util.logging.Logger; import java.util.stream.Collectors; -import io.helidon.common.http.Parameters; -import io.helidon.common.http.UriComponent; import io.helidon.common.reactive.BufferedEmittingPublisher; import io.helidon.common.reactive.Multi; +import io.helidon.common.uri.UriQuery; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; @@ -179,8 +178,10 @@ WebSocketEngine.UpgradeInfo upgrade(ChannelHandlerContext ctx) { // Create Tyrus request context, copy request headers and query params Map paramsMap = new HashMap<>(); - Parameters params = UriComponent.decodeQuery(queryString, true); - params.toMap().forEach((key, value) -> paramsMap.put(key, value.toArray(new String[0]))); + UriQuery uriQuery = UriQuery.create(queryString); + for (String name : uriQuery.names()) { + paramsMap.put(name, uriQuery.all(name).toArray(new String[0])); + } RequestContext requestContext = RequestContext.Builder.create() .requestURI(URI.create(path)) // excludes context path .queryString(queryString) diff --git a/webserver/websocket/src/test/java/io/helidon/webserver/websocket/test/WSTest.java b/webserver/websocket/src/test/java/io/helidon/webserver/websocket/test/WSTest.java index 339f5592e85..02e675193cc 100644 --- a/webserver/websocket/src/test/java/io/helidon/webserver/websocket/test/WSTest.java +++ b/webserver/websocket/src/test/java/io/helidon/webserver/websocket/test/WSTest.java @@ -46,18 +46,15 @@ import jakarta.websocket.server.ServerEndpointConfig; -public class WSTest { +class WSTest { private static WebServer webServer; @BeforeAll - public static void startServer() throws Exception { + static void startServer() { LogConfig.configureRuntime(); webServer = WebServer.builder() - .defaultSocket(s -> s - .bindAddress("localhost") - .port(8080) - ) + .defaultSocket(s -> s.bindAddress("localhost")) .routing(r -> r .get("/http-ctx", (req, res) -> res.send("Hello http!/http-ctx")) .get((req, res) -> res.send("Hello http!")) From abacf2ba2645e433a0d77ec1ca81143a95faf38f Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 11 Aug 2022 11:05:09 +0200 Subject: [PATCH 43/54] Checkstyle fixes --- .../main/java/io/helidon/webserver/examples/basics/Main.java | 4 ++-- .../helidon/webserver/examples/tutorial/CommentService.java | 2 +- .../graal/nativeimage/extension/HelidonReflectionFeature.java | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/Main.java b/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/Main.java index 931ab768f4d..3d876754a15 100644 --- a/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/Main.java +++ b/examples/webserver/basics/src/main/java/io/helidon/webserver/examples/basics/Main.java @@ -55,8 +55,8 @@ public class Main { private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); - public static final Http.HeaderName BAR_HEADER = Http.Header.create("bar"); - public static final Http.HeaderName FOO_HEADER = Http.Header.create("foo"); + private static final Http.HeaderName BAR_HEADER = Http.Header.create("bar"); + private static final Http.HeaderName FOO_HEADER = Http.Header.create("foo"); // ---------------- EXAMPLES diff --git a/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/CommentService.java b/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/CommentService.java index 73cf820adc0..4cbc35ac322 100644 --- a/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/CommentService.java +++ b/examples/webserver/tutorial/src/main/java/io/helidon/webserver/examples/tutorial/CommentService.java @@ -166,7 +166,7 @@ private static final class CommentWriter implements MessageBodyWriter type, MessageBodyWriterContext context) { - if(List.class.isAssignableFrom(type.rawType())) { + if (List.class.isAssignableFrom(type.rawType())) { if (context.headers().isAccepted(MediaTypes.TEXT_PLAIN)) { return PredicateResult.SUPPORTED; } else { diff --git a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java index d7d3b368ea8..63fb7ea476d 100644 --- a/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java +++ b/integrations/graal/native-image-extension/src/main/java/io/helidon/integrations/graal/nativeimage/extension/HelidonReflectionFeature.java @@ -32,9 +32,9 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import io.helidon.common.HelidonFeatures; import io.helidon.common.LogConfig; import io.helidon.common.Reflected; -import io.helidon.common.HelidonFeatures; import io.helidon.config.mp.MpConfigProviderResolver; import com.oracle.svm.core.jdk.Resources; From 97090ef4678c10b87f949924ee09565d5ca9ae50 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 11 Aug 2022 11:19:56 +0200 Subject: [PATCH 44/54] Fix wrong module info --- integrations/vault/sys/sys/src/main/java/module-info.java | 1 - 1 file changed, 1 deletion(-) diff --git a/integrations/vault/sys/sys/src/main/java/module-info.java b/integrations/vault/sys/sys/src/main/java/module-info.java index 126f795cfd4..a2e3e23e71c 100644 --- a/integrations/vault/sys/sys/src/main/java/module-info.java +++ b/integrations/vault/sys/sys/src/main/java/module-info.java @@ -27,7 +27,6 @@ requires io.helidon.integrations.vault; requires io.helidon.integrations.common.rest; requires io.helidon.integrations.vault.auths.common; - requires transitive io.helidon.common.reactive; exports io.helidon.integrations.vault.sys; From 4e1e42c83eb2b9e7a2fefb049e850a5516bc95f4 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 11 Aug 2022 11:25:11 +0200 Subject: [PATCH 45/54] Fix compilation issues. --- .../helidon/examples/se/httpstatuscount/StatusTest.java | 6 +++--- .../vault/secrets/database/src/main/java/module-info.java | 1 - messaging/messaging/src/main/java/module-info.java | 1 - .../upgrade/test/UpgradeCodecsCompositionTest.java | 8 ++++---- 4 files changed, 7 insertions(+), 9 deletions(-) diff --git a/examples/metrics/http-status-count-se/src/test/java/io/helidon/examples/se/httpstatuscount/StatusTest.java b/examples/metrics/http-status-count-se/src/test/java/io/helidon/examples/se/httpstatuscount/StatusTest.java index 5eee561e696..bb92c25df18 100644 --- a/examples/metrics/http-status-count-se/src/test/java/io/helidon/examples/se/httpstatuscount/StatusTest.java +++ b/examples/metrics/http-status-count-se/src/test/java/io/helidon/examples/se/httpstatuscount/StatusTest.java @@ -19,7 +19,7 @@ import java.util.concurrent.TimeUnit; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.config.Config; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.metrics.api.RegistryFactory; @@ -97,7 +97,7 @@ void checkStatusAfterGreet() throws ExecutionException, InterruptedException { } WebClientResponse response = webClient.get() .path("/greet") - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .request() .get(); assertThat("Status of /greet", response.status().code(), is(Http.Status.OK_200.code())); @@ -111,7 +111,7 @@ void checkAfterStatus(int status) throws ExecutionException, InterruptedExceptio } WebClientResponse response = webClient.get() .path("/status/" + status) - .accept(MediaType.APPLICATION_JSON) + .accept(MediaTypes.APPLICATION_JSON) .request() .get(); assertThat("Response status", response.status().code(), is(status)); diff --git a/integrations/vault/secrets/database/src/main/java/module-info.java b/integrations/vault/secrets/database/src/main/java/module-info.java index 373c245ef9c..09195f9b994 100644 --- a/integrations/vault/secrets/database/src/main/java/module-info.java +++ b/integrations/vault/secrets/database/src/main/java/module-info.java @@ -23,7 +23,6 @@ requires transitive io.helidon.common.reactive; requires io.helidon.integrations.vault; requires io.helidon.integrations.common.rest; - requires transitive io.helidon.common.reactive; exports io.helidon.integrations.vault.secrets.database; diff --git a/messaging/messaging/src/main/java/module-info.java b/messaging/messaging/src/main/java/module-info.java index bd03dd9d43d..45af45a206b 100644 --- a/messaging/messaging/src/main/java/module-info.java +++ b/messaging/messaging/src/main/java/module-info.java @@ -29,7 +29,6 @@ requires transitive microprofile.config.api; requires transitive microprofile.reactive.messaging.api; requires transitive microprofile.reactive.streams.operators.api; - requires transitive io.helidon.common.reactive; exports io.helidon.messaging; } diff --git a/tests/integration/webserver/upgrade/src/test/java/io/helidon/integration/webserver/upgrade/test/UpgradeCodecsCompositionTest.java b/tests/integration/webserver/upgrade/src/test/java/io/helidon/integration/webserver/upgrade/test/UpgradeCodecsCompositionTest.java index b3604ce85b1..73a37c7a1bf 100644 --- a/tests/integration/webserver/upgrade/src/test/java/io/helidon/integration/webserver/upgrade/test/UpgradeCodecsCompositionTest.java +++ b/tests/integration/webserver/upgrade/src/test/java/io/helidon/integration/webserver/upgrade/test/UpgradeCodecsCompositionTest.java @@ -197,17 +197,17 @@ void versionSpecificHttp20MultipleMethods(String param) throws IOException, Inte String expectedResponse = version + " route " + method + "\n"; Assertions.assertEquals(expectedResponse, - httpClient(Http.RequestMethod.create(method), + httpClient(Http.Method.create(method), url, version.contains("2") ? HttpClient.Version.HTTP_2 : HttpClient.Version.HTTP_1_1).body()); Assertions.assertEquals(expectedResponse, - webClient(Http.RequestMethod.create(method), + webClient(Http.Method.create(method), url, Http.Version.create(version)).content().as(String.class).await(TIMEOUT)); } - private HttpResponse httpClient(Http.RequestMethod method, + private HttpResponse httpClient(Http.Method method, String url, HttpClient.Version version) throws IOException, InterruptedException { return httpClient.send(HttpRequest.newBuilder() @@ -217,7 +217,7 @@ private HttpResponse httpClient(Http.RequestMethod method, .build(), HttpResponse.BodyHandlers.ofString()); } - private WebClientResponse webClient(Http.RequestMethod method, String url, Http.Version version) { + private WebClientResponse webClient(Http.Method method, String url, Http.Version version) { return webClient.method(method) .uri(resolveUri(url)) .httpVersion(version) From 6d1f2432a08fb49627481090595406d73e365081 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 11 Aug 2022 11:42:35 +0200 Subject: [PATCH 46/54] Fix compilation issues. --- .../tests/integration/vault/mp/TestDbSecrets.java | 6 +++--- .../tests/integration/vault/mp/TestKubernetesAuth.java | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/tests/integration/vault/mp/src/test/java/io/helidon/tests/integration/vault/mp/TestDbSecrets.java b/tests/integration/vault/mp/src/test/java/io/helidon/tests/integration/vault/mp/TestDbSecrets.java index 058218c0c5e..6c5d1cc2505 100644 --- a/tests/integration/vault/mp/src/test/java/io/helidon/tests/integration/vault/mp/TestDbSecrets.java +++ b/tests/integration/vault/mp/src/test/java/io/helidon/tests/integration/vault/mp/TestDbSecrets.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2021 Oracle and/or its affiliates. + * Copyright (c) 2021, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -50,7 +50,7 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import static io.helidon.config.testing.OptionalMatcher.present; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalPresent; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; import static org.hamcrest.MatcherAssert.assertThat; @@ -116,7 +116,7 @@ void testAddRole() { void testGetSecrets() throws SQLException { Optional maybeCreds = db.get("readonly"); - assertThat(maybeCreds, is(present())); + assertThat(maybeCreds, is(optionalPresent())); DbCredentials mysql = maybeCreds.get(); diff --git a/tests/integration/vault/mp/src/test/java/io/helidon/tests/integration/vault/mp/TestKubernetesAuth.java b/tests/integration/vault/mp/src/test/java/io/helidon/tests/integration/vault/mp/TestKubernetesAuth.java index 5426e4ea28b..b9b9bb5dc4a 100644 --- a/tests/integration/vault/mp/src/test/java/io/helidon/tests/integration/vault/mp/TestKubernetesAuth.java +++ b/tests/integration/vault/mp/src/test/java/io/helidon/tests/integration/vault/mp/TestKubernetesAuth.java @@ -58,8 +58,8 @@ import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import static io.helidon.config.testing.OptionalMatcher.empty; -import static io.helidon.config.testing.OptionalMatcher.present; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalEmpty; +import static io.helidon.common.testing.junit5.OptionalMatcher.optionalPresent; import static io.helidon.common.testing.junit5.OptionalMatcher.optionalValue; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.notNullValue; @@ -136,13 +136,13 @@ void testSecrets(SeContainer container) { try { Optional maybeSecret = secrets.get("nested/path/secret"); - assertThat(maybeSecret, is(present())); + assertThat(maybeSecret, is(optionalPresent())); Kv2Secret theSecret = maybeSecret.get(); - assertThat(theSecret.value("first"), value(is("a value"))); + assertThat(theSecret.value("first"), optionalValue(is("a value"))); assertThat(theSecret.metadata().version(), is(1)); assertThat(theSecret.metadata().deleted(), is(false)); assertThat(theSecret.metadata().destroyed(), is(false)); - assertThat(theSecret.metadata().deletedTime(), is(empty())); + assertThat(theSecret.metadata().deletedTime(), is(optionalEmpty())); assertThat(theSecret.metadata().createdTime(), notNullValue()); } finally { // we want to remote the secret even if validation fails From 7d7a7e7dec3cba2c1baef21121afb04c409e6f0f Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 11 Aug 2022 15:48:47 +0200 Subject: [PATCH 47/54] Fix archetypes for new common. --- .../src/main/resources/META-INF/beans.xml | 14 +++--- .../test/java/__pkg__/TestCORS.java.mustache | 44 ++++++++++--------- .../FileService.java.multipart.mustache | 19 ++++---- .../FileServiceTest.java.multipart.mustache | 28 ++++++------ .../test/java/__pkg__/TestCORS.java.mustache | 40 +++++++++-------- .../PokemonMapperProvider.java.mustache | 5 +-- .../PokemonTypeMapperProvider.java.mustache | 5 +-- dependencies/pom.xml | 2 +- 8 files changed, 83 insertions(+), 74 deletions(-) diff --git a/archetypes/helidon/src/main/archetype/mp/common/files/src/main/resources/META-INF/beans.xml b/archetypes/helidon/src/main/archetype/mp/common/files/src/main/resources/META-INF/beans.xml index 5d94aab5a26..fedfc8e6222 100644 --- a/archetypes/helidon/src/main/archetype/mp/common/files/src/main/resources/META-INF/beans.xml +++ b/archetypes/helidon/src/main/archetype/mp/common/files/src/main/resources/META-INF/beans.xml @@ -1,8 +1,8 @@ - - + + \ No newline at end of file diff --git a/archetypes/helidon/src/main/archetype/mp/custom/files/src/test/java/__pkg__/TestCORS.java.mustache b/archetypes/helidon/src/main/archetype/mp/custom/files/src/test/java/__pkg__/TestCORS.java.mustache index 62c90704e32..1913474bc87 100644 --- a/archetypes/helidon/src/main/archetype/mp/custom/files/src/test/java/__pkg__/TestCORS.java.mustache +++ b/archetypes/helidon/src/main/archetype/mp/custom/files/src/test/java/__pkg__/TestCORS.java.mustache @@ -1,11 +1,14 @@ package {{package}}; import io.helidon.common.http.Headers; +import io.helidon.common.http.Http; import io.helidon.config.Config; import io.helidon.microprofile.server.Server; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; +import io.helidon.webclient.WebClientRequestHeaders; import io.helidon.webclient.WebClientResponse; +import io.helidon.webclient.WebClientResponseHeaders; import io.helidon.webserver.cors.CrossOriginConfig; import org.junit.jupiter.api.AfterAll; import org.junit.jupiter.api.BeforeAll; @@ -49,28 +52,28 @@ public class TestCORS { @Test void testAnonymousGreetWithCors() { WebClientRequestBuilder builder = client.get(); - Headers headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); + WebClientRequestHeaders headers = builder.headers(); + headers.set(Http.Header.ORIGIN, "http://foo.com"); + headers.set(Http.Header.HOST, "here.com"); WebClientResponse r = getResponse(builder); assertThat("HTTP response", r.status().code(), is(200)); String payload = fromPayload(r); - assertThat("HTTP response payload", payload, is("{\"message\":\"Hello World!\"}")); - headers = r.headers(); - Optional allowOrigin = headers.value(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); +assertThat("HTTP response payload", payload, is("{\"message\":\"Hello World!\"}")); + WebClientResponseHeaders resHeaders = r.headers(); + Optional allowOrigin = resHeaders.value(Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN); assertThat("Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN + " is present", allowOrigin.isPresent(), is(true)); - assertThat("CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin.get(), is("http://foo.com")); + assertThat("CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin.get(), is("*")); } @Test void testCustomGreetingWithCors() { WebClientRequestBuilder builder = client.method("OPTIONS"); - Headers headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); + WebClientRequestHeaders headers = builder.headers(); + headers.set(Http.Header.ORIGIN, "http://foo.com"); + headers.set(Http.Header.HOST, "here.com"); headers.add("Access-Control-Request-Method", "PUT"); WebClientResponse r = builder.path("/simple-greet") @@ -78,26 +81,27 @@ public class TestCORS { .await(); assertThat("pre-flight status", r.status().code(), is(200)); - Headers preflightResponseHeaders = r.headers(); - List allowMethods = preflightResponseHeaders.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS); + WebClientResponseHeaders responseHeaders = r.headers(); + Headers preflightResponseHeaders = responseHeaders; + List allowMethods = preflightResponseHeaders.values(Http.Header.ACCESS_CONTROL_ALLOW_METHODS); assertThat("pre-flight response check for " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS, allowMethods, is(not(empty()))); assertThat("Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS, allowMethods, contains("PUT")); - List allowOrigins = preflightResponseHeaders.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + List allowOrigins = preflightResponseHeaders.values(Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN); assertThat("pre-flight response check for " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigins, is(not(empty()))); assertThat( "Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigins, contains("http://foo.com")); builder = client.put(); headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); + headers.set(Http.Header.ORIGIN, "http://foo.com"); + headers.set(Http.Header.HOST, "here.com"); headers.addAll(preflightResponseHeaders); r = putResponse("Cheers", builder); assertThat("HTTP response3", r.status().code(), is(200)); - headers = r.headers(); - allowOrigins = headers.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + responseHeaders = r.headers(); + allowOrigins = headers.values(Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN); assertThat("Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigins, is(not(empty()))); assertThat( "Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigins, contains("http://foo.com")); @@ -107,9 +111,9 @@ public class TestCORS { @Test void testGreetingChangeWithCorsAndOtherOrigin() { WebClientRequestBuilder builder = client.put(); - Headers headers = builder.headers(); - headers.add("Origin", "http://other.com"); - headers.add("Host", "here.com"); + WebClientRequestHeaders headers = builder.headers(); + headers.set(Http.Header.ORIGIN, "http://other.com"); + headers.set(Http.Header.HOST, "here.com"); WebClientResponse r = putResponse("Ahoy", builder); boolean isOverriding = Config.create().get("cors").exists(); diff --git a/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/FileService.java.multipart.mustache b/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/FileService.java.multipart.mustache index 42d796c9fc4..b815733fcd4 100644 --- a/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/FileService.java.multipart.mustache +++ b/archetypes/helidon/src/main/archetype/se/custom/files/src/main/java/__pkg__/FileService.java.multipart.mustache @@ -1,27 +1,28 @@ package {{package}}; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Map; +import java.util.concurrent.ExecutorService; + import io.helidon.common.configurable.ThreadPoolSupplier; +import io.helidon.common.http.ContentDisposition; import io.helidon.common.http.DataChunk; import io.helidon.common.http.Http; -import io.helidon.common.http.MediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.IoMulti; -import io.helidon.media.multipart.ContentDisposition; import io.helidon.media.multipart.ReadableBodyPart; import io.helidon.webserver.ResponseHeaders; import io.helidon.webserver.Routing; import io.helidon.webserver.ServerRequest; import io.helidon.webserver.ServerResponse; import io.helidon.webserver.Service; + import jakarta.json.Json; import jakarta.json.JsonArrayBuilder; import jakarta.json.JsonBuilderFactory; -import java.nio.file.Path; -import java.util.Arrays; -import java.util.Map; -import java.util.concurrent.ExecutorService; - /** * File service. */ @@ -55,8 +56,8 @@ public final class FileService implements Service { private void download(ServerRequest req, ServerResponse res) { Path filePath = storage.lookup(req.path().param("fname")); ResponseHeaders headers = res.headers(); - headers.contentType(MediaType.APPLICATION_OCTET_STREAM); - headers.put(Http.Header.CONTENT_DISPOSITION, ContentDisposition.builder() + headers.contentType(MediaTypes.APPLICATION_OCTET_STREAM); + headers.set(Http.Header.CONTENT_DISPOSITION, ContentDisposition.builder() .filename(filePath.getFileName().toString()) .build() .toString()); diff --git a/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/FileServiceTest.java.multipart.mustache b/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/FileServiceTest.java.multipart.mustache index ce607e4eb12..8c7998e7267 100644 --- a/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/FileServiceTest.java.multipart.mustache +++ b/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/FileServiceTest.java.multipart.mustache @@ -1,13 +1,22 @@ package {{package}}; -import io.helidon.common.http.MediaType; +import java.io.IOException; +import java.nio.charset.StandardCharsets; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.concurrent.TimeUnit; + +import io.helidon.common.http.Http; +import io.helidon.common.media.type.MediaTypes; import io.helidon.media.jsonp.JsonpSupport; import io.helidon.media.multipart.FileFormParams; import io.helidon.media.multipart.MultiPartSupport; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientResponse; import io.helidon.webserver.WebServer; + import jakarta.json.JsonObject; import jakarta.json.JsonString; import org.hamcrest.Matchers; @@ -18,13 +27,6 @@ import org.junit.jupiter.api.Order; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.TestMethodOrder; -import java.io.IOException; -import java.nio.charset.StandardCharsets; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.List; -import java.util.concurrent.TimeUnit; - import static org.hamcrest.CoreMatchers.containsString; import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.MatcherAssert.assertThat; @@ -65,7 +67,7 @@ public class FileServiceTest { Path file = Files.write( Files.createTempFile(null, null), "bar\n".getBytes(StandardCharsets.UTF_8)); WebClientResponse response = webClient .post() - .contentType(MediaType.MULTIPART_FORM_DATA) + .contentType(MediaTypes.MULTIPART_FORM_DATA) .submit(FileFormParams.builder() .addFile("file[]", "foo.txt", file) .build()) @@ -81,7 +83,7 @@ public class FileServiceTest { WebClientResponse response = webClient .post() .queryParam("stream", "true") - .contentType(MediaType.MULTIPART_FORM_DATA) + .contentType(MediaTypes.MULTIPART_FORM_DATA) .submit(FileFormParams.builder() .addFile("file[]", "streamed-foo.txt", file) .addFile("otherPart", "streamed-foo2.txt", file2) @@ -95,7 +97,7 @@ public class FileServiceTest { public void testList() { WebClientResponse response = webClient .get() - .contentType(MediaType.APPLICATION_JSON) + .contentType(MediaTypes.APPLICATION_JSON) .request() .await(); assertThat(response.status().code(), Matchers.is(200)); @@ -111,11 +113,11 @@ public class FileServiceTest { WebClientResponse response = webClient .get() .path("foo.txt") - .accept(MediaType.APPLICATION_OCTET_STREAM) + .accept(MediaTypes.APPLICATION_OCTET_STREAM) .request() .await(); assertThat(response.status().code(), is(200)); - assertThat(response.headers().first("Content-Disposition").orElse(null), + assertThat(response.headers().first(Http.Header.CONTENT_DISPOSITION).orElse(null), containsString("filename=\"foo.txt\"")); byte[] bytes = response.content().as(byte[].class).await(); assertThat(new String(bytes, StandardCharsets.UTF_8), Matchers.is("bar\n")); diff --git a/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/TestCORS.java.mustache b/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/TestCORS.java.mustache index 2558e7a6004..602d60d64a4 100644 --- a/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/TestCORS.java.mustache +++ b/archetypes/helidon/src/main/archetype/se/custom/files/src/test/java/__pkg__/TestCORS.java.mustache @@ -1,10 +1,13 @@ package {{package}}; import io.helidon.common.http.Headers; +import io.helidon.common.http.Http; import io.helidon.config.Config; import io.helidon.webclient.WebClient; import io.helidon.webclient.WebClientRequestBuilder; +import io.helidon.webclient.WebClientRequestHeaders; import io.helidon.webclient.WebClientResponse; +import io.helidon.webclient.WebClientResponseHeaders; import io.helidon.webserver.WebServer; import io.helidon.webserver.cors.CrossOriginConfig; import org.junit.jupiter.api.AfterAll; @@ -58,16 +61,16 @@ public class TestCORS { @Test void testAnonymousGreetWithCors() { WebClientRequestBuilder builder = client.get(); - Headers headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); + WebClientRequestHeaders headers = builder.headers(); + headers.set(Http.Header.ORIGIN, "http://foo.com"); + headers.set(Http.Header.HOST, "here.com"); WebClientResponse r = getResponse(builder); assertThat("HTTP response", r.status().code(), is(200)); String payload = fromPayload(r); assertThat("HTTP response payload", payload, is("Hello World!")); - headers = r.headers(); - Optional allowOrigin = headers.value(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + WebClientResponseHeaders resHeaders = r.headers(); + Optional allowOrigin = resHeaders.value(Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN); assertThat("Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN + " is present", allowOrigin.isPresent(), is(true)); assertThat("CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigin.get(), is("*")); @@ -77,9 +80,9 @@ public class TestCORS { void testCustomGreetingWithCors() { WebClientRequestBuilder builder = client.method("OPTIONS"); - Headers headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); + WebClientRequestHeaders headers = builder.headers(); + headers.set(Http.Header.ORIGIN, "http://foo.com"); + headers.set(Http.Header.HOST, "here.com"); headers.add("Access-Control-Request-Method", "PUT"); WebClientResponse r = builder.path("/cors") @@ -87,26 +90,27 @@ public class TestCORS { .await(); assertThat("pre-flight status", r.status().code(), is(200)); - Headers preflightResponseHeaders = r.headers(); - List allowMethods = preflightResponseHeaders.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS); + WebClientResponseHeaders responseHeaders = r.headers(); + Headers preflightResponseHeaders = responseHeaders; + List allowMethods = preflightResponseHeaders.values(Http.Header.ACCESS_CONTROL_ALLOW_METHODS); assertThat("pre-flight response check for " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS, allowMethods, is(not(empty()))); assertThat("Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_METHODS, allowMethods, contains("PUT")); - List allowOrigins = preflightResponseHeaders.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + List allowOrigins = preflightResponseHeaders.values(Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN); assertThat("pre-flight response check for " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigins, is(not(empty()))); assertThat( "Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigins, contains("http://foo.com")); builder = client.put(); headers = builder.headers(); - headers.add("Origin", "http://foo.com"); - headers.add("Host", "here.com"); + headers.set(Http.Header.ORIGIN, "http://foo.com"); + headers.set(Http.Header.HOST, "here.com"); headers.addAll(preflightResponseHeaders); r = putResponse("Cheers", builder); assertThat("HTTP response3", r.status().code(), is(200)); - headers = r.headers(); - allowOrigins = headers.values(CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN); + responseHeaders = r.headers(); + allowOrigins = headers.values(Http.Header.ACCESS_CONTROL_ALLOW_ORIGIN); assertThat("Expected CORS header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigins, is(not(empty()))); assertThat( "Header " + CrossOriginConfig.ACCESS_CONTROL_ALLOW_ORIGIN, allowOrigins, contains("http://foo.com")); @@ -116,9 +120,9 @@ public class TestCORS { @Test void testGreetingChangeWithCorsAndOtherOrigin() { WebClientRequestBuilder builder = client.put(); - Headers headers = builder.headers(); - headers.add("Origin", "http://other.com"); - headers.add("Host", "here.com"); + WebClientRequestHeaders headers = builder.headers(); + headers.set(Http.Header.ORIGIN, "http://other.com"); + headers.set(Http.Header.HOST, "here.com"); WebClientResponse r = putResponse("Ahoy", builder); boolean isOverriding = Config.create().get("cors").exists(); diff --git a/archetypes/helidon/src/main/archetype/se/database/files/src/main/java/__pkg__/PokemonMapperProvider.java.mustache b/archetypes/helidon/src/main/archetype/se/database/files/src/main/java/__pkg__/PokemonMapperProvider.java.mustache index ad9befd5ddb..e5bbc89fd0c 100644 --- a/archetypes/helidon/src/main/archetype/se/database/files/src/main/java/__pkg__/PokemonMapperProvider.java.mustache +++ b/archetypes/helidon/src/main/archetype/se/database/files/src/main/java/__pkg__/PokemonMapperProvider.java.mustache @@ -7,8 +7,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import jakarta.annotation.Priority; - +import io.helidon.common.Weight; import io.helidon.dbclient.DbColumn; import io.helidon.dbclient.DbMapper; import io.helidon.dbclient.DbRow; @@ -19,7 +18,7 @@ import io.helidon.dbclient.spi.DbMapperProvider; * * Pokemon, and Pokemon character names are trademarks of Nintendo. */ -@Priority(1000) +@Weight(50) public class PokemonMapperProvider implements DbMapperProvider { private static final PokemonMapper MAPPER = new PokemonMapper(); diff --git a/archetypes/helidon/src/main/archetype/se/database/files/src/main/java/__pkg__/PokemonTypeMapperProvider.java.mustache b/archetypes/helidon/src/main/archetype/se/database/files/src/main/java/__pkg__/PokemonTypeMapperProvider.java.mustache index 1d4e6b1943e..9c3f887db09 100644 --- a/archetypes/helidon/src/main/archetype/se/database/files/src/main/java/__pkg__/PokemonTypeMapperProvider.java.mustache +++ b/archetypes/helidon/src/main/archetype/se/database/files/src/main/java/__pkg__/PokemonTypeMapperProvider.java.mustache @@ -5,8 +5,7 @@ import java.util.List; import java.util.Map; import java.util.Optional; -import jakarta.annotation.Priority; - +import io.helidon.common.Weight; import io.helidon.dbclient.DbColumn; import io.helidon.dbclient.DbMapper; import io.helidon.dbclient.DbRow; @@ -17,7 +16,7 @@ import io.helidon.dbclient.spi.DbMapperProvider; * * Pokemon, and Pokemon character names are trademarks of Nintendo. */ -@Priority(1000) +@Weight(50) public class PokemonTypeMapperProvider implements DbMapperProvider { private static final PokemonTypeMapper MAPPER = new PokemonTypeMapper(); diff --git a/dependencies/pom.xml b/dependencies/pom.xml index 998ee5d5b74..969a5a65ccd 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -45,7 +45,7 @@ 1.2 9.1.6 4.1.2 - 3.0.2 + 3.0.3-RC2 4.0.2 2.18.0 2.3.1 From 333f48db608485d4a36a17166175a9ac7cb603a6 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 11 Aug 2022 16:32:23 +0200 Subject: [PATCH 48/54] Fix native image test --- .../integration/nativeimage/se1/WebClientService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/WebClientService.java b/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/WebClientService.java index d45c01744d0..0dd3990f84e 100644 --- a/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/WebClientService.java +++ b/tests/integration/native-image/se-1/src/main/java/io/helidon/tests/integration/nativeimage/se1/WebClientService.java @@ -25,11 +25,9 @@ import java.util.function.Predicate; import java.util.logging.Logger; -import jakarta.json.JsonValue; - import io.helidon.common.context.Context; import io.helidon.common.http.Http; -import io.helidon.common.http.HttpMediaType; +import io.helidon.common.media.type.MediaTypes; import io.helidon.common.reactive.Single; import io.helidon.config.Config; import io.helidon.media.jsonb.JsonbSupport; @@ -41,6 +39,8 @@ import io.helidon.webserver.ServerResponse; import io.helidon.webserver.Service; +import jakarta.json.JsonValue; + public class WebClientService implements Service { private static final Logger LOGGER = Logger.getLogger(WebClientService.class.getName()); @@ -54,7 +54,7 @@ public WebClientService(Config config, MockZipkinService zipkinService) { client = WebClient.builder() .baseUri(context) .addReader(JsonbSupport.reader()) - .addHeader(Http.Header.ACCEPT, MediaType.APPLICATION_JSON.toString()) + .addHeader(Http.Header.ACCEPT, MediaTypes.APPLICATION_JSON.toString()) .config(config.get("client")) .build(); } From 6df84e3f57936e4950fd5e7a5e27de9360b14764 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 11 Aug 2022 17:05:05 +0200 Subject: [PATCH 49/54] Fixed javadoc refs --- .../main/java/io/helidon/webserver/ServerConfiguration.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/ServerConfiguration.java b/webserver/webserver/src/main/java/io/helidon/webserver/ServerConfiguration.java index 100383e350f..c32e8c7759c 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/ServerConfiguration.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/ServerConfiguration.java @@ -211,7 +211,7 @@ default Optional transport() { } /** - * Whether to print details of {@link io.helidon.common.features.HelidonFeatures}. + * Whether to print details of {@link io.helidon.common.HelidonFeatures}. * * @return whether to print details */ @@ -575,7 +575,7 @@ public Builder transport(Transport transport) { * * @param print whether to print details or not * @return updated builder instance - * @see io.helidon.common.features.HelidonFeatures + * @see io.helidon.common.HelidonFeatures */ public Builder printFeatureDetails(boolean print) { this.printFeatureDetails = print; From a06d92873e80715ac4bab852a738d2b1e6da85aa Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 11 Aug 2022 18:02:15 +0200 Subject: [PATCH 50/54] Javadoc fix --- .../java/io/helidon/webserver/HandlerRoute.java | 4 ++-- .../java/io/helidon/webserver/Http1Route.java | 17 ++++++++++++++++- .../java/io/helidon/webserver/JsonService.java | 5 +++++ .../io/helidon/webserver/WebTracingConfig.java | 6 ++++++ 4 files changed, 29 insertions(+), 3 deletions(-) diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/HandlerRoute.java b/webserver/webserver/src/main/java/io/helidon/webserver/HandlerRoute.java index 18b0cc8b377..53af3b5f396 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/HandlerRoute.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/HandlerRoute.java @@ -135,7 +135,7 @@ public Handler handler() { return handler; } - public Map diagnosticEvent() { + Map diagnosticEvent() { return diagnosticEvent; } @@ -146,7 +146,7 @@ public Map diagnosticEvent() { * @return a {@link PathMatcher.Result} of the test. * @throws NullPointerException in case that {@code path} parameter is {@code null}. */ - public PathMatcher.Result match(CharSequence path) { + PathMatcher.Result match(CharSequence path) { return pathMatcher.match(extractPathParams(path.toString())); } diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/Http1Route.java b/webserver/webserver/src/main/java/io/helidon/webserver/Http1Route.java index 32fd4358631..ae430c31903 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/Http1Route.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/Http1Route.java @@ -25,9 +25,24 @@ */ public class Http1Route extends HandlerRoute implements HttpRoute { + /** + * Create a new route. + * + * @param method supported method + * @param path request path (exact) + * @param handler handle the request + */ protected Http1Route(Http.Method method, String path, Handler handler) { super(List.of(), PathMatcher.create(path), handler, method); } + + /** + * Create a new route. + * + * @param pathMatcher match path of the request + * @param handler handle the request + * @param methods supported methods + */ protected Http1Route(PathMatcher pathMatcher, Handler handler, Http.Method... methods) { super(List.of(), pathMatcher, handler, methods); } @@ -57,7 +72,7 @@ public static Http1Route route(Http.Method method, String path, Handler handler) * * @param pathMatcher URI Path Matcher * @param handler handler - * @param methods HTTP {@link io.helidon.common.http.Http.RequestMethod methods} handled by this route + * @param methods HTTP {@link io.helidon.common.http.Http.Method methods} handled by this route * @return a new HTTP/1.1 specific route */ public static Http1Route route(PathMatcher pathMatcher, Handler handler, Http.Method... methods) { diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/JsonService.java b/webserver/webserver/src/main/java/io/helidon/webserver/JsonService.java index dc78f0af7be..661624b739f 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/JsonService.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/JsonService.java @@ -28,6 +28,11 @@ * @see Routing.Rules */ public abstract class JsonService implements Service, Handler { + /** + * No side effects. + */ + protected JsonService() { + } /** * Registers this handler for any HTTP method. diff --git a/webserver/webserver/src/main/java/io/helidon/webserver/WebTracingConfig.java b/webserver/webserver/src/main/java/io/helidon/webserver/WebTracingConfig.java index a4aeb93c64d..cfe1daf16ee 100644 --- a/webserver/webserver/src/main/java/io/helidon/webserver/WebTracingConfig.java +++ b/webserver/webserver/src/main/java/io/helidon/webserver/WebTracingConfig.java @@ -41,6 +41,12 @@ * and a path specific {@link PathTracingConfig}. */ public abstract class WebTracingConfig { + /** + * No side effects. + */ + protected WebTracingConfig() { + } + /** * Tracing configuration. * This is the configuration set up for the whole server. There can also be a path specific configuration available From d29804289570b8a3b055e9a3dddcb0c196066a1f Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Thu, 11 Aug 2022 18:18:06 +0200 Subject: [PATCH 51/54] Javadoc fixes. --- .../dbclient/src/main/java/io/helidon/dbclient/DbClient.java | 2 +- .../src/main/java/io/helidon/dbclient/DbStatements.java | 5 ++++- .../src/main/java/io/helidon/webserver/http2/Http2Route.java | 2 +- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClient.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClient.java index f0e32062146..2ebb0e4c17a 100644 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClient.java +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbClient.java @@ -36,7 +36,7 @@ */ public interface DbClient { /** - * Qualifier used for mapping using {@link io.helidon.common.mapper.MapperManager#map(Object, Class, Class, String)}. + * Qualifier used for mapping using {@link io.helidon.common.mapper.MapperManager#map(Object, Class, Class, String...)}. */ String MAPPING_QUALIFIER = "dbclient"; diff --git a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbStatements.java b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbStatements.java index 413e1f5a229..a3e74c266d7 100644 --- a/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbStatements.java +++ b/dbclient/dbclient/src/main/java/io/helidon/dbclient/DbStatements.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2019, 2021 Oracle and/or its affiliates. + * Copyright (c) 2019, 2022 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -63,6 +63,9 @@ static DbStatements create(Config config) { class Builder implements io.helidon.common.Builder { private final Map configuredStatements = new HashMap<>(); + private Builder() { + } + /** * Add named database statement to database configuration.. * diff --git a/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2Route.java b/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2Route.java index 88e684aefc1..a64ac57c85f 100644 --- a/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2Route.java +++ b/webserver/http2/src/main/java/io/helidon/webserver/http2/Http2Route.java @@ -57,7 +57,7 @@ public static Http2Route route(Http.Method method, String path, Handler handler) * * @param pathMatcher URI Path Matcher * @param handler handler - * @param methods HTTP {@link io.helidon.common.http.Http.RequestMethod methods} handled by this route + * @param methods HTTP {@link io.helidon.common.http.Http.Method methods} handled by this route * @return a new HTTP/2 specific route */ public static Http2Route route(PathMatcher pathMatcher, Handler handler, Http.Method... methods) { From 02d8980aaca513186b625203e9da08d79c8aa47b Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Fri, 12 Aug 2022 14:15:25 +0200 Subject: [PATCH 52/54] Remove transitive dependency. --- metrics/api/pom.xml | 2 ++ metrics/api/src/main/java/module-info.java | 2 +- metrics/service-api/src/main/java/module-info.java | 2 +- microprofile/server/src/main/java/module-info.java | 2 +- 4 files changed, 5 insertions(+), 3 deletions(-) diff --git a/metrics/api/pom.xml b/metrics/api/pom.xml index fd3e88b49e3..a888d44cc17 100644 --- a/metrics/api/pom.xml +++ b/metrics/api/pom.xml @@ -44,6 +44,8 @@ io.helidon.config helidon-config-metadata + provided + true org.eclipse.microprofile.metrics diff --git a/metrics/api/src/main/java/module-info.java b/metrics/api/src/main/java/module-info.java index cae574b7925..91c76a7dcd7 100644 --- a/metrics/api/src/main/java/module-info.java +++ b/metrics/api/src/main/java/module-info.java @@ -27,7 +27,7 @@ requires transitive io.helidon.config; requires transitive microprofile.metrics.api; - requires io.helidon.config.metadata; + requires static io.helidon.config.metadata; exports io.helidon.metrics.api; exports io.helidon.metrics.api.spi; diff --git a/metrics/service-api/src/main/java/module-info.java b/metrics/service-api/src/main/java/module-info.java index 14cdafc38b9..dc0e60cb195 100644 --- a/metrics/service-api/src/main/java/module-info.java +++ b/metrics/service-api/src/main/java/module-info.java @@ -21,7 +21,7 @@ requires java.logging; requires io.helidon.webserver; - requires io.helidon.config.metadata; + requires static io.helidon.config.metadata; requires io.helidon.servicecommon.rest; requires io.helidon.metrics.api; diff --git a/microprofile/server/src/main/java/module-info.java b/microprofile/server/src/main/java/module-info.java index cd424c8d76e..c0f153964e3 100644 --- a/microprofile/server/src/main/java/module-info.java +++ b/microprofile/server/src/main/java/module-info.java @@ -40,7 +40,7 @@ // there is now a hardcoded dependency on Weld, to configure additional bean defining annotation requires java.management; requires microprofile.config.api; - requires io.helidon.config.metadata; + requires static io.helidon.config.metadata; exports io.helidon.microprofile.server; From 34817b92d51fa37e56fcd3fd832436dc6e09f708 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Fri, 12 Aug 2022 16:56:40 +0200 Subject: [PATCH 53/54] Missing module re-added to reactor. --- integrations/micronaut/pom.xml | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/integrations/micronaut/pom.xml b/integrations/micronaut/pom.xml index 4a67297d3b4..9668b884eb9 100644 --- a/integrations/micronaut/pom.xml +++ b/integrations/micronaut/pom.xml @@ -34,9 +34,6 @@ cdi-processor cdi - - + data From b82c12feb4f6f770c94b5bc284ee3b8dcaef6445 Mon Sep 17 00:00:00 2001 From: Tomas Langer Date: Sat, 13 Aug 2022 00:30:10 +0200 Subject: [PATCH 54/54] Fix query param processing in Prometheus support --- .../java/io/helidon/metrics/prometheus/PrometheusSupport.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/metrics/prometheus/src/main/java/io/helidon/metrics/prometheus/PrometheusSupport.java b/metrics/prometheus/src/main/java/io/helidon/metrics/prometheus/PrometheusSupport.java index 243a65664f8..01f7e09e3a1 100644 --- a/metrics/prometheus/src/main/java/io/helidon/metrics/prometheus/PrometheusSupport.java +++ b/metrics/prometheus/src/main/java/io/helidon/metrics/prometheus/PrometheusSupport.java @@ -18,6 +18,7 @@ import java.util.Enumeration; import java.util.HashSet; +import java.util.List; import java.util.Set; import io.helidon.common.http.HttpMediaType; @@ -64,7 +65,7 @@ public void update(Routing.Rules rules) { } private void process(ServerRequest req, ServerResponse res) { - Set filters = new HashSet<>(req.queryParams().all("name[]")); + Set filters = new HashSet<>(req.queryParams().all("name[]", List::of)); Enumeration mfs = collectorRegistry.filteredMetricFamilySamples(filters); res.headers().contentType(CONTENT_TYPE); res.send(compose(mfs));