diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..7df82f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.vscode +.classpath +.settings +.project +target diff --git a/.s2i/environment b/.s2i/environment new file mode 100644 index 0000000..268e159 --- /dev/null +++ b/.s2i/environment @@ -0,0 +1,4 @@ +MAVEN_S2I_ARTIFACT_DIRS=target +S2I_SOURCE_DEPLOYMENTS_FILTER=*-runner.jar lib +JAVA_OPTIONS=-Dquarkus.http.host=0.0.0.0 +AB_JOLOKIA_OFF=true diff --git a/README.md b/README.md new file mode 100644 index 0000000..b511ff1 --- /dev/null +++ b/README.md @@ -0,0 +1,11 @@ +# Library API + +``` +oc new-project library-api +oc create secret generic 3scale-toolbox -n library-api --from-file="$HOME/.3scalerc.yaml" +oc new-app -n library-api --template=jenkins-ephemeral --name=jenkins -p MEMORY_LIMIT=2Gi +oc set env -n library-api dc/jenkins JENKINS_OPTS=--sessionTimeout=86400 +oc import-image openjdk-8-rhel8 --from=registry.redhat.io/openjdk/openjdk-8-rhel8 --confirm -n openshift --reference-policy=local +oc new-app -n library-api -i openjdk-8-rhel8 https://github.com/nmasse-itix/library-api.git --name=library-api +oc expose -n library-api svc/library-api --hostname="library-api.apps.ocp4.itix.fr" +``` diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..967b490 --- /dev/null +++ b/pom.xml @@ -0,0 +1,122 @@ + + + 4.0.0 + io.example.library + library-api + 1.0.0 + + UTF-8 + 2.22.0 + 0.23.2 + UTF-8 + 1.8 + 1.8 + + + + + io.quarkus + quarkus-bom + ${quarkus.version} + pom + import + + + + + + io.quarkus + quarkus-resteasy + + + io.quarkus + quarkus-junit5 + test + + + io.rest-assured + rest-assured + test + + + io.quarkus + quarkus-smallrye-openapi + + + io.quarkus + quarkus-resteasy-jackson + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + build + + + + + + maven-surefire-plugin + ${surefire-plugin.version} + + + org.jboss.logmanager.LogManager + + + + + + + + native + + + native + + + + + + io.quarkus + quarkus-maven-plugin + ${quarkus.version} + + + + native-image + + + true + + + + + + maven-failsafe-plugin + ${surefire-plugin.version} + + + + integration-test + verify + + + + ${project.build.directory}/${project.build.finalName}-runner + + + + + + + + + + diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm new file mode 100644 index 0000000..7c7f082 --- /dev/null +++ b/src/main/docker/Dockerfile.jvm @@ -0,0 +1,23 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in JVM mode +# +# Before building the docker image run: +# +# mvn package +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.jvm -t quarkus/sample-application-jvm . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/sample-application-jvm +# +### +FROM fabric8/java-alpine-openjdk8-jre +ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV AB_ENABLED=jmx_exporter +COPY target/lib/* /deployments/lib/ +COPY target/*-runner.jar /deployments/app.jar +EXPOSE 8080 +ENTRYPOINT [ "/deployments/run-java.sh" ] \ No newline at end of file diff --git a/src/main/docker/Dockerfile.native b/src/main/docker/Dockerfile.native new file mode 100644 index 0000000..617f348 --- /dev/null +++ b/src/main/docker/Dockerfile.native @@ -0,0 +1,22 @@ +#### +# This Dockerfile is used in order to build a container that runs the Quarkus application in native (no JVM) mode +# +# Before building the docker image run: +# +# mvn package -Pnative -Dnative-image.docker-build=true +# +# Then, build the image with: +# +# docker build -f src/main/docker/Dockerfile.native -t quarkus/sample-application . +# +# Then run the container using: +# +# docker run -i --rm -p 8080:8080 quarkus/sample-application +# +### +FROM registry.access.redhat.com/ubi8/ubi-minimal +WORKDIR /work/ +COPY target/*-runner /work/application +RUN chmod 775 /work +EXPOSE 8080 +CMD ["./application", "-Dquarkus.http.host=0.0.0.0"] \ No newline at end of file diff --git a/src/main/java/io/example/library/api/AuthorsResource.java b/src/main/java/io/example/library/api/AuthorsResource.java new file mode 100644 index 0000000..b591ba8 --- /dev/null +++ b/src/main/java/io/example/library/api/AuthorsResource.java @@ -0,0 +1,56 @@ +package io.example.library.api; + +import io.example.library.api.beans.Author; +import java.lang.String; +import java.util.List; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; + +/** + * A JAX-RS interface. An implementation of this interface must be provided. + */ +@Path("/authors") +public interface AuthorsResource { + /** + * Gets a list of all `Author` entities. + */ + @GET + @Produces("application/json") + List getauthors(); + + /** + * Creates a new instance of a `Author`. + */ + @POST + @Consumes("application/json") + void createAuthor(Author data); + + /** + * Gets the details of a single instance of a `Author`. + */ + @Path("/{authorId}") + @GET + @Produces("application/json") + Author getAuthor(@PathParam("authorId") String authorId); + + /** + * Updates an existing `Author`. + */ + @Path("/{authorId}") + @PUT + @Consumes("application/json") + void updateAuthor(@PathParam("authorId") String authorId, Author data); + + /** + * Deletes an existing `Author`. + */ + @Path("/{authorId}") + @DELETE + void deleteAuthor(@PathParam("authorId") String authorId); +} diff --git a/src/main/java/io/example/library/api/BooksResource.java b/src/main/java/io/example/library/api/BooksResource.java new file mode 100644 index 0000000..1d6310f --- /dev/null +++ b/src/main/java/io/example/library/api/BooksResource.java @@ -0,0 +1,56 @@ +package io.example.library.api; + +import io.example.library.api.beans.Book; +import java.lang.String; +import java.util.List; +import javax.ws.rs.Consumes; +import javax.ws.rs.DELETE; +import javax.ws.rs.GET; +import javax.ws.rs.POST; +import javax.ws.rs.PUT; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; + +/** + * A JAX-RS interface. An implementation of this interface must be provided. + */ +@Path("/books") +public interface BooksResource { + /** + * Gets a list of all `Book` entities. + */ + @GET + @Produces("application/json") + List getbooks(); + + /** + * Creates a new instance of a `Book`. + */ + @POST + @Consumes("application/json") + void createBook(Book data); + + /** + * Gets the details of a single instance of a `Book`. + */ + @Path("/{bookId}") + @GET + @Produces("application/json") + Book getBook(@PathParam("bookId") String bookId); + + /** + * Updates an existing `Book`. + */ + @Path("/{bookId}") + @PUT + @Consumes("application/json") + void updateBook(@PathParam("bookId") String bookId, Book data); + + /** + * Deletes an existing `Book`. + */ + @Path("/{bookId}") + @DELETE + void deleteBook(@PathParam("bookId") String bookId); +} diff --git a/src/main/java/io/example/library/api/beans/Author.java b/src/main/java/io/example/library/api/beans/Author.java new file mode 100644 index 0000000..2bede09 --- /dev/null +++ b/src/main/java/io/example/library/api/beans/Author.java @@ -0,0 +1,60 @@ + +package io.example.library.api.beans; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * Root Type for Author + *

+ * The author of a book. + * + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "id", + "name", + "dob" +}) +public class Author { + + @JsonProperty("id") + private String id; + @JsonProperty("name") + private String name; + @JsonProperty("dob") + private String dob; + + @JsonProperty("id") + public String getId() { + return id; + } + + @JsonProperty("id") + public void setId(String id) { + this.id = id; + } + + @JsonProperty("name") + public String getName() { + return name; + } + + @JsonProperty("name") + public void setName(String name) { + this.name = name; + } + + @JsonProperty("dob") + public String getDob() { + return dob; + } + + @JsonProperty("dob") + public void setDob(String dob) { + this.dob = dob; + } + +} diff --git a/src/main/java/io/example/library/api/beans/Book.java b/src/main/java/io/example/library/api/beans/Book.java new file mode 100644 index 0000000..81331fb --- /dev/null +++ b/src/main/java/io/example/library/api/beans/Book.java @@ -0,0 +1,93 @@ + +package io.example.library.api.beans; + +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyDescription; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + + +/** + * Root Type for Book + *

+ * Information about a book. + * + */ +@JsonInclude(JsonInclude.Include.NON_NULL) +@JsonPropertyOrder({ + "ddsn", + "title", + "author", + "publish-date" +}) +public class Book { + + @JsonProperty("ddsn") + private String ddsn; + @JsonProperty("title") + private String title; + /** + * Root Type for Author + *

+ * The author of a book. + * + */ + @JsonProperty("author") + @JsonPropertyDescription("The author of a book.") + private Author author; + @JsonProperty("publish-date") + private String publishDate; + + @JsonProperty("ddsn") + public String getDdsn() { + return ddsn; + } + + @JsonProperty("ddsn") + public void setDdsn(String ddsn) { + this.ddsn = ddsn; + } + + @JsonProperty("title") + public String getTitle() { + return title; + } + + @JsonProperty("title") + public void setTitle(String title) { + this.title = title; + } + + /** + * Root Type for Author + *

+ * The author of a book. + * + */ + @JsonProperty("author") + public Author getAuthor() { + return author; + } + + /** + * Root Type for Author + *

+ * The author of a book. + * + */ + @JsonProperty("author") + public void setAuthor(Author author) { + this.author = author; + } + + @JsonProperty("publish-date") + public String getPublishDate() { + return publishDate; + } + + @JsonProperty("publish-date") + public void setPublishDate(String publishDate) { + this.publishDate = publishDate; + } + +} diff --git a/src/main/java/io/example/library/api/impl/AuthorsResourceImpl.java b/src/main/java/io/example/library/api/impl/AuthorsResourceImpl.java new file mode 100644 index 0000000..bfc5789 --- /dev/null +++ b/src/main/java/io/example/library/api/impl/AuthorsResourceImpl.java @@ -0,0 +1,53 @@ +package io.example.library.api.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; + +import io.example.library.api.AuthorsResource; +import io.example.library.api.beans.Author; + +@ApplicationScoped +public class AuthorsResourceImpl implements AuthorsResource { + + private Map authorDB = new HashMap<>(); + + public AuthorsResourceImpl() { + Author author = new Author(); + author.setId("poe"); + author.setDob("1809-01-19"); + author.setName("Edgar Allan Poe"); + this.authorDB.put("", author); + } + + @Override + public List getauthors() { + List rval = new ArrayList<>(); + rval.addAll(this.authorDB.values()); + return rval; + } + + @Override + public void createAuthor(Author data) { + this.authorDB.put(data.getId(), data); + } + + @Override + public Author getAuthor(String authorId) { + return this.authorDB.get(authorId); + } + + @Override + public void updateAuthor(String authorId, Author data) { + this.authorDB.put(authorId, data); + } + + @Override + public void deleteAuthor(String authorId) { + this.authorDB.remove(authorId); + } + +} diff --git a/src/main/java/io/example/library/api/impl/BooksResourceImpl.java b/src/main/java/io/example/library/api/impl/BooksResourceImpl.java new file mode 100644 index 0000000..978c348 --- /dev/null +++ b/src/main/java/io/example/library/api/impl/BooksResourceImpl.java @@ -0,0 +1,48 @@ +package io.example.library.api.impl; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +import javax.enterprise.context.ApplicationScoped; + +import io.example.library.api.BooksResource; +import io.example.library.api.beans.Book; + +@ApplicationScoped +public class BooksResourceImpl implements BooksResource { + + private Map bookDB = new HashMap<>(); + + public BooksResourceImpl() { + } + + @Override + public List getbooks() { + List rval = new ArrayList<>(); + rval.addAll(this.bookDB.values()); + return rval; + } + + @Override + public void createBook(Book data) { + this.bookDB.put(data.getDdsn(), data); + } + + @Override + public Book getBook(String bookId) { + return this.bookDB.get(bookId); + } + + @Override + public void updateBook(String bookId, Book data) { + this.bookDB.put(data.getDdsn(), data); + } + + @Override + public void deleteBook(String bookId) { + this.bookDB.remove(bookId); + } + +} diff --git a/src/main/resources/META-INF/microprofile-config.properties b/src/main/resources/META-INF/microprofile-config.properties new file mode 100644 index 0000000..c3fe390 --- /dev/null +++ b/src/main/resources/META-INF/microprofile-config.properties @@ -0,0 +1 @@ +mp.openapi.scan.disable=true \ No newline at end of file diff --git a/src/main/resources/META-INF/openapi.json b/src/main/resources/META-INF/openapi.json new file mode 100644 index 0000000..34d5203 --- /dev/null +++ b/src/main/resources/META-INF/openapi.json @@ -0,0 +1,308 @@ +{ + "openapi": "3.0.2", + "info": { + "title": "Library API", + "version": "1.0.0", + "description": "A simple API for managing authors and books.", + "contact": { + "name": "Eric Wittmann", + "email": "eric.wittmann@redhat.com" + }, + "license": { + "name": "Mozilla 2.0", + "url": "https://www.mozilla.org/en-US/MPL/2.0/" + } + }, + "paths": { + "/authors": { + "summary": "Path used to manage the list of authors.", + "description": "The REST endpoint/path used to list and create zero or more `Author` entities. This path contains a `GET` and `POST` operation to perform the list and create tasks, respectively.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Author" + } + } + } + }, + "description": "Successful response - returns an array of `Author` entities." + } + }, + "operationId": "getauthors", + "summary": "List All authors", + "description": "Gets a list of all `Author` entities." + }, + "post": { + "requestBody": { + "description": "A new `Author` to be created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Author" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successful response." + } + }, + "operationId": "createAuthor", + "summary": "Create a Author", + "description": "Creates a new instance of a `Author`." + } + }, + "/authors/{authorId}": { + "summary": "Path used to manage a single Author.", + "description": "The REST endpoint/path used to get, update, and delete single instances of an `Author`. This path contains `GET`, `PUT`, and `DELETE` operations used to perform the get, update, and delete tasks, respectively.", + "get": { + "tags": [ + ], + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Author" + } + } + }, + "description": "Successful response - returns a single `Author`." + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "operationId": "getAuthor", + "summary": "Get a Author", + "description": "Gets the details of a single instance of a `Author`." + }, + "put": { + "requestBody": { + "description": "Updated `Author` information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Author" + } + } + }, + "required": true + }, + "responses": { + "202": { + "description": "Successful response." + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "operationId": "updateAuthor", + "summary": "Update a Author", + "description": "Updates an existing `Author`." + }, + "delete": { + "responses": { + "204": { + "description": "Successful response." + }, + "404": { + "$ref": "#/components/responses/NotFound" + } + }, + "operationId": "deleteAuthor", + "summary": "Delete a Author", + "description": "Deletes an existing `Author`." + }, + "parameters": [ + { + "name": "authorId", + "description": "A unique identifier for a `Author`.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + }, + "/books": { + "summary": "Path used to manage the list of books.", + "description": "The REST endpoint/path used to list and create zero or more `Book` entities. This path contains a `GET` and `POST` operation to perform the list and create tasks, respectively.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "type": "array", + "items": { + "$ref": "#/components/schemas/Book" + } + } + } + }, + "description": "Successful response - returns an array of `Book` entities." + } + }, + "operationId": "getbooks", + "summary": "List All books", + "description": "Gets a list of all `Book` entities." + }, + "post": { + "requestBody": { + "description": "A new `Book` to be created.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + } + }, + "required": true + }, + "responses": { + "201": { + "description": "Successful response." + } + }, + "operationId": "createBook", + "summary": "Create a Book", + "description": "Creates a new instance of a `Book`." + } + }, + "/books/{bookId}": { + "summary": "Path used to manage a single Book.", + "description": "The REST endpoint/path used to get, update, and delete single instances of an `Book`. This path contains `GET`, `PUT`, and `DELETE` operations used to perform the get, update, and delete tasks, respectively.", + "get": { + "responses": { + "200": { + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + } + }, + "description": "Successful response - returns a single `Book`." + } + }, + "operationId": "getBook", + "summary": "Get a Book", + "description": "Gets the details of a single instance of a `Book`." + }, + "put": { + "requestBody": { + "description": "Updated `Book` information.", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/Book" + } + } + }, + "required": true + }, + "responses": { + "202": { + "description": "Successful response." + } + }, + "operationId": "updateBook", + "summary": "Update a Book", + "description": "Updates an existing `Book`." + }, + "delete": { + "responses": { + "204": { + "description": "Successful response." + } + }, + "operationId": "deleteBook", + "summary": "Delete a Book", + "description": "Deletes an existing `Book`." + }, + "parameters": [ + { + "name": "bookId", + "description": "A unique identifier for a `Book`.", + "schema": { + "type": "string" + }, + "in": "path", + "required": true + } + ] + } + }, + "components": { + "schemas": { + "Author": { + "title": "Root Type for Author", + "description": "The author of a book.", + "type": "object", + "properties": { + "id": { + "type": "string" + }, + "name": { + "type": "string" + }, + "dob": { + "format": "date", + "type": "string" + } + }, + "example": { + "id": "jk-rowling", + "name": "JK Rowling", + "dob": "1968-01-01" + } + }, + "Book": { + "title": "Root Type for Book", + "description": "Information about a book.", + "type": "object", + "properties": { + "ddsn": { + "type": "string" + }, + "title": { + "type": "string" + }, + "author": { + "$ref": "#/components/schemas/Author" + }, + "publish-date": { + "format": "date", + "type": "string" + } + }, + "example": { + "ddsn": "632.4", + "title": "SQL For Dummies", + "publish-date": "2001-05-13" + } + } + }, + "responses": { + "NotFound": { + "content": { + "application/json": { + "schema": { + "type": "string" + } + } + }, + "description": "Generic response when not found." + } + } + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties new file mode 100644 index 0000000..3c1ac56 --- /dev/null +++ b/src/main/resources/application.properties @@ -0,0 +1,2 @@ +# Configuration file +# key = value \ No newline at end of file