diff --git a/config/yaml-mp/src/main/java/io/helidon/config/yaml/mp/YamlMpConfigSource.java b/config/yaml-mp/src/main/java/io/helidon/config/yaml/mp/YamlMpConfigSource.java index eaf70de1f02..34f95df0e5e 100644 --- a/config/yaml-mp/src/main/java/io/helidon/config/yaml/mp/YamlMpConfigSource.java +++ b/config/yaml-mp/src/main/java/io/helidon/config/yaml/mp/YamlMpConfigSource.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or 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,6 +39,7 @@ import io.helidon.config.MutabilitySupport; import org.eclipse.microprofile.config.spi.ConfigSource; +import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; @@ -380,7 +381,7 @@ public String getName() { static Map toMap(Reader reader) { // the default of Snake YAML is a Map, safe constructor makes sure we never deserialize into anything // harmful - Yaml yaml = new Yaml(new SafeConstructor()); + Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions())); return (Map) yaml.loadAs(reader, Object.class); } } 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 686e18eaf81..47312d1b5ad 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 @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2022 Oracle and/or its affiliates. + * Copyright (c) 2020, 2023 Oracle and/or 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 io.helidon.config.spi.ConfigParser; import io.helidon.config.spi.ConfigParserException; +import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.Yaml; import org.yaml.snakeyaml.constructor.SafeConstructor; @@ -113,7 +114,7 @@ public ObjectNode parse(Content content) throws ConfigParserException { static Map toMap(Reader reader) { // the default of Snake YAML is a Map, safe constructor makes sure we never deserialize into anything // harmful - Yaml yaml = new Yaml(new SafeConstructor()); + Yaml yaml = new Yaml(new SafeConstructor(new LoaderOptions())); return (Map) yaml.loadAs(reader, Object.class); } diff --git a/dependencies/pom.xml b/dependencies/pom.xml index cc5886e9285..76725507d8a 100644 --- a/dependencies/pom.xml +++ b/dependencies/pom.xml @@ -143,7 +143,7 @@ 0.9.0 2.0.0 2.1.16 - 1.32 + 2.0 1.4.2 2.0.4 5.0.SP3 diff --git a/examples/todo-app/backend/pom.xml b/examples/todo-app/backend/pom.xml index 944b03da727..5d52c162ade 100644 --- a/examples/todo-app/backend/pom.xml +++ b/examples/todo-app/backend/pom.xml @@ -42,6 +42,7 @@ 4.9.0 4.9.0 3.0.2 + 1.32 @@ -57,6 +58,11 @@ + + org.yaml + snakeyaml + ${version.lib.snakeyaml.override} + diff --git a/microprofile/tests/tck/tck-openapi/pom.xml b/microprofile/tests/tck/tck-openapi/pom.xml index 2d582010cbf..f5cb6d53773 100644 --- a/microprofile/tests/tck/tck-openapi/pom.xml +++ b/microprofile/tests/tck/tck-openapi/pom.xml @@ -30,6 +30,22 @@ tck-openapi Helidon Microprofile Tests TCK OpenAPI + + 1.32 + + + + + + + org.yaml + snakeyaml + ${selectedSnakeYamlVersion} + + + + io.helidon.microprofile.tests diff --git a/nima/openapi/src/main/java/io/helidon/nima/openapi/OpenApiService.java b/nima/openapi/src/main/java/io/helidon/nima/openapi/OpenApiService.java index 50600755bab..39babafdb22 100644 --- a/nima/openapi/src/main/java/io/helidon/nima/openapi/OpenApiService.java +++ b/nima/openapi/src/main/java/io/helidon/nima/openapi/OpenApiService.java @@ -739,7 +739,9 @@ private OpenApiStaticFile getExplicitStaticFile() { + " is not one of recognized types: " + OpenAPIMediaType.recognizedFileTypes())); - try (InputStream is = new BufferedInputStream(Files.newInputStream(path))) { + // DO NOT use try-with-close; the stream needs to remain open because it is not consumed until later. + try { + InputStream is = new BufferedInputStream(Files.newInputStream(path)); LOGGER.log(Level.DEBUG, () -> String.format( OPENAPI_EXPLICIT_STATIC_FILE_LOG_MESSAGE_FORMAT, diff --git a/openapi/src/main/java/io/helidon/openapi/CustomConstructor.java b/openapi/src/main/java/io/helidon/openapi/CustomConstructor.java index 2e1e4276780..9dd8d074ce6 100644 --- a/openapi/src/main/java/io/helidon/openapi/CustomConstructor.java +++ b/openapi/src/main/java/io/helidon/openapi/CustomConstructor.java @@ -29,6 +29,7 @@ import org.eclipse.microprofile.openapi.models.responses.APIResponse; import org.eclipse.microprofile.openapi.models.responses.APIResponses; import org.eclipse.microprofile.openapi.models.security.SecurityRequirement; +import org.yaml.snakeyaml.LoaderOptions; import org.yaml.snakeyaml.TypeDescription; import org.yaml.snakeyaml.constructor.Constructor; import org.yaml.snakeyaml.error.Mark; @@ -148,7 +149,7 @@ record ChildMapType(Class

parentType, Function, ExpandedTypeDescription.MapLikeTypeDescription> typeDescriptionFactory) { } /** - * Type information about a map-resembling interface in which a child can have 0, 1, or more values i.e., the child is + * Type information about a map-resembling interface in which a child can have 0, 1, or more values (i.e., the child is * a list). * * @param

parent type @@ -165,7 +166,7 @@ record ChildMapListType( private static final System.Logger LOGGER = System.getLogger(CustomConstructor.class.getName()); CustomConstructor(TypeDescription td) { - super(td); + super(td, new LoaderOptions()); yamlClassConstructors.put(NodeId.mapping, new ConstructMapping()); } diff --git a/openapi/src/main/java/io/helidon/openapi/ImplTypeDescription.java b/openapi/src/main/java/io/helidon/openapi/ImplTypeDescription.java index 6d315ad81a3..e00dbc4d563 100644 --- a/openapi/src/main/java/io/helidon/openapi/ImplTypeDescription.java +++ b/openapi/src/main/java/io/helidon/openapi/ImplTypeDescription.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2020, 2021 Oracle and/or its affiliates. + * Copyright (c) 2020, 2023 Oracle and/or its affiliates. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -61,46 +61,18 @@ public Tag getTag() { return delegate.getTag(); } - @Override - public void setTag(Tag tag) { - delegate.setTag(tag); - } - - @Override - public void setTag(String tag) { - delegate.setTag(tag); - } - @Override @Deprecated public void putListPropertyType(String property, Class type) { delegate.putListPropertyType(property, type); } - @Override - @Deprecated - public Class getListPropertyType(String property) { - return delegate.getListPropertyType(property); - } - @Override @Deprecated public void putMapPropertyType(String property, Class key, Class value) { delegate.putMapPropertyType(property, key, value); } - @Override - @Deprecated - public Class getMapKeyType(String property) { - return delegate.getMapKeyType(property); - } - - @Override - @Deprecated - public Class getMapValueType(String property) { - return delegate.getMapValueType(property); - } - @Override public void addPropertyParameters(String pName, Class... classes) { delegate.addPropertyParameters(pName, classes); diff --git a/openapi/src/main/java/io/helidon/openapi/ParserHelper.java b/openapi/src/main/java/io/helidon/openapi/ParserHelper.java index 3e9050fcf14..a832eb38843 100644 --- a/openapi/src/main/java/io/helidon/openapi/ParserHelper.java +++ b/openapi/src/main/java/io/helidon/openapi/ParserHelper.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2022 Oracle and/or its affiliates. + * Copyright (c) 2022, 2023 Oracle and/or 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,13 @@ * Wraps generated parser and uses {@link io.helidon.openapi.ExpandedTypeDescription} as its type. */ public class ParserHelper { + + // Temporary to suppress SnakeYAML warnings. + // As a static we keep a reference to the logger, thereby making sure any changes we make are persistent. (JUL holds + // only weak references to loggers internally.) + private static final java.util.logging.Logger SNAKE_YAML_INTROSPECTOR_LOGGER = + java.util.logging.Logger.getLogger(org.yaml.snakeyaml.introspector.PropertySubstitute.class.getPackage().getName()); + /** * The SnakeYAMLParserHelper is generated by a maven plug-in. */ @@ -39,6 +46,10 @@ public class ParserHelper { private ParserHelper(SnakeYAMLParserHelper generatedHelper) { this.generatedHelper = generatedHelper; + boolean warningsEnabled = Boolean.getBoolean("openapi.parsing.warnings.enabled"); + if (SNAKE_YAML_INTROSPECTOR_LOGGER.isLoggable(java.util.logging.Level.WARNING) && !warningsEnabled) { + SNAKE_YAML_INTROSPECTOR_LOGGER.setLevel(java.util.logging.Level.SEVERE); + } } /** diff --git a/openapi/src/main/java/io/helidon/openapi/Serializer.java b/openapi/src/main/java/io/helidon/openapi/Serializer.java index d7c91c1193d..514c3c2891d 100644 --- a/openapi/src/main/java/io/helidon/openapi/Serializer.java +++ b/openapi/src/main/java/io/helidon/openapi/Serializer.java @@ -114,7 +114,6 @@ static class CustomRepresenter extends Representer { private static final String EXTENSIONS = "extensions"; - private final DumperOptions dumperOptions; private final DumperOptions.ScalarStyle stringStyle; private final Map, ExpandedTypeDescription> implsToTypes; @@ -122,8 +121,8 @@ static class CustomRepresenter extends Representer { CustomRepresenter(Map, ExpandedTypeDescription> types, Map, ExpandedTypeDescription> implsToTypes, DumperOptions dumperOptions, DumperOptions.ScalarStyle stringStyle) { + super(dumperOptions); this.implsToTypes = implsToTypes; - this.dumperOptions = dumperOptions; this.stringStyle = stringStyle; types.values().stream() .map(ImplTypeDescription::new) diff --git a/openapi/src/main/java/module-info.java b/openapi/src/main/java/module-info.java index 12e10c17097..830b6cd537b 100644 --- a/openapi/src/main/java/module-info.java +++ b/openapi/src/main/java/module-info.java @@ -42,6 +42,7 @@ requires transitive microprofile.openapi.api; requires static io.helidon.config.metadata; + requires java.logging; // temporary to adjust SnakeYAML logger level exports io.helidon.openapi; exports io.helidon.openapi.internal to io.helidon.microprofile.openapi, io.helidon.reactive.openapi, io.helidon.nima.openapi; diff --git a/tests/integration/gh-5792-nima/README.md b/tests/integration/gh-5792-nima/README.md new file mode 100644 index 00000000000..66f0618e73f --- /dev/null +++ b/tests/integration/gh-5792-nima/README.md @@ -0,0 +1,21 @@ + +# helidon-tests-integration-yaml-parsing + +Sample Helidon Níma project to make sure that we can build and run using an older release of SnakeYAML in case users need to fall back. + +Note that the static OpenAPI document packaged into the application JAR file intentionally _does not_ describe the API for this service. +It contains a much richer definition to exercise YAML parsing a bit more. + +## Build and run + +With JDK19+ + ```bash + mvn package +java --enable-preview -jar target/helidon-tests-integration-yaml-parsing.jar + ``` + +## Try OpenAPI + + ``` + curl -s -X GET http://localhost:8080/openapi +``` diff --git a/tests/integration/gh-5792-nima/pom.xml b/tests/integration/gh-5792-nima/pom.xml new file mode 100644 index 00000000000..99dcffd153f --- /dev/null +++ b/tests/integration/gh-5792-nima/pom.xml @@ -0,0 +1,117 @@ + + + + + 4.0.0 + + io.helidon.applications + helidon-nima + 4.0.0-SNAPSHOT + ../../../applications/nima/pom.xml + + io.helidon.tests.integration + helidon-tests-integration-yaml-parsing-nima + 4.0.0-SNAPSHOT + + Helidon SnakeYAML Earlier Version Test - Nima + + + io.helidon.tests.integration.yamlparsing.Main + 1.32 + + + + + + + org.yaml + snakeyaml + ${selectedSnakeYamlVersion} + + + + + + + io.helidon.nima.webserver + helidon-nima-webserver + + + io.helidon.nima.webclient + helidon-nima-webclient + + + io.helidon.nima.http.media + helidon-nima-http-media-jsonp + + + io.helidon.nima.openapi + helidon-nima-openapi + + + io.helidon.config + helidon-config-yaml + + + jakarta.json + jakarta.json-api + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + io.helidon.nima.testing.junit5 + helidon-nima-testing-junit5-webserver + test + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${selectedSnakeYamlVersion} + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + + diff --git a/tests/integration/gh-5792-nima/src/main/java/io/helidon/tests/integration/yamlparsing/GreetClientHttp.java b/tests/integration/gh-5792-nima/src/main/java/io/helidon/tests/integration/yamlparsing/GreetClientHttp.java new file mode 100644 index 00000000000..d175a024b7e --- /dev/null +++ b/tests/integration/gh-5792-nima/src/main/java/io/helidon/tests/integration/yamlparsing/GreetClientHttp.java @@ -0,0 +1,51 @@ +/* + * Copyright (c) 2023 Oracle and/or 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.tests.integration.yamlparsing; + +import io.helidon.common.http.Http; +import io.helidon.nima.webclient.WebClient; +import io.helidon.nima.webclient.http1.Http1Client; + +/** + * Executable class that invokes HTTP/1 requests against the server. + */ +public class GreetClientHttp { + private GreetClientHttp() { + } + + /** + * Main method. + * + * @param args ignored + */ + public static void main(String[] args) { + Http1Client client = WebClient.builder() + .baseUri("http://localhost:8080/greet") + .build(); + + String response = client.method(Http.Method.GET) + .request() + .as(String.class); + + System.out.println(response); + + response = client.get("Nima") + .request() + .as(String.class); + + System.out.println(response); + } +} diff --git a/tests/integration/gh-5792-nima/src/main/java/io/helidon/tests/integration/yamlparsing/GreetService.java b/tests/integration/gh-5792-nima/src/main/java/io/helidon/tests/integration/yamlparsing/GreetService.java new file mode 100644 index 00000000000..c697910278f --- /dev/null +++ b/tests/integration/gh-5792-nima/src/main/java/io/helidon/tests/integration/yamlparsing/GreetService.java @@ -0,0 +1,129 @@ +/* + * Copyright (c) 2023 Oracle and/or 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.tests.integration.yamlparsing; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; + +import io.helidon.common.http.Http; +import io.helidon.nima.webserver.http.HttpRules; +import io.helidon.nima.webserver.http.HttpService; +import io.helidon.nima.webserver.http.ServerRequest; +import io.helidon.nima.webserver.http.ServerResponse; + +import jakarta.json.Json; +import jakarta.json.JsonBuilderFactory; +import jakarta.json.JsonObject; + +/** + * A simple service to greet you. Examples: + *

+ * Get default greeting message: + * {@code curl -X GET http://localhost:8080/greet} + *

+ * Get greeting message for Joe: + * {@code curl -X GET http://localhost:8080/greet/Joe} + *

+ * Change greeting + * {@code curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting} + *

+ * The message is returned as a JSON object + */ +class GreetService implements HttpService { + + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); + + /** + * The config value for the key {@code greeting}. + */ + private final AtomicReference greeting = new AtomicReference<>(); + + GreetService() { + greeting.set("Hello"); + } + + /** + * A service registers itself by updating the routing rules. + * + * @param rules the routing rules. + */ + @Override + public void routing(HttpRules rules) { + rules + .get("/", this::getDefaultMessageHandler) + .get("/{name}", this::getMessageHandler) + .put("/greeting", this::updateGreetingHandler); + } + + /** + * Return a worldly greeting message. + * + * @param request the server request + * @param response the server response + */ + private void getDefaultMessageHandler(ServerRequest request, + ServerResponse response) { + sendResponse(response, "World"); + } + + /** + * Return a greeting message using the name that was provided. + * + * @param request the server request + * @param response the server response + */ + private void getMessageHandler(ServerRequest request, + ServerResponse response) { + String name = request.path().pathParameters().value("name"); + sendResponse(response, name); + } + + private void sendResponse(ServerResponse response, String name) { + String msg = String.format("%s %s!", greeting.get(), name); + + JsonObject returnObject = JSON.createObjectBuilder() + .add("message", msg) + .build(); + response.send(returnObject); + } + + private void updateGreetingFromJson(JsonObject jo, ServerResponse response) { + + if (!jo.containsKey("greeting")) { + JsonObject jsonErrorObject = JSON.createObjectBuilder() + .add("error", "No greeting provided") + .build(); + response.status(Http.Status.BAD_REQUEST_400) + .send(jsonErrorObject); + return; + } + + greeting.set(jo.getString("greeting")); + response.status(Http.Status.NO_CONTENT_204).send(); + } + + /** + * Set the greeting to use in future messages. + * + * @param request the server request + * @param response the server response + */ + private void updateGreetingHandler(ServerRequest request, + ServerResponse response) { + updateGreetingFromJson(request.content().as(JsonObject.class), response); + } + +} diff --git a/tests/integration/gh-5792-nima/src/main/java/io/helidon/tests/integration/yamlparsing/Main.java b/tests/integration/gh-5792-nima/src/main/java/io/helidon/tests/integration/yamlparsing/Main.java new file mode 100644 index 00000000000..022c8c6bdd1 --- /dev/null +++ b/tests/integration/gh-5792-nima/src/main/java/io/helidon/tests/integration/yamlparsing/Main.java @@ -0,0 +1,63 @@ +/* + * Copyright (c) 2023 Oracle and/or 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.tests.integration.yamlparsing; + +import io.helidon.logging.common.LogConfig; +import io.helidon.nima.openapi.OpenApiService; +import io.helidon.nima.webserver.WebServer; +import io.helidon.nima.webserver.http.HttpRouting; + +/** + * The application main class. + */ +public final class Main { + + /** + * Cannot be instantiated. + */ + private Main() { + } + + /** + * Application main entry point. + * @param args command line arguments. + */ + public static void main(final String[] args) { + // load logging configuration + LogConfig.configureRuntime(); + + WebServer server = WebServer.builder() + .routing(Main::routing) + .start(); + + System.out.println("WEB server is up! http://localhost:" + server.port() + "/greet"); + } + + /** + * Updates HTTP Routing. + */ + static void routing(HttpRouting.Builder routing) { + + OpenApiService openApiService = OpenApiService.builder() + .staticFile("target/classes/petstore.yaml") + .build(); + + GreetService greetService = new GreetService(); + + routing.register("/greet", greetService) + .register(openApiService); + } +} diff --git a/tests/integration/gh-5792-nima/src/main/java/io/helidon/tests/integration/yamlparsing/package-info.java b/tests/integration/gh-5792-nima/src/main/java/io/helidon/tests/integration/yamlparsing/package-info.java new file mode 100644 index 00000000000..b81dd6a2525 --- /dev/null +++ b/tests/integration/gh-5792-nima/src/main/java/io/helidon/tests/integration/yamlparsing/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Oracle and/or 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. + */ + +/** + * Integration test to make sure apps can use the older release of SnakeYAML if needed. + */ +package io.helidon.tests.integration.yamlparsing; diff --git a/tests/integration/gh-5792-nima/src/main/resources/application.yaml b/tests/integration/gh-5792-nima/src/main/resources/application.yaml new file mode 100644 index 00000000000..63c5f08a061 --- /dev/null +++ b/tests/integration/gh-5792-nima/src/main/resources/application.yaml @@ -0,0 +1,18 @@ +# +# Copyright (c) 2023 Oracle and/or 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. +server: + port: 8080 + host: 0.0.0.0 + diff --git a/tests/integration/gh-5792-nima/src/main/resources/logging.properties b/tests/integration/gh-5792-nima/src/main/resources/logging.properties new file mode 100644 index 00000000000..e70b8888d96 --- /dev/null +++ b/tests/integration/gh-5792-nima/src/main/resources/logging.properties @@ -0,0 +1,19 @@ +# +# Copyright (c) 2023 Oracle and/or 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. +handlers=java.util.logging.ConsoleHandler +java.util.logging.SimpleFormatter.format=%1$tY.%1$tm.%1$td %1$tH:%1$tM:%1$tS.%1$tL %5$s%6$s%n +# Global logging level. Can be overridden by specific loggers +.level=INFO +io.helidon.nima.level=INFO diff --git a/tests/integration/gh-5792-nima/src/main/resources/petstore.yaml b/tests/integration/gh-5792-nima/src/main/resources/petstore.yaml new file mode 100644 index 00000000000..aefd6ea97e0 --- /dev/null +++ b/tests/integration/gh-5792-nima/src/main/resources/petstore.yaml @@ -0,0 +1,124 @@ +# +# Copyright (c) 2023 Oracle and/or 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. +# +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + "200": + description: An paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + "201": + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + "200": + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string \ No newline at end of file diff --git a/tests/integration/gh-5792-nima/src/test/java/io/helidon/tests/integration/yamlparsing/AbstractMainTest.java b/tests/integration/gh-5792-nima/src/test/java/io/helidon/tests/integration/yamlparsing/AbstractMainTest.java new file mode 100644 index 00000000000..ce241888e8a --- /dev/null +++ b/tests/integration/gh-5792-nima/src/test/java/io/helidon/tests/integration/yamlparsing/AbstractMainTest.java @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2023 Oracle and/or 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.tests.integration.yamlparsing; + + +import io.helidon.common.http.Http; +import io.helidon.nima.testing.junit5.webserver.SetUpRoute; +import io.helidon.nima.webclient.http1.Http1Client; +import io.helidon.nima.webclient.http1.Http1ClientResponse; +import io.helidon.nima.webserver.http.HttpRouting; + +import org.junit.jupiter.api.Test; +import jakarta.json.JsonObject; + +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.Matchers.is; + +abstract class AbstractMainTest { + private final Http1Client client; + + protected AbstractMainTest(Http1Client client) { + this.client = client; + } + + @SetUpRoute + static void routing(HttpRouting.Builder builder) { + Main.routing(builder); + } + + + @Test + void testRootRoute() { + try (Http1ClientResponse response = client.get("/greet") + .request()) { + + assertThat(response.status(), is(Http.Status.OK_200)); + JsonObject json = response.as(JsonObject.class); + assertThat(json.getString("message"), is("Hello World!")); + } + } + + @Test + void testOpenApi() { + try (Http1ClientResponse response = client.get("/openapi").request()) { + assertThat("/openapi status", response.status(), is(Http.Status.OK_200)); + assertThat("/openapi content", response.as(String.class), containsString("title: Swagger Petstore")); + } + } + +} diff --git a/tests/integration/gh-5792-nima/src/test/java/io/helidon/tests/integration/yamlparsing/MainIT.java b/tests/integration/gh-5792-nima/src/test/java/io/helidon/tests/integration/yamlparsing/MainIT.java new file mode 100644 index 00000000000..2fc907f441f --- /dev/null +++ b/tests/integration/gh-5792-nima/src/test/java/io/helidon/tests/integration/yamlparsing/MainIT.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Oracle and/or 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.tests.integration.yamlparsing; + +import io.helidon.nima.testing.junit5.webserver.ServerTest; +import io.helidon.nima.webclient.http1.Http1Client; + +@ServerTest +class MainIT extends AbstractMainTest { + MainIT(Http1Client client) { + super(client); + } +} \ No newline at end of file diff --git a/tests/integration/gh-5792-nima/src/test/java/io/helidon/tests/integration/yamlparsing/MainTest.java b/tests/integration/gh-5792-nima/src/test/java/io/helidon/tests/integration/yamlparsing/MainTest.java new file mode 100644 index 00000000000..5c7a5e3a8ac --- /dev/null +++ b/tests/integration/gh-5792-nima/src/test/java/io/helidon/tests/integration/yamlparsing/MainTest.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2023 Oracle and/or 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.tests.integration.yamlparsing; + +import io.helidon.nima.testing.junit5.webserver.DirectClient; +import io.helidon.nima.testing.junit5.webserver.RoutingTest; + +@RoutingTest +class MainTest extends AbstractMainTest { + MainTest(DirectClient client) { + super(client); + } +} \ No newline at end of file diff --git a/tests/integration/gh-5792-nima/src/test/resources/application.yaml b/tests/integration/gh-5792-nima/src/test/resources/application.yaml new file mode 100644 index 00000000000..850e85592c4 --- /dev/null +++ b/tests/integration/gh-5792-nima/src/test/resources/application.yaml @@ -0,0 +1,20 @@ +# +# Copyright (c) 2023 Oracle and/or 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. +server: + port: 0 + host: 0.0.0.0 + +app: + greeting: "Hello" \ No newline at end of file diff --git a/tests/integration/gh-5792/README.md b/tests/integration/gh-5792/README.md new file mode 100644 index 00000000000..fc289363cc6 --- /dev/null +++ b/tests/integration/gh-5792/README.md @@ -0,0 +1,126 @@ +# helidon-tests-integration-yaml-parsing + +Sample Helidon SE project that includes multiple REST operations. + +## Build and run + + +With JDK19+ +```bash +mvn package +java --enable-preview -jar target/helidon-tests-integration-yaml-parsing.jar +``` + +## Exercise the application +``` +curl -X GET http://localhost:8080/simple-greet +{"message":"Hello World!"} +``` +``` +curl -X GET http://localhost:8080/greet +{"message":"Hello World!"} + +curl -X GET http://localhost:8080/greet/Joe +{"message":"Hello Joe!"} + +curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Hola"}' http://localhost:8080/greet/greeting + +curl -X GET http://localhost:8080/greet/Jose +{"message":"Hola Jose!"} +``` + + +## Try metrics + +``` +# Prometheus Format +curl -s -X GET http://localhost:8080/metrics +# TYPE base:gc_g1_young_generation_count gauge +. . . + +# JSON Format +curl -H 'Accept: application/json' -X GET http://localhost:8080/metrics +{"base":... +. . . +``` + + +## Try health + +``` +curl -s -X GET http://localhost:8080/health +{"outcome":"UP",... + +``` + + +## Building a Native Image + +Make sure you have GraalVM locally installed: + +``` +$GRAALVM_HOME/bin/native-image --version +``` + +Build the native image using the native image profile: + +``` +mvn package -Pnative-image +``` + +This uses the helidon-maven-plugin to perform the native compilation using your installed copy of GraalVM. It might take a while to complete. +Once it completes start the application using the native executable (no JVM!): + +``` +./target/helidon-tests-integration-yaml-parsing +``` + +Yep, it starts fast. You can exercise the application’s endpoints as before. +## Building the Docker Image + +``` +docker build -t helidon-tests-integration-yaml-parsing . +``` + +## Running the Docker Image + +``` +docker run --rm -p 8080:8080 helidon-tests-integration-yaml-parsing:latest +``` + +Exercise the application as described above. + +## Building a Custom Runtime Image + +Build the custom runtime image using the jlink image profile: + +``` +mvn package -Pjlink-image +``` + +This uses the helidon-maven-plugin to perform the custom image generation. +After the build completes it will report some statistics about the build including the reduction in image size. + +The target/helidon-tests-integration-yaml-parsing-jri directory is a self contained custom image of your application. It contains your application, +its runtime dependencies and the JDK modules it depends on. You can start your application using the provide start script: + +``` +./target/helidon-tests-integration-yaml-parsing-jri/bin/start +``` + +Class Data Sharing (CDS) Archive +Also included in the custom image is a Class Data Sharing (CDS) archive that improves your application’s startup +performance and in-memory footprint. You can learn more about Class Data Sharing in the JDK documentation. + +The CDS archive increases your image size to get these performance optimizations. It can be of significant size (tens of MB). +The size of the CDS archive is reported at the end of the build output. + +If you’d rather have a smaller image size (with a slightly increased startup time) you can skip the creation of the CDS +archive by executing your build like this: + +``` +mvn package -Pjlink-image -Djlink.image.addClassDataSharingArchive=false +``` + +For more information on available configuration options see the helidon-maven-plugin documentation. + \ No newline at end of file diff --git a/tests/integration/gh-5792/pom.xml b/tests/integration/gh-5792/pom.xml new file mode 100644 index 00000000000..2b90aa88269 --- /dev/null +++ b/tests/integration/gh-5792/pom.xml @@ -0,0 +1,113 @@ + + + + + 4.0.0 + + io.helidon.applications + helidon-se + 4.0.0-SNAPSHOT + ../../../applications/se/pom.xml + + io.helidon.tests.integration + helidon-tests-integration-yaml-parsing + 4.0.0-SNAPSHOT + + Helidon SnakeYAML Earlier Version Test - Reactive + + io.helidon.tests.integration.yamlparsing.Main + 1.32 + + + + + + + org.yaml + snakeyaml + ${selectedSnakeYamlVersion} + + + + + + + io.helidon.reactive.webserver + helidon-reactive-webserver + + + io.helidon.reactive.media + helidon-reactive-media-jsonp + + + io.helidon.reactive.openapi + helidon-reactive-openapi + + + io.helidon.config + helidon-config-yaml + + + io.helidon.reactive.webclient + helidon-reactive-webclient + test + + + io.helidon.common + helidon-common-media-type + test + + + org.junit.jupiter + junit-jupiter-api + test + + + org.hamcrest + hamcrest-all + test + + + + + + + org.apache.maven.plugins + maven-dependency-plugin + + + copy-libs + + + + + org.apache.maven.plugins + maven-surefire-plugin + + + ${selectedSnakeYamlVersion} + + + + + + diff --git a/tests/integration/gh-5792/src/main/java/io/helidon/tests/integration/yamlparsing/GreetService.java b/tests/integration/gh-5792/src/main/java/io/helidon/tests/integration/yamlparsing/GreetService.java new file mode 100644 index 00000000000..5389690545b --- /dev/null +++ b/tests/integration/gh-5792/src/main/java/io/helidon/tests/integration/yamlparsing/GreetService.java @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2023 Oracle and/or 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.tests.integration.yamlparsing; + +import java.util.Collections; +import java.util.concurrent.atomic.AtomicReference; +import java.util.logging.Level; +import java.util.logging.Logger; + +import jakarta.json.Json; +import jakarta.json.JsonBuilderFactory; +import jakarta.json.JsonException; +import jakarta.json.JsonObject; + +import io.helidon.common.http.Http; +import io.helidon.config.Config; +import io.helidon.reactive.webserver.Routing; +import io.helidon.reactive.webserver.ServerRequest; +import io.helidon.reactive.webserver.ServerResponse; +import io.helidon.reactive.webserver.Service; + +/** + * A simple service to greet you. Examples: + * + * Get default greeting message: + * curl -X GET http://localhost:8080/greet + * + * Get greeting message for Joe: + * curl -X GET http://localhost:8080/greet/Joe + * + * Change greeting + * curl -X PUT -H "Content-Type: application/json" -d '{"greeting" : "Howdy"}' http://localhost:8080/greet/greeting + * + * The message is returned as a JSON object + */ + +public class GreetService implements Service { + + /** + * The config value for the key {@code greeting}. + */ + private final AtomicReference greeting = new AtomicReference<>(); + + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); + + private static final Logger LOGGER = Logger.getLogger(GreetService.class.getName()); + + GreetService(Config config) { + greeting.set(config.get("app.greeting").asString().orElse("Ciao")); + } + + /** + * A service registers itself by updating the routing rules. + * @param rules the routing rules. + */ + @Override + public void update(Routing.Rules rules) { + rules + .get("/", this::getDefaultMessageHandler) + .get("/{name}", this::getMessageHandler) + .put("/greeting", this::updateGreetingHandler); + } + + /** + * Return a worldly greeting message. + * @param request the server request + * @param response the server response + */ + private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { + sendResponse(response, "World"); + } + + /** + * Return a greeting message using the name that was provided. + * @param request the server request + * @param response the server response + */ + private void getMessageHandler(ServerRequest request, ServerResponse response) { + String name = request.path().param("name"); + sendResponse(response, name); + } + + private void sendResponse(ServerResponse response, String name) { + String msg = String.format("%s %s!", greeting.get(), name); + + JsonObject returnObject = JSON.createObjectBuilder() + .add("message", msg) + .build(); + response.send(returnObject); + } + + private static T processErrors(Throwable ex, ServerRequest request, ServerResponse response) { + + if (ex.getCause() instanceof JsonException){ + + LOGGER.log(Level.FINE, "Invalid JSON", ex); + JsonObject jsonErrorObject = JSON.createObjectBuilder() + .add("error", "Invalid JSON") + .build(); + response.status(Http.Status.BAD_REQUEST_400).send(jsonErrorObject); + } else { + + LOGGER.log(Level.FINE, "Internal error", ex); + JsonObject jsonErrorObject = JSON.createObjectBuilder() + .add("error", "Internal error") + .build(); + response.status(Http.Status.INTERNAL_SERVER_ERROR_500).send(jsonErrorObject); + } + + return null; + } + + private void updateGreetingFromJson(JsonObject jo, ServerResponse response) { + if (!jo.containsKey("greeting")) { + JsonObject jsonErrorObject = JSON.createObjectBuilder() + .add("error", "No greeting provided") + .build(); + response.status(Http.Status.BAD_REQUEST_400) + .send(jsonErrorObject); + return; + } + + greeting.set(jo.getString("greeting")); + response.status(Http.Status.NO_CONTENT_204).send(); + } + + /** + * Set the greeting to use in future messages. + * @param request the server request + * @param response the server response + */ + private void updateGreetingHandler(ServerRequest request, + ServerResponse response) { + request.content().as(JsonObject.class) + .thenAccept(jo -> updateGreetingFromJson(jo, response)) + .exceptionally(ex -> processErrors(ex, request, response)); + } +} diff --git a/tests/integration/gh-5792/src/main/java/io/helidon/tests/integration/yamlparsing/Main.java b/tests/integration/gh-5792/src/main/java/io/helidon/tests/integration/yamlparsing/Main.java new file mode 100644 index 00000000000..b85da78e8c8 --- /dev/null +++ b/tests/integration/gh-5792/src/main/java/io/helidon/tests/integration/yamlparsing/Main.java @@ -0,0 +1,98 @@ +/* + * Copyright (c) 2023 Oracle and/or 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.tests.integration.yamlparsing; + + +import io.helidon.reactive.media.jsonp.JsonpSupport; +import io.helidon.logging.common.LogConfig; +import io.helidon.common.reactive.Single; +import io.helidon.config.Config; +import io.helidon.reactive.openapi.OpenAPISupport; +import io.helidon.reactive.webserver.Routing; +import io.helidon.reactive.webserver.WebServer; + +/** + * The application main class. + */ +public final class Main { + + /** + * Cannot be instantiated. + */ + private Main() { + } + + /** + * Application main entry point. + * @param args command line arguments. + */ + public static void main(final String[] args) { + startServer(); + } + + /** + * Start the server. + * @return the created {@link WebServer} instance + */ + static Single startServer() { + + // load logging configuration + LogConfig.configureRuntime(); + + // By default this will pick up application.yaml from the classpath + Config config = Config.create(); + + WebServer server = WebServer.builder(createRouting(config)) + .config(config.get("server")) + .addMediaSupport(JsonpSupport.create()) .build(); + + Single webserver = server.start(); + + // Try to start the server. If successful, print some info and arrange to + // print a message at shutdown. If unsuccessful, print the exception. + webserver.thenAccept(ws -> { + System.out.println("WEB server is up! http://localhost:" + ws.port() + "/greet"); + ws.whenShutdown().thenRun(() -> System.out.println("WEB server is DOWN. Good bye!")); + }) + .exceptionallyAccept(t -> { + System.err.println("Startup failed: " + t.getMessage()); + t.printStackTrace(System.err); + }); + + return webserver; + } + + /** + * Creates new {@link io.helidon.reactive.webserver.Routing}. + * + * @return routing configured with JSON support, a health check, and a service + * @param config configuration of this server + */ + private static Routing createRouting(Config config) { + SimpleGreetService simpleGreetService = new SimpleGreetService(config); + GreetService greetService = new GreetService(config); + OpenAPISupport openApiSupport = OpenAPISupport.builder() + .staticFile("target/classes/petstore.yaml") + .build(); + Routing.Builder builder = Routing.builder() + .register(openApiSupport) + .register("/simple-greet", simpleGreetService) + .register("/greet", greetService); + + + return builder.build(); + } +} diff --git a/tests/integration/gh-5792/src/main/java/io/helidon/tests/integration/yamlparsing/SimpleGreetService.java b/tests/integration/gh-5792/src/main/java/io/helidon/tests/integration/yamlparsing/SimpleGreetService.java new file mode 100644 index 00000000000..0c9f98a0278 --- /dev/null +++ b/tests/integration/gh-5792/src/main/java/io/helidon/tests/integration/yamlparsing/SimpleGreetService.java @@ -0,0 +1,81 @@ +/* + * Copyright (c) 2023 Oracle and/or 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.tests.integration.yamlparsing; + +import java.util.Collections; +import java.util.logging.Logger; + +import jakarta.json.Json; +import jakarta.json.JsonBuilderFactory; +import jakarta.json.JsonObject; + +import io.helidon.config.Config; +import io.helidon.reactive.webserver.Routing; +import io.helidon.reactive.webserver.ServerRequest; +import io.helidon.reactive.webserver.ServerResponse; +import io.helidon.reactive.webserver.Service; + +/** + * A simple service to greet you. Examples: + * + * Get default greeting message: + * curl -X GET http://localhost:8080/simple-greet + * + * The message is returned as a JSON object + */ +public class SimpleGreetService implements Service { + + private static final Logger LOGGER = Logger.getLogger(SimpleGreetService.class.getName()); + private static final JsonBuilderFactory JSON = Json.createBuilderFactory(Collections.emptyMap()); + + private final String greeting; + + SimpleGreetService(Config config) { + greeting = config.get("app.greeting").asString().orElse("Ciao"); + } + + + /** + * A service registers itself by updating the routing rules. + * + * @param rules the routing rules. + */ + @Override + public void update(Routing.Rules rules) { + rules.get("/", this::getDefaultMessageHandler); + rules.get("/greet-count", this::countAccess, this::getDefaultMessageHandler); + } + + /** + * Return a worldly greeting message. + * + * @param request the server request + * @param response the server response + */ + private void getDefaultMessageHandler(ServerRequest request, ServerResponse response) { + String msg = String.format("%s %s!", greeting, "World"); + LOGGER.info("Greeting message is " + msg); + JsonObject returnObject = JSON.createObjectBuilder() + .add("message", msg) + .build(); + response.send(returnObject); + } + + + private void countAccess(ServerRequest request, ServerResponse response) { + request.next(); + } +} diff --git a/tests/integration/gh-5792/src/main/java/io/helidon/tests/integration/yamlparsing/package-info.java b/tests/integration/gh-5792/src/main/java/io/helidon/tests/integration/yamlparsing/package-info.java new file mode 100644 index 00000000000..d754dcafe1b --- /dev/null +++ b/tests/integration/gh-5792/src/main/java/io/helidon/tests/integration/yamlparsing/package-info.java @@ -0,0 +1,20 @@ +/* + * Copyright (c) 2023 Oracle and/or 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. + */ + +/** + * Tests of YAML parsing with an older SnakeYAML release. + */ +package io.helidon.tests.integration.yamlparsing; diff --git a/tests/integration/gh-5792/src/main/resources/application.yaml b/tests/integration/gh-5792/src/main/resources/application.yaml new file mode 100644 index 00000000000..a62827b24ef --- /dev/null +++ b/tests/integration/gh-5792/src/main/resources/application.yaml @@ -0,0 +1,21 @@ +# +# Copyright (c) 2023 Oracle and/or 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. + +server: + port: 8080 + host: 0.0.0.0 + +app: + greeting: "Hello" diff --git a/tests/integration/gh-5792/src/main/resources/logging.properties b/tests/integration/gh-5792/src/main/resources/logging.properties new file mode 100644 index 00000000000..b0b8dcf9994 --- /dev/null +++ b/tests/integration/gh-5792/src/main/resources/logging.properties @@ -0,0 +1,33 @@ +# +# Copyright (c) 2023 Oracle and/or 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.reactive.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/tests/integration/gh-5792/src/main/resources/petstore.yaml b/tests/integration/gh-5792/src/main/resources/petstore.yaml new file mode 100644 index 00000000000..b15eb29dac1 --- /dev/null +++ b/tests/integration/gh-5792/src/main/resources/petstore.yaml @@ -0,0 +1,124 @@ +# +# Copyright (c) 2023 Oracle and/or 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. +# +openapi: "3.0.0" +info: + version: 1.0.0 + title: Swagger Petstore + license: + name: MIT +servers: + - url: http://petstore.swagger.io/v1 +paths: + /pets: + get: + summary: List all pets + operationId: listPets + tags: + - pets + parameters: + - name: limit + in: query + description: How many items to return at one time (max 100) + required: false + schema: + type: integer + format: int32 + responses: + "200": + description: An paged array of pets + headers: + x-next: + description: A link to the next page of responses + schema: + type: string + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + post: + summary: Create a pet + operationId: createPets + tags: + - pets + responses: + "201": + description: Null response + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" + /pets/{petId}: + get: + summary: Info for a specific pet + operationId: showPetById + tags: + - pets + parameters: + - name: petId + in: path + required: true + description: The id of the pet to retrieve + schema: + type: string + responses: + "200": + description: Expected response to a valid request + content: + application/json: + schema: + $ref: "#/components/schemas/Pets" + default: + description: unexpected error + content: + application/json: + schema: + $ref: "#/components/schemas/Error" +components: + schemas: + Pet: + required: + - id + - name + properties: + id: + type: integer + format: int64 + name: + type: string + tag: + type: string + Pets: + type: array + items: + $ref: "#/components/schemas/Pet" + Error: + required: + - code + - message + properties: + code: + type: integer + format: int32 + message: + type: string diff --git a/tests/integration/gh-5792/src/test/java/io/helidon/tests/integration/yamlparsing/MainTest.java b/tests/integration/gh-5792/src/test/java/io/helidon/tests/integration/yamlparsing/MainTest.java new file mode 100644 index 00000000000..82a6a6188f7 --- /dev/null +++ b/tests/integration/gh-5792/src/test/java/io/helidon/tests/integration/yamlparsing/MainTest.java @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2023 Oracle and/or 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.tests.integration.yamlparsing; + +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.TimeUnit; +import java.util.Collections; +import jakarta.json.Json; +import jakarta.json.JsonBuilderFactory; +import jakarta.json.JsonObject; + +import io.helidon.common.media.type.MediaTypes; +import io.helidon.reactive.media.jsonp.JsonpSupport; +import io.helidon.reactive.webclient.WebClient; +import io.helidon.reactive.webclient.WebClientResponse; +import io.helidon.reactive.webserver.WebServer; + +import org.junit.jupiter.api.AfterAll; +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.Test; + +import static org.hamcrest.Matchers.containsString; +import static org.hamcrest.MatcherAssert.assertThat; +import static org.hamcrest.Matchers.is; +import static org.hamcrest.Matchers.notNullValue; + +class MainTest { + + private static final JsonBuilderFactory JSON_BUILDER = Json.createBuilderFactory(Collections.emptyMap()); + private static final JsonObject TEST_JSON_OBJECT = JSON_BUILDER.createObjectBuilder() + .add("greeting", "Hola") + .build(); + private static WebServer webServer; + private static WebClient webClient; + + @BeforeAll + static void startTheServer() { + webServer = Main.startServer().await(); + + webClient = WebClient.builder() + .baseUri("http://localhost:" + webServer.port()) + .addMediaSupport(JsonpSupport.create()) + .build(); + } + + @AfterAll + static void stopServer() throws ExecutionException, InterruptedException, TimeoutException { + if (webServer != null) { + webServer.shutdown() + .toCompletableFuture() + .get(10, TimeUnit.SECONDS); + } + } + + @Test + void testOpenApi() { + String get = webClient.get() + .path("/openapi") + .accept(MediaTypes.APPLICATION_OPENAPI_YAML) + .request(String.class) + .await(); + + assertThat(get, containsString("title: Swagger Petstore")); + } + + @Test + void testSimpleGreet() { + JsonObject jsonObject = webClient.get() + .path("/simple-greet") + .request(JsonObject.class) + .await(); + assertThat(jsonObject.getString("message"), is("Hello World!")); + } + + @Test + void testGreetings() { + JsonObject jsonObject; + WebClientResponse response; + + jsonObject = webClient.get() + .path("/greet/Joe") + .request(JsonObject.class) + .await(); + assertThat(jsonObject.getString("message"), is("Hello Joe!")); + + response = webClient.put() + .path("/greet/greeting") + .submit(TEST_JSON_OBJECT) + .await(); + assertThat(response.status().code(), is(204)); + + jsonObject = webClient.get() + .path("/greet/Joe") + .request(JsonObject.class) + .await(); + assertThat(jsonObject.getString("message"), is("Hola Joe!")); + } + + @Test + void verifySnakeYamlVersion() { + String selectedSnakeYamlVersion = System.getProperty("selected-snakeyaml-version"); + assertThat("Spec of selected SnakeYAML version", selectedSnakeYamlVersion, is(notNullValue())); + + String classpath = System.getProperty("java.class.path"); + assertThat("SnakeYAML version in classpath", + classpath, + containsString("snakeyaml-" + selectedSnakeYamlVersion)); + } +} diff --git a/tests/integration/gh-5792/src/test/resources/application.yaml b/tests/integration/gh-5792/src/test/resources/application.yaml new file mode 100644 index 00000000000..e7a165b42dd --- /dev/null +++ b/tests/integration/gh-5792/src/test/resources/application.yaml @@ -0,0 +1,23 @@ +# +# Copyright (c) 2023 Oracle and/or 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. +server: + port: 0 + host: 0.0.0.0 + +app: + greeting: "Hello" + +security: + enabled: false diff --git a/tests/integration/pom.xml b/tests/integration/pom.xml index f3faa7ded5c..2fba593105b 100644 --- a/tests/integration/pom.xml +++ b/tests/integration/pom.xml @@ -71,6 +71,8 @@ native-image vault oidc + gh-5792 + gh-5792-nima