From 29c9eaa23f47d92025766d61ed9a2fb58c66ae7d Mon Sep 17 00:00:00 2001 From: Ruben Romero Montes Date: Mon, 7 Sep 2020 10:34:11 +0200 Subject: [PATCH] Add Quarkus example (#226) * Add Quarkus example Signed-off-by: ruromero * Rename project and remove unrelated files Signed-off-by: ruromero --- examples/pom.xml | 1 + examples/restful-ws-quarkus/README.md | 85 +++++++++++++ .../restful-ws-quarkus/examples/user.json | 6 + examples/restful-ws-quarkus/pom.xml | 118 ++++++++++++++++++ .../examples/quarkus/client/UserClient.java | 18 +++ .../quarkus/client/UserEventsGenerator.java | 59 +++++++++ .../examples/quarkus/model/User.java | 55 ++++++++ .../quarkus/resources/UserResource.java | 66 ++++++++++ .../src/main/resources/application.properties | 9 ++ .../quarkus/NativeUserResourceTestIT.java | 9 ++ .../examples/quarkus/UserResourceTest.java | 20 +++ 11 files changed, 446 insertions(+) create mode 100644 examples/restful-ws-quarkus/README.md create mode 100644 examples/restful-ws-quarkus/examples/user.json create mode 100644 examples/restful-ws-quarkus/pom.xml create mode 100644 examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/client/UserClient.java create mode 100644 examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/client/UserEventsGenerator.java create mode 100644 examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/model/User.java create mode 100644 examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/resources/UserResource.java create mode 100644 examples/restful-ws-quarkus/src/main/resources/application.properties create mode 100644 examples/restful-ws-quarkus/src/test/java/io/cloudevents/examples/quarkus/NativeUserResourceTestIT.java create mode 100644 examples/restful-ws-quarkus/src/test/java/io/cloudevents/examples/quarkus/UserResourceTest.java diff --git a/examples/pom.xml b/examples/pom.xml index 833fc5c7..eb75f3b5 100644 --- a/examples/pom.xml +++ b/examples/pom.xml @@ -15,6 +15,7 @@ kafka + restful-ws-quarkus vertx diff --git a/examples/restful-ws-quarkus/README.md b/examples/restful-ws-quarkus/README.md new file mode 100644 index 00000000..6b06b99a --- /dev/null +++ b/examples/restful-ws-quarkus/README.md @@ -0,0 +1,85 @@ +# Cloudevents Restful WS Quarkus example + +This sample application has a `/users` REST endpoint in which you can manage the different users. +The way to create users is through CloudEvents. Here is an example POST: + +```shell script +curl -v http://localhost:8080/users \ + -H "Ce-Specversion: 1.0" \ + -H "Ce-Type: User" \ + -H "Ce-Source: io.cloudevents.examples/user" \ + -H "Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f78" \ + -H "Content-Type: application/json" \ + -H "Ce-Subject: SUBJ-0001" \ + -d @examples/user.json + +> POST /users HTTP/1.1 +> Host: localhost:8080 +> User-Agent: curl/7.69.1 +> Accept: */* +> Ce-Specversion: 1.0 +> Ce-Type: User +> Ce-Source: io.cloudevents.examples/user +> Ce-Id: 536808d3-88be-4077-9d7a-a3f162705f78 +> Content-Type: application/json +> Ce-Subject: SUBJ-0001 +> Content-Length: 88 + +< HTTP/1.1 201 Created +< Content-Length: 0 +< Location: http://localhost:8080/users +``` + +In order to also show how to create and send CloudEvents, generated events will be periodically sent +each 2 seconds through HTTP to the same endpoint using a REST client. + +Check the following URL to view the existing users: + +```shell script +$ curl http://localhost:8080/users +{ + "user1": { + "username": "user1", + "firstName": "firstName1", + "lastName": "lastName1", + "age": 1 + }, + "user2": { + "username": "user2", + "firstName": "firstName2", + "lastName": "lastName2", + "age": 2 + } +} +``` + +## Build and execution + +This project uses Quarkus, the Supersonic Subatomic Java Framework. + +If you want to learn more about Quarkus, please visit its website: https://quarkus.io/ . + +### Running the application in dev mode + +You can run your application in dev mode that enables live coding using: +``` +mvn quarkus:dev +``` + +### Packaging and running the application + +The application can be packaged using `mvn package`. +It produces the `cloudevents-restful-ws-quarkus-example-1.0-SNAPSHOT-runner.jar` file in the `/target` directory. +Be aware that it’s not an _über-jar_ as the dependencies are copied into the `target/lib` directory. + +The application is now runnable using `java -jar target/cloudevents-restful-ws-quarkus-example-1.0-SNAPSHOT-runner.jar`. + +### Creating a native executable + +You can create a native executable using: `mvn package -Pnative`. + +Or, if you don't have GraalVM installed, you can run the native executable build in a container using: `mvn package -Pnative -Dquarkus.native.container-build=true`. + +You can then execute your native executable with: `./target/cloudevents-restful-ws-quarkus-example-1.0-SNAPSHOT-runner` + +If you want to learn more about building native executables, please consult https://quarkus.io/guides/building-native-image. diff --git a/examples/restful-ws-quarkus/examples/user.json b/examples/restful-ws-quarkus/examples/user.json new file mode 100644 index 00000000..489a5678 --- /dev/null +++ b/examples/restful-ws-quarkus/examples/user.json @@ -0,0 +1,6 @@ +{ + "username": "jsmith", + "firstName": "John", + "lastName": "Smith", + "age": 37 +} diff --git a/examples/restful-ws-quarkus/pom.xml b/examples/restful-ws-quarkus/pom.xml new file mode 100644 index 00000000..3f6394cc --- /dev/null +++ b/examples/restful-ws-quarkus/pom.xml @@ -0,0 +1,118 @@ + + + + cloudevents-examples + io.cloudevents + 2.0.0-SNAPSHOT + + 4.0.0 + cloudevents-restful-ws-quarkus-example + + 1.7.1.Final + quarkus-universe-bom + io.quarkus + 1.7.1.Final + + + + + ${quarkus.platform.group-id} + ${quarkus.platform.artifact-id} + ${quarkus.platform.version} + pom + import + + + + + + io.quarkus + quarkus-resteasy-jackson + + + io.quarkus + quarkus-rest-client + + + io.cloudevents + cloudevents-api + ${project.version} + + + io.cloudevents + cloudevents-http-restful-ws + ${project.version} + + + io.cloudevents + cloudevents-json-jackson + ${project.version} + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus-plugin.version} + + + + prepare + prepare-tests + build + + + + + + + + + native + + + native + + + + + + maven-failsafe-plugin + ${maven-surefire-plugin.version} + + + + integration-test + verify + + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + + + native + + + + diff --git a/examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/client/UserClient.java b/examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/client/UserClient.java new file mode 100644 index 00000000..90c0de50 --- /dev/null +++ b/examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/client/UserClient.java @@ -0,0 +1,18 @@ +package io.cloudevents.examples.quarkus.client; + +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import io.cloudevents.CloudEvent; +import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; + +@Path("/users") +@RegisterRestClient +public interface UserClient { + + @POST + @Produces(MediaType.APPLICATION_JSON) + void emit(CloudEvent event); +} diff --git a/examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/client/UserEventsGenerator.java b/examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/client/UserEventsGenerator.java new file mode 100644 index 00000000..eb249297 --- /dev/null +++ b/examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/client/UserEventsGenerator.java @@ -0,0 +1,59 @@ +package io.cloudevents.examples.quarkus.client; + +import java.net.URI; +import java.time.Duration; +import java.util.UUID; +import javax.enterprise.context.ApplicationScoped; +import javax.enterprise.event.Observes; +import javax.inject.Inject; +import javax.ws.rs.core.MediaType; + +import io.cloudevents.CloudEvent; +import io.cloudevents.core.builder.CloudEventBuilder; +import io.cloudevents.examples.quarkus.model.User; +import io.quarkus.runtime.StartupEvent; +import io.smallrye.mutiny.Multi; +import io.vertx.core.json.Json; +import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@ApplicationScoped +public class UserEventsGenerator { + + private static final Logger LOGGER = LoggerFactory.getLogger(UserEventsGenerator.class); + + @Inject + @RestClient + UserClient userClient; + + public void init(@Observes StartupEvent startupEvent) { + Multi.createFrom().ticks().every(Duration.ofSeconds(2)) + .onItem() + .transform(this::createEvent) + .subscribe() + .with(event -> { + LOGGER.info("try to emit user: {}", event.getId()); + userClient.emit(event); + }); + } + + private CloudEvent createEvent(long id) { + return CloudEventBuilder.v1() + .withSource(URI.create("example")) + .withType("io.cloudevents.examples.quarkus.user") + .withId(UUID.randomUUID().toString()) + .withDataContentType(MediaType.APPLICATION_JSON) + .withData(createUserAsByteArray(id)) + .build(); + } + + private byte[] createUserAsByteArray(Long id) { + User user = new User() + .setAge(id.intValue()) + .setUsername("user" + id) + .setFirstName("firstName" + id) + .setLastName("lastName" + id); + return Json.encode(user).getBytes(); + } +} diff --git a/examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/model/User.java b/examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/model/User.java new file mode 100644 index 00000000..cb8df73a --- /dev/null +++ b/examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/model/User.java @@ -0,0 +1,55 @@ +package io.cloudevents.examples.quarkus.model; + +public class User { + + private String username; + private String firstName; + private String lastName; + private Integer age; + + public String getUsername() { + return username; + } + + public User setUsername(String username) { + this.username = username; + return this; + } + + public String getFirstName() { + return firstName; + } + + public User setFirstName(String firstName) { + this.firstName = firstName; + return this; + } + + public String getLastName() { + return lastName; + } + + public User setLastName(String lastName) { + this.lastName = lastName; + return this; + } + + public Integer getAge() { + return age; + } + + public User setAge(Integer age) { + this.age = age; + return this; + } + + @Override + public String toString() { + return "User{" + + "username='" + username + '\'' + + ", firstName='" + firstName + '\'' + + ", lastName='" + lastName + '\'' + + ", age=" + age + + '}'; + } +} diff --git a/examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/resources/UserResource.java b/examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/resources/UserResource.java new file mode 100644 index 00000000..1c62bd83 --- /dev/null +++ b/examples/restful-ws-quarkus/src/main/java/io/cloudevents/examples/quarkus/resources/UserResource.java @@ -0,0 +1,66 @@ +package io.cloudevents.examples.quarkus.resources; + +import java.util.HashMap; +import java.util.Map; +import javax.ws.rs.BadRequestException; +import javax.ws.rs.Consumes; +import javax.ws.rs.GET; +import javax.ws.rs.NotFoundException; +import javax.ws.rs.POST; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.core.UriInfo; + +import io.cloudevents.CloudEvent; +import io.cloudevents.examples.quarkus.model.User; +import io.vertx.core.buffer.Buffer; +import io.vertx.core.json.Json; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +@Path("/users") +@Consumes(MediaType.APPLICATION_JSON) +@Produces(MediaType.APPLICATION_JSON) +public class UserResource { + + private static final Logger LOGGER = LoggerFactory.getLogger(UserResource.class); + + @Context + UriInfo uriInfo; + + private Map users = new HashMap<>(); + + @GET + @Path("/{username}") + public User get(@PathParam("username") String username) { + if (users.containsKey(username)) { + return users.get(username); + } + throw new NotFoundException(); + } + + @GET + public Map list() { + return users; + } + + @POST + public Response create(CloudEvent event) { + if (event == null || event.getData() == null) { + throw new BadRequestException("Invalid data received. Null or empty event"); + } + User user = Json.decodeValue(Buffer.buffer(event.getData()), User.class); + if (users.containsKey(user.getUsername())) { + throw new BadRequestException("Username already exists: " + user.getUsername()); + } + LOGGER.info("Received User: {}", user); + users.put(user.getUsername(), user); + return Response + .created(uriInfo.getAbsolutePathBuilder().build(event.getId())) + .build(); + } +} diff --git a/examples/restful-ws-quarkus/src/main/resources/application.properties b/examples/restful-ws-quarkus/src/main/resources/application.properties new file mode 100644 index 00000000..a425f5ee --- /dev/null +++ b/examples/restful-ws-quarkus/src/main/resources/application.properties @@ -0,0 +1,9 @@ +# Configuration file +# key = value + +## The Rest client will send events to the local UserResource +io.cloudevents.examples.quarkus.client.UserClient/mp-rest/url=http://localhost:8080 +io.cloudevents.examples.quarkus.client.UserClient/mp-rest/scope=javax.inject.Singleton + +quarkus.index-dependency.cloudevents.group-id=io.cloudevents +quarkus.index-dependency.cloudevents.artifact-id=cloudevents-http-restful-ws diff --git a/examples/restful-ws-quarkus/src/test/java/io/cloudevents/examples/quarkus/NativeUserResourceTestIT.java b/examples/restful-ws-quarkus/src/test/java/io/cloudevents/examples/quarkus/NativeUserResourceTestIT.java new file mode 100644 index 00000000..b1cca4df --- /dev/null +++ b/examples/restful-ws-quarkus/src/test/java/io/cloudevents/examples/quarkus/NativeUserResourceTestIT.java @@ -0,0 +1,9 @@ +package io.cloudevents.examples.quarkus; + +import io.quarkus.test.junit.NativeImageTest; + +@NativeImageTest +public class NativeUserResourceTestIT extends UserResourceTest { + + // Execute the same tests but in native mode. +} diff --git a/examples/restful-ws-quarkus/src/test/java/io/cloudevents/examples/quarkus/UserResourceTest.java b/examples/restful-ws-quarkus/src/test/java/io/cloudevents/examples/quarkus/UserResourceTest.java new file mode 100644 index 00000000..a3eeb1e6 --- /dev/null +++ b/examples/restful-ws-quarkus/src/test/java/io/cloudevents/examples/quarkus/UserResourceTest.java @@ -0,0 +1,20 @@ +package io.cloudevents.examples.quarkus; + +import io.quarkus.test.junit.QuarkusTest; +import org.junit.jupiter.api.Test; + +import static io.restassured.RestAssured.given; +import static org.hamcrest.CoreMatchers.is; + +@QuarkusTest +public class UserResourceTest { + + @Test + public void testUserEndpoint() { + given() + .when().get("/users") + .then() + .statusCode(200); + } + +}