From c49bedf06feba8242b8b7227630c53e9bf11079f Mon Sep 17 00:00:00 2001 From: "Michael G. Noll" Date: Thu, 2 May 2024 12:31:45 +0200 Subject: [PATCH] Use Quarkus 3.10.0 to implement the REST app --- Dockerfile | 2 +- README.md | 10 +- justfile | 21 ++- pom.xml | 151 +++++++++--------- .../java/com/miguno/javadockerbuild/App.java | 13 -- .../com/miguno/javadockerbuild/Greeting.java | 10 -- .../javadockerbuild/GreetingController.java | 24 --- .../miguno/javadockerbuild/LoggingFilter.java | 32 ++++ .../com/miguno/javadockerbuild/Status.java | 10 ++ .../javadockerbuild/StatusResource.java | 16 ++ src/main/resources/application.properties | 4 +- .../com/miguno/javadockerbuild/AppTests.java | 18 --- .../GreetingControllerTest.java | 42 ----- .../javadockerbuild/StatusResourceIT.java | 5 + .../javadockerbuild/StatusResourceTest.java | 21 +++ start_container.sh | 2 +- 16 files changed, 188 insertions(+), 193 deletions(-) delete mode 100644 src/main/java/com/miguno/javadockerbuild/App.java delete mode 100644 src/main/java/com/miguno/javadockerbuild/Greeting.java delete mode 100644 src/main/java/com/miguno/javadockerbuild/GreetingController.java create mode 100644 src/main/java/com/miguno/javadockerbuild/LoggingFilter.java create mode 100644 src/main/java/com/miguno/javadockerbuild/Status.java create mode 100644 src/main/java/com/miguno/javadockerbuild/StatusResource.java delete mode 100644 src/test/java/com/miguno/javadockerbuild/AppTests.java delete mode 100644 src/test/java/com/miguno/javadockerbuild/GreetingControllerTest.java create mode 100644 src/test/java/com/miguno/javadockerbuild/StatusResourceIT.java create mode 100644 src/test/java/com/miguno/javadockerbuild/StatusResourceTest.java diff --git a/Dockerfile b/Dockerfile index 4c9fe85..52ee4fd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -54,7 +54,7 @@ WORKDIR $APP_DIR COPY --from=builder /minimal-jre $JAVA_HOME # Copy packaged app from builder image. -COPY --from=builder --chown=$USER_NAME:$GROUP_NAME /usr/src/myapp/target/app.jar . +COPY --from=builder --chown=$USER_NAME:$GROUP_NAME /usr/src/myapp/target/app-runner.jar ./app.jar # Run the application. USER $USER_NAME:$GROUP_NAME diff --git a/README.md b/README.md index fb7bf2f..db9af72 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ [![License](https://img.shields.io/badge/License-Apache%202.0-blue.svg)](https://opensource.org/licenses/Apache-2.0) A template project to create a Docker image for a Java application. -The example application uses Spring Boot to expose an HTTP endpoint. +The example application uses Quarkus to expose an HTTP endpoint. > **Golang developer?** Check out https://github.com/miguno/golang-docker-build-tutorial @@ -71,8 +71,8 @@ $ docker run -p 8123:8123 miguno/java-docker-build-tutorial:latest running container. ```shell -$ curl http://localhost:8123/greeting -{"id":1,"name":"Hello, World!"} +$ curl http://localhost:8123/status +{"status":"idle"} ``` # Usage with just @@ -111,10 +111,10 @@ on Windows). $ ./mvnw clean package # Run the application locally. -$ java -jar target/app.jar +$ java -jar target/app-runner.jar # Alternatively, you can run the application via Maven. -$ ./mvnw spring-boot:run +$ ./mvnw quarkus:dev ``` # References diff --git a/justfile b/justfile index a64de9e..c75f185 100644 --- a/justfile +++ b/justfile @@ -17,6 +17,14 @@ system-info: @echo "os: {{os()}}" @echo "os family: {{os_family()}}" +# build the native application locally (requires GraalVM) +build-native: + @./mvnw install -Dnative + +# run the application locally (in Quarkus development mode) with hot reload +dev: + @./mvnw quarkus:dev + # create a docker image (requires Docker) docker-image-create: @echo "Creating a docker image ..." @@ -31,10 +39,15 @@ docker-image-run: @echo "Running container from docker image ..." @./start_container.sh +# package the application to create an uber jar +package: + @./mvnw package + +# run the application locally. +run: package + @java -jar target/app-runner.jar + # send request to the app's HTTP endpoint (requires running container) send-request-to-app: - curl http://localhost:8123/greeting + curl http://localhost:8123/status -# Run the application locally -run: - @./mvnw spring-boot:run diff --git a/pom.xml b/pom.xml index fe918ed..8f8b3e4 100644 --- a/pom.xml +++ b/pom.xml @@ -3,12 +3,7 @@ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> 4.0.0 - - org.springframework.boot - spring-boot-starter-parent - 3.2.5 - - + com.miguno java-docker-build jar @@ -19,30 +14,35 @@ Apache License 2.0 - http://www.apache.org/licenses/LICENSE-2.0.html + https://www.apache.org/licenses/LICENSE-2.0.html repo + 3.13.0 17 - 3.1.6 5.10.2 - 2.1.3 - 2.3.1 + true ${java.version} UTF-8 + true + quarkus-bom + io.quarkus.platform + 3.10.0 + 3.2.5 - org.glassfish.jersey - jersey-bom - ${jersey.version} + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} pom import + org.junit junit-bom @@ -54,44 +54,21 @@ - - org.springframework.boot - spring-boot-starter-web + io.quarkus + quarkus-rest-jackson - org.springframework.boot - spring-boot-starter-test + io.quarkus + quarkus-junit5 test - org.glassfish.jersey.containers - jersey-container-grizzly2-http - - - - org.glassfish.jersey.inject - jersey-hk2 - - - javax.inject - javax.inject - - - - - - jakarta.activation - jakarta.activation-api - ${jakarta.activation-api.version} - - - - javax.xml.bind - jaxb-api - ${jaxb-api.version} + io.rest-assured + rest-assured + test @@ -99,28 +76,26 @@ junit-jupiter test - - app - - - org.springframework.boot - spring-boot-maven-plugin - - org.apache.maven.plugins maven-compiler-plugin - 3.13.0 + ${compiler-plugin.version} ${java.version} ${java.version} @@ -130,7 +105,13 @@ org.apache.maven.plugins maven-surefire-plugin - 3.2.5 + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + ${maven.home} + + org.junit.jupiter @@ -140,33 +121,57 @@ - - + + + native + + + native + + + + native + + + + + org.apache.maven.plugins + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + org.jboss.logmanager.LogManager + ${maven.home} + + + + + + + + + + diff --git a/src/main/java/com/miguno/javadockerbuild/App.java b/src/main/java/com/miguno/javadockerbuild/App.java deleted file mode 100644 index 1b9233f..0000000 --- a/src/main/java/com/miguno/javadockerbuild/App.java +++ /dev/null @@ -1,13 +0,0 @@ -package com.miguno.javadockerbuild; - -import org.springframework.boot.SpringApplication; -import org.springframework.boot.autoconfigure.SpringBootApplication; - -@SpringBootApplication -public class App { - - public static void main(String[] args) { - SpringApplication.run(App.class, args); - } - -} diff --git a/src/main/java/com/miguno/javadockerbuild/Greeting.java b/src/main/java/com/miguno/javadockerbuild/Greeting.java deleted file mode 100644 index 174fcb0..0000000 --- a/src/main/java/com/miguno/javadockerbuild/Greeting.java +++ /dev/null @@ -1,10 +0,0 @@ -package com.miguno.javadockerbuild; - -/** - * {@link Greeting} is resource representation class, from which the eventual - * JSON response message is being derived. - * - * @param id The unique ID of the response. - * @param name The name to be greeted. - */ -public record Greeting(long id, String name) { } diff --git a/src/main/java/com/miguno/javadockerbuild/GreetingController.java b/src/main/java/com/miguno/javadockerbuild/GreetingController.java deleted file mode 100644 index 80d4904..0000000 --- a/src/main/java/com/miguno/javadockerbuild/GreetingController.java +++ /dev/null @@ -1,24 +0,0 @@ -package com.miguno.javadockerbuild; - -import java.util.concurrent.atomic.AtomicLong; - -import org.springframework.web.bind.annotation.GetMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; - -// The @RestController annotation marks this class as a controller, where every -// method returns a domain object instead of a view. It is shorthand for -// including both @Controller and @ResponseBody. -@RestController -public class GreetingController { - - private static final String template = "Hello, %s!"; - private final AtomicLong counter = new AtomicLong(); - - @GetMapping("/greeting") - public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) { - // The new Greeting instance is automatically converted to JSON via - // Spring’s HTTP message converter support (which uses Jackson 2). - return new Greeting(counter.incrementAndGet(), String.format(template, name)); - } -} \ No newline at end of file diff --git a/src/main/java/com/miguno/javadockerbuild/LoggingFilter.java b/src/main/java/com/miguno/javadockerbuild/LoggingFilter.java new file mode 100644 index 0000000..7267be3 --- /dev/null +++ b/src/main/java/com/miguno/javadockerbuild/LoggingFilter.java @@ -0,0 +1,32 @@ +package com.miguno.javadockerbuild; + +import jakarta.ws.rs.container.ContainerRequestContext; +import jakarta.ws.rs.container.ContainerRequestFilter; +import jakarta.ws.rs.core.Context; +import jakarta.ws.rs.core.UriInfo; +import jakarta.ws.rs.ext.Provider; + +import org.jboss.logging.Logger; + +import io.vertx.core.http.HttpServerRequest; + +@Provider +public class LoggingFilter implements ContainerRequestFilter { + + private static final Logger LOG = Logger.getLogger(LoggingFilter.class); + + @Context + UriInfo info; + + @Context + HttpServerRequest request; + + @Override + public void filter(ContainerRequestContext context) { + // Uncomment to enable HTTP request logging. + //final String method = context.getMethod(); + //final String path = info.getPath(); + //final String address = request.remoteAddress().toString(); + //LOG.infof("Request %s %s from IP %s", method, path, address); + } +} \ No newline at end of file diff --git a/src/main/java/com/miguno/javadockerbuild/Status.java b/src/main/java/com/miguno/javadockerbuild/Status.java new file mode 100644 index 0000000..861edc5 --- /dev/null +++ b/src/main/java/com/miguno/javadockerbuild/Status.java @@ -0,0 +1,10 @@ +package com.miguno.javadockerbuild; + +public class Status { + + public String status; + + public Status(String status) { + this.status = status; + } +} \ No newline at end of file diff --git a/src/main/java/com/miguno/javadockerbuild/StatusResource.java b/src/main/java/com/miguno/javadockerbuild/StatusResource.java new file mode 100644 index 0000000..c442ec6 --- /dev/null +++ b/src/main/java/com/miguno/javadockerbuild/StatusResource.java @@ -0,0 +1,16 @@ +package com.miguno.javadockerbuild; + +import jakarta.ws.rs.GET; +import jakarta.ws.rs.Path; + +@Path("/status") +public class StatusResource { + + final private Status status = new Status("idle"); + + @GET + public Status list() { + return status; + } + +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 6a46f86..ee9a9a8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,2 +1,2 @@ -spring.application.name=java-docker-build -server.port=8123 +quarkus.http.port=8123 +quarkus.package.jar.type=uber-jar diff --git a/src/test/java/com/miguno/javadockerbuild/AppTests.java b/src/test/java/com/miguno/javadockerbuild/AppTests.java deleted file mode 100644 index 55ec34e..0000000 --- a/src/test/java/com/miguno/javadockerbuild/AppTests.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.miguno.javadockerbuild; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -// The @SpringBootTest annotation tells Spring Boot to look for a main -// configuration class (one with @SpringBootApplication, for instance) -// and use that to start a Spring application context. -@SpringBootTest -class AppTests { - - @Test - void contextLoads() { - // This test is a simple sanity check test that will fail if the - // application context cannot start. - } - -} diff --git a/src/test/java/com/miguno/javadockerbuild/GreetingControllerTest.java b/src/test/java/com/miguno/javadockerbuild/GreetingControllerTest.java deleted file mode 100644 index 08d256f..0000000 --- a/src/test/java/com/miguno/javadockerbuild/GreetingControllerTest.java +++ /dev/null @@ -1,42 +0,0 @@ -package com.miguno.javadockerbuild; - -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; - -import org.junit.jupiter.api.Test; - -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; -import org.springframework.boot.test.context.SpringBootTest; -import org.springframework.http.MediaType; -import org.springframework.test.web.servlet.MockMvc; - -// By default, @SpringBootTest does not start the web server, but instead sets -// up a mock environment for testing web endpoints. Thus, this test covers -// almost but not entirely the full stack. -// https://docs.spring.io/spring-boot/docs/current/reference/html/features.html#features.testing -@SpringBootTest -@AutoConfigureMockMvc -class GreetingControllerTest { - - @Autowired - private MockMvc mockMvc; - - @Test - void shouldReturnDefaultResponse() throws Exception { - this.mockMvc.perform(get("/greeting").contentType(MediaType.APPLICATION_JSON)) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.name").value("Hello, World!")); - } - - @Test - void shouldReturnParameterizedResponse() throws Exception { - var name = "Foo"; - this.mockMvc.perform(get("/greeting?name=" + name).contentType(MediaType.APPLICATION_JSON)) - .andDo(print()) - .andExpect(status().isOk()) - .andExpect(jsonPath("$.name").value("Hello, " + name + "!")); - } -} \ No newline at end of file diff --git a/src/test/java/com/miguno/javadockerbuild/StatusResourceIT.java b/src/test/java/com/miguno/javadockerbuild/StatusResourceIT.java new file mode 100644 index 0000000..6fa823c --- /dev/null +++ b/src/test/java/com/miguno/javadockerbuild/StatusResourceIT.java @@ -0,0 +1,5 @@ +package com.miguno.javadockerbuild; +import io.quarkus.test.junit.QuarkusIntegrationTest; + +@QuarkusIntegrationTest +public class StatusResourceIT extends StatusResourceTest { } \ No newline at end of file diff --git a/src/test/java/com/miguno/javadockerbuild/StatusResourceTest.java b/src/test/java/com/miguno/javadockerbuild/StatusResourceTest.java new file mode 100644 index 0000000..e995b24 --- /dev/null +++ b/src/test/java/com/miguno/javadockerbuild/StatusResourceTest.java @@ -0,0 +1,21 @@ +package com.miguno.javadockerbuild; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +@QuarkusTest +public class StatusResourceTest { + + @Test + public void verifyList() { + given() + .when().get("/status") + .then() + .statusCode(200) + .body("status", is("idle")); + } + +} \ No newline at end of file diff --git a/start_container.sh b/start_container.sh index c0b2df6..c8c971a 100755 --- a/start_container.sh +++ b/start_container.sh @@ -14,6 +14,6 @@ declare -r DOCKER_OPTIONS="--platform linux/amd64" # Import environment variables from .env set -o allexport && source .env && set +o allexport echo "Starting container for image '$DOCKER_IMAGE_NAME:$DOCKER_IMAGE_TAG', exposing port ${APP_PORT}/tcp" -echo "- Run 'curl http://localhost:${APP_PORT}/greeting' to send a test request to the containerized app." +echo "- Run 'curl http://localhost:${APP_PORT}/status' to send a test request to the containerized app." echo "- Enter Ctrl-C to stop the container." docker run $DOCKER_OPTIONS -p "$APP_PORT:$APP_PORT" "$DOCKER_IMAGE_NAME":"$DOCKER_IMAGE_TAG"