diff --git a/docker-compose.yaml b/docker-compose.yaml index a94ea7245088ab0597397eefdcaeb17af42416c2..c0aa52b8c7fe179d769a63e9eb9d5926347e702d 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -5,7 +5,7 @@ services: context: . dockerfile: ./src/main/docker/Dockerfile.jvm ports: - - "8090:8080" + - "10126:8090" volumes: - ./config:/config deploy: diff --git a/makefile b/makefile index 7e843ec0db0c2a1d895bafa50c1a6f92d3021c64..0245e87839054c8591cb6216fdf34765e08b245c 100644 --- a/makefile +++ b/makefile @@ -1,11 +1,10 @@ SHELL = /bin/bash dev-start:; - mvn compile quarkus:dev + mvn compile -e quarkus:dev clean:; mvn clean - docker compose down compile-java:; mvn compile package diff --git a/pom.xml b/pom.xml index 939f9db928719538cbb7d43b485c81dcb67858b4..af5bc7da7a911f5c903e993519c6db9db7a5c26c 100644 --- a/pom.xml +++ b/pom.xml @@ -1,154 +1,187 @@ <?xml version="1.0"?> -<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" - xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> - <modelVersion>4.0.0</modelVersion> - <groupId>org.eclipsefoundation</groupId> - <artifactId>eclipsefdn-project-adopters</artifactId> - <version>0.0.1</version> - <properties> - <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> - <surefire-plugin.version>2.22.0</surefire-plugin.version> - <quarkus.version>1.6.1.Final</quarkus.version> - <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> - <maven.compiler.source>11</maven.compiler.source> - <maven.compiler.target>11</maven.compiler.target> - <maven.compiler.parameters>true</maven.compiler.parameters> - <compiler-plugin.version>3.8.1</compiler-plugin.version> - <sonar.sources>src/main</sonar.sources> - <sonar.tests>src/test</sonar.tests> - <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin> - <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis> - <sonar.jacoco.reportPaths>${project.build.directory}/jacoco-report</sonar.jacoco.reportPaths> - <sonar.junit.reportPath>${project.build.directory}/surefire-reports</sonar.junit.reportPath> - </properties> - <dependencyManagement> - <dependencies> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-bom</artifactId> - <version>${quarkus.version}</version> - <type>pom</type> - <scope>import</scope> - </dependency> - </dependencies> - </dependencyManagement> - <dependencies> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-resteasy</artifactId> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-resteasy-jsonb</artifactId> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-rest-client</artifactId> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-cache</artifactId> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-smallrye-context-propagation</artifactId> - </dependency> - <dependency> - <groupId>com.google.guava</groupId> - <artifactId>guava</artifactId> - </dependency> - - <!-- Testing --> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-junit5</artifactId> - <scope>test</scope> - </dependency> - <dependency> - <groupId>io.rest-assured</groupId> - <artifactId>rest-assured</artifactId> - <scope>test</scope> - </dependency> - </dependencies> - <build> - <plugins> - <plugin> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-maven-plugin</artifactId> - <version>${quarkus.version}</version> - <executions> - <execution> - <goals> - <goal>build</goal> - </goals> - </execution> - </executions> - </plugin> - <plugin> - <artifactId>maven-surefire-plugin</artifactId> - <version>${surefire-plugin.version}</version> - <configuration> - <argLine>${argLine} -Xmx2048m</argLine> - <includes> - <include>**/*Test.java</include> - </includes> - </configuration> - </plugin> - <plugin> - <groupId>org.jacoco</groupId> - <artifactId>jacoco-maven-plugin</artifactId> - <version>0.8.4</version> - <configuration> - <skip>${maven.test.skip}</skip> - <destFile>${basedir}/target/coverage-reports/jacoco-unit.exec</destFile> - <dataFile>${basedir}/target/coverage-reports/jacoco-unit.exec</dataFile> - <output>file</output> - <append>true</append> - </configuration> - <executions> - <execution> - <id>jacoco-initialize</id> - <goals> - <goal>prepare-agent</goal> - </goals> - </execution> - <execution> - <id>report</id> - <phase>verify</phase> - <goals> - <goal>report</goal> - </goals> - </execution> - </executions> - </plugin> - </plugins> - </build> - <profiles> - <profile> - <id>native</id> - <properties> - <quarkus.package.type>native</quarkus.package.type> - </properties> - <activation> - <property> - <name>native</name> - </property> - </activation> - </profile> - <profile> - <id>sonar-dev</id> - <build> - <plugins> - <plugin> - <groupId>org.sonarsource.scanner.maven</groupId> - <artifactId>sonar-maven-plugin</artifactId> - <version>3.6.0.1398</version> - </plugin> - </plugins> - </build> - <properties> - <sonar.host.url>https://sonarqube.dev.docker</sonar.host.url> - </properties> - </profile> - </profiles> -</project> +<project + xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" + xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"> + <modelVersion>4.0.0</modelVersion> + <groupId>org.eclipsefoundation</groupId> + <artifactId>eclipsefdn-project-adopters</artifactId> + <version>0.0.1</version> + + <properties> + <compiler-plugin.version>3.8.1</compiler-plugin.version> + <maven.compiler.source>11</maven.compiler.source> + <maven.compiler.target>11</maven.compiler.target> + <maven.compiler.parameters>true</maven.compiler.parameters> + <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> + <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> + <quarkus.platform.artifact-id>quarkus-bom</quarkus.platform.artifact-id> + <quarkus.platform.group-id>io.quarkus.platform</quarkus.platform.group-id> + <quarkus.platform.version>2.14.2.Final</quarkus.platform.version> + <surefire-plugin.version>3.0.0-M5</surefire-plugin.version> + <sonar.sources>src/main</sonar.sources> + <sonar.tests>src/test</sonar.tests> + <sonar.java.coveragePlugin>jacoco</sonar.java.coveragePlugin> + <sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis> + <sonar.jacoco.reportPaths>${project.build.directory}/jacoco-report</sonar.jacoco.reportPaths> + <sonar.junit.reportPath>${project.build.directory}/surefire-reports</sonar.junit.reportPath> + <auto-value.version>1.8.2</auto-value.version> + <eclipse-api-version>0.7.1</eclipse-api-version> + </properties> + + <repositories> + <repository> + <id>eclipsefdn</id> + <url>https://repo.eclipse.org/content/repositories/eclipsefdn/</url> + <releases> + <enabled>true</enabled> + </releases> + <snapshots> + <enabled>true</enabled> + </snapshots> + </repository> + </repositories> + + <dependencyManagement> + <dependencies> + <dependency> + <groupId>${quarkus.platform.group-id}</groupId> + <artifactId>${quarkus.platform.artifact-id}</artifactId> + <version>${quarkus.platform.version}</version> + <type>pom</type> + <scope>import</scope> + </dependency> + </dependencies> + </dependencyManagement> + + <dependencies> + <dependency> + <groupId>org.eclipsefoundation</groupId> + <artifactId>quarkus-core</artifactId> + <version>${eclipse-api-version}</version> + <!-- Can be removed once dependency is removed from base package + https://stackoverflow.com/questions/67510802/logging-in-quarkus-works-in-dev-mode-but-doesnt-output-in-jvm-docker-image --> + <exclusions> + <exclusion> + <groupId>org.jboss.logmanager</groupId> + <artifactId>jboss-logmanager</artifactId> + </exclusion> + </exclusions> + </dependency> + + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-resteasy-jackson</artifactId> + </dependency> + <dependency> + <groupId>io.quarkus</groupId> + <artifactId>quarkus-rest-client</artifactId> + </dependency> + <dependency> + <groupId>com.google.guava</groupId> + <artifactId>guava</artifactId> + </dependency> + + <!-- + Annotation preprocessors - reduce all of the boiler plate --> + <dependency> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value</artifactId> + <version>${auto-value.version}</version> + <scope>provided</scope> + </dependency> + <dependency> + <groupId>com.google.auto.value</groupId> + <artifactId>auto-value-annotations</artifactId> + <version>${auto-value.version}</version> + </dependency> + <dependency> + <groupId>com.google.code.findbugs</groupId> + <artifactId>jsr305</artifactId> + <version>3.0.0</version> + </dependency> + </dependencies> + + <build> + <plugins> + <plugin> + <groupId>${quarkus.platform.group-id}</groupId> + <artifactId>quarkus-maven-plugin</artifactId> + <version>${quarkus.platform.version}</version> + <executions> + <execution> + <goals> + <goal>build</goal> + </goals> + </execution> + </executions> + </plugin> + <plugin> + <artifactId>maven-surefire-plugin</artifactId> + <version>${surefire-plugin.version}</version> + <configuration> + <argLine>${argLine} + -Xmx2048m</argLine> + <includes> + <include>**/*Test.java</include> + </includes> + </configuration> + </plugin> + <plugin> + <groupId>org.jacoco</groupId> + <artifactId>jacoco-maven-plugin</artifactId> + <version>0.8.4</version> + <configuration> + <skip>${maven.test.skip}</skip> + <destFile>${basedir}/target/coverage-reports/jacoco-unit.exec</destFile> + <dataFile> + ${basedir}/target/coverage-reports/jacoco-unit.exec</dataFile> + <output>file</output> + <append>true</append> + </configuration> + <executions> + <execution> + <id>jacoco-initialize</id> + <goals> + <goal>prepare-agent</goal> + </goals> + </execution> + <execution> + <id>report</id> + <phase>verify</phase> + <goals> + <goal>report</goal> + </goals> + </execution> + </executions> + </plugin> + </plugins> + </build> + <profiles> + <profile> + <id>native</id> + <properties> + <quarkus.package.type>native</quarkus.package.type> + </properties> + <activation> + <property> + <name>native</name> + </property> + </activation> + </profile> + <profile> + <id>sonar-dev</id> + <build> + <plugins> + <plugin> + <groupId>org.sonarsource.scanner.maven</groupId> + <artifactId>sonar-maven-plugin</artifactId> + <version>3.6.0.1398</version> + </plugin> + </plugins> + </build> + <properties> + <sonar.host.url>https://sonarqube.dev.docker</sonar.host.url> + </properties> + </profile> + </profiles> +</project> \ No newline at end of file diff --git a/spec/openapi.yaml b/spec/openapi.yaml index 240fa7f318bb7f07091bec98609d7d286bc34bd1..c64f8217a13d12981225304d187f8b2aa45a00c9 100644 --- a/spec/openapi.yaml +++ b/spec/openapi.yaml @@ -1,12 +1,12 @@ -openapi: '3.1.0' +openapi: "3.1.0" info: version: 1.0.0 title: PROJECT ADOPTERS API description: Access information on Eclipse Foundation project adopters license: - name: Eclipse Public License - 2.0 - url: https://www.eclipse.org/legal/epl-2.0/ + name: Eclipse Public License - 2.0 + url: https://www.eclipse.org/legal/epl-2.0/ servers: - url: https://api.eclipse.org/adopters @@ -32,13 +32,13 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/adopted_projects' + $ref: "#/components/schemas/adopted_projects" 500: description: Error while retrieving data. - + /projects/{projectId}: parameters: - - name: projectId + - name: projectId in: path description: The id of the project to retreive required: true @@ -53,21 +53,21 @@ paths: content: application/json: schema: - $ref: '#/components/schemas/adopted_project' + $ref: "#/components/schemas/adopted_project" 500: - description: Error while retrieving data. + description: Error while retrieving data. components: schemas: adopted_projects: type: array items: - $ref: '#/components/schemas/adopted_project' + $ref: "#/components/schemas/adopted_project" adopted_project: type: object properties: adopters: - $ref: '#/components/schemas/adopter' + $ref: "#/components/schemas/adopter" logo: type: string description: The URL containing the project logo. @@ -83,7 +83,7 @@ components: adopters: type: array items: - $ref: '#/components/schemas/adopter' + $ref: "#/components/schemas/adopter" adopter: type: object properties: @@ -99,4 +99,3 @@ components: name: type: string description: The adopter's company name. - diff --git a/src/main/docker/Dockerfile.jvm b/src/main/docker/Dockerfile.jvm index 796219ff8e584be31303a565f7a4c2a6a61da619..2674345997a9f6d07dbfd01d75222606eec75c58 100644 --- a/src/main/docker/Dockerfile.jvm +++ b/src/main/docker/Dockerfile.jvm @@ -14,21 +14,21 @@ # docker run -i --rm -p 8080:8080 eclipsefdn/eclipsefdn-project-adopters # ### -FROM fabric8/java-alpine-openjdk11-jre -ENV JAVA_OPTIONS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" -ENV AB_ENABLED=jmx_exporter -# Be prepared for running in OpenShift too -RUN adduser -G root --no-create-home --disabled-password 1001 \ - && chown -R 1001 /deployments \ - && chmod -R "g+rwX" /deployments \ - && chown -R 1001:root /deployments -COPY target/lib/* /deployments/lib/ -COPY target/*-runner.jar /deployments/app.jar -EXPOSE 8080 +FROM registry.access.redhat.com/ubi8/openjdk-11:1.11 + +ENV LANG='en_US.UTF-8' LANGUAGE='en_US:en' -# run with user 1001 -USER 1001 -ENTRYPOINT [ "/deployments/run-java.sh" ] \ No newline at end of file +# We make four distinct layers so if there are application changes the library layers can be re-used +COPY --chown=185 target/quarkus-app/lib/ /deployments/lib/ +COPY --chown=185 target/quarkus-app/*.jar /deployments/ +COPY --chown=185 target/quarkus-app/app/ /deployments/app/ +COPY --chown=185 target/quarkus-app/quarkus/ /deployments/quarkus/ + +EXPOSE 8080 +USER 185 +ENV AB_JOLOKIA_OFF="" +ENV JAVA_OPTS="-Dquarkus.http.host=0.0.0.0 -Djava.util.logging.manager=org.jboss.logmanager.LogManager" +ENV JAVA_APP_JAR="/deployments/quarkus-run.jar" \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/adopters/api/ProjectsAPI.java b/src/main/java/org/eclipsefoundation/adopters/api/ProjectsAPI.java index b3574f9486254e8fbafd6e8f05343a557536ed52..40a536076c8b1a02a360bb0afaa6280f6c527848 100644 --- a/src/main/java/org/eclipsefoundation/adopters/api/ProjectsAPI.java +++ b/src/main/java/org/eclipsefoundation/adopters/api/ProjectsAPI.java @@ -11,15 +11,14 @@ **********************************************************************/ package org.eclipsefoundation.adopters.api; -import java.util.List; - +import javax.ws.rs.BeanParam; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.Produces; -import javax.ws.rs.QueryParam; +import javax.ws.rs.core.Response; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; -import org.eclipsefoundation.adopters.model.Project; +import org.eclipsefoundation.core.service.APIMiddleware.BaseAPIParameters; /** * Interface for interacting with the PMI Projects API. Used to link Git @@ -35,10 +34,10 @@ public interface ProjectsAPI { /** * Retrieves all projects with the given repo URL. * - * @param repoUrl the target repos URL + * @param params the pagination parameters * @return a list of Eclipse Foundation projects. */ @GET @Produces("application/json") - List<Project> getProject(@QueryParam("page") int page, @QueryParam("pagesize") int pageSize); + Response getProjects(@BeanParam BaseAPIParameters params); } diff --git a/src/main/java/org/eclipsefoundation/adopters/config/JsonBConfig.java b/src/main/java/org/eclipsefoundation/adopters/config/JsonBConfig.java deleted file mode 100644 index 150aa743d688d7f3ca649097aa1ae76f71303561..0000000000000000000000000000000000000000 --- a/src/main/java/org/eclipsefoundation/adopters/config/JsonBConfig.java +++ /dev/null @@ -1,39 +0,0 @@ -/********************************************************************* -* Copyright (c) 2019 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.adopters.config; - -import javax.json.bind.Jsonb; -import javax.json.bind.JsonbBuilder; -import javax.json.bind.JsonbConfig; -import javax.json.bind.config.PropertyNamingStrategy; -import javax.ws.rs.ext.ContextResolver; -import javax.ws.rs.ext.Provider; - -/** - * Updates JSONB config to use a naming convention when interacting with objects - * that match the API best practices set by internal documentation. - * - * @author Martin Lowe - */ -@Provider -public class JsonBConfig implements ContextResolver<Jsonb> { - - @Override - public Jsonb getContext(Class<?> type) { - JsonbConfig config = new JsonbConfig(); - - // following strategy is defined as default by internal API guidelines - config.withPropertyNamingStrategy(PropertyNamingStrategy.LOWER_CASE_WITH_UNDERSCORES) - .withDateFormat("uuuu-MM-dd'T'HH:mm:ssXXX", null); - return JsonbBuilder.create(config); - } -} diff --git a/src/main/java/org/eclipsefoundation/adopters/model/AdoptedProject.java b/src/main/java/org/eclipsefoundation/adopters/model/AdoptedProject.java index 91e436d890d89bf4412770e4ee1d1e8d543c6d3a..ddd3f612b4a9cc75f589d5e128c7fd56a3db3115 100644 --- a/src/main/java/org/eclipsefoundation/adopters/model/AdoptedProject.java +++ b/src/main/java/org/eclipsefoundation/adopters/model/AdoptedProject.java @@ -1,19 +1,22 @@ /********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. +* Copyright (c) 2020, 2023 Eclipse Foundation. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * Author: Martin Lowe <martin.lowe@eclipse-foundation.org> +* Zachary Sabourin <zachary.sabourin@eclipse-foundation.org> * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ package org.eclipsefoundation.adopters.model; -import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; /** * A project with information about its adopters (read from the file system) @@ -22,81 +25,38 @@ import java.util.List; * @author Martin Lowe * */ -public class AdoptedProject { - - private String projectId; - private String name; - private String url; - private String logo; - private List<Adopter> adopters; - - /** - * @return the projectId - */ - public String getProjectId() { - return projectId; - } +@AutoValue +@JsonDeserialize(builder = AutoValue_AdoptedProject.Builder.class) +public abstract class AdoptedProject { - /** - * @param projectId the projectId to set - */ - public void setProjectId(String projectId) { - this.projectId = projectId; - } + public abstract String getProjectId(); - /** - * @return the name - */ - public String getName() { - return name; - } + public abstract String getName(); - /** - * @param name the name to set - */ - public void setName(String name) { - this.name = name; - } + public abstract String getUrl(); - /** - * @return the url - */ - public String getUrl() { - return url; - } + public abstract String getLogo(); - /** - * @param url the url to set - */ - public void setUrl(String url) { - this.url = url; - } + public abstract List<Adopter> getAdopters(); - /** - * @return the logo - */ - public String getLogo() { - return logo; + public static Builder builder() { + return new AutoValue_AdoptedProject.Builder(); } - /** - * @param logo the logo to set - */ - public void setLogo(String logo) { - this.logo = logo; - } + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { - /** - * @return the adopters - */ - public List<Adopter> getAdopters() { - return new ArrayList<>(adopters); - } + public abstract Builder setProjectId(String id); + + public abstract Builder setName(String name); + + public abstract Builder setUrl(String url); + + public abstract Builder setLogo(String logo); + + public abstract Builder setAdopters(List<Adopter> adopters); - /** - * @param adopters the adopters to set - */ - public void setAdopters(List<Adopter> adopters) { - this.adopters = new ArrayList<>(adopters); + public abstract AdoptedProject build(); } } diff --git a/src/main/java/org/eclipsefoundation/adopters/model/Adopter.java b/src/main/java/org/eclipsefoundation/adopters/model/Adopter.java index a6e2889d982e484901d7ef848951cab312d2324a..ff52b7f04c4424fff8f6a62dd00ffbd1f58d6e40 100644 --- a/src/main/java/org/eclipsefoundation/adopters/model/Adopter.java +++ b/src/main/java/org/eclipsefoundation/adopters/model/Adopter.java @@ -1,21 +1,23 @@ /********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. +* Copyright (c) 2020, 2023 Eclipse Foundation. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * Author: Martin Lowe <martin.lowe@eclipse-foundation.org> +* Zachary Sabourin <zachary.sabourin@eclipse-foundation.org> * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ package org.eclipsefoundation.adopters.model; -import java.util.ArrayList; import java.util.List; -import javax.json.bind.annotation.JsonbProperty; -import javax.json.bind.annotation.JsonbTransient; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; /** * Represents an adopter from the serialized adopter.json file. @@ -23,88 +25,42 @@ import javax.json.bind.annotation.JsonbTransient; * @author Martin Lowe * */ -public class Adopter { - private String name; - @JsonbProperty("homepage_url") - private String homepageUrl; - private String logo; - @JsonbProperty("logo_white") - private String logoWhite; - private List<String> projects; - - public Adopter() { - this.projects = new ArrayList<>(); - } +@AutoValue +@JsonDeserialize(builder = AutoValue_Adopter.Builder.class) +public abstract class Adopter { + public abstract String getName(); - /** - * @return the name - */ - public String getName() { - return name; - } + @JsonProperty("homepage_url") + public abstract String getHomepageUrl(); - /** - * @param name the name to set - */ - public void setName(String name) { - this.name = name; - } + public abstract String getLogo(); - /** - * @return the homepageUrl - */ - public String getHomepageUrl() { - return homepageUrl; - } + @JsonProperty("logo_white") + public abstract String getLogoWhite(); - /** - * @param homepageUrl the homepageUrl to set - */ - public void setHomepageUrl(String homepageUrl) { - this.homepageUrl = homepageUrl; - } + public abstract List<String> getProjects(); - /** - * @return the logo - */ - public String getLogo() { - return logo; + public static Builder builder() { + return new AutoValue_Adopter.Builder(); } - /** - * @param logo the logo to set - */ - public void setLogo(String logo) { - this.logo = logo; - } + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { - /** - * @return the logoWhite - */ - public String getLogoWhite() { - return logoWhite; - } + public abstract Builder setName(String name); - /** - * @param logoWhite the logoWhite to set - */ - public void setLogoWhite(String logoWhite) { - this.logoWhite = logoWhite; - } + @JsonProperty("homepage_url") + public abstract Builder setHomepageUrl(String url); - /** - * @return the projects - */ - @JsonbTransient - public List<String> getProjects() { - return new ArrayList<>(projects); - } + public abstract Builder setLogo(String logo); - /** - * @param projects the projects to set - */ - public void setProjects(List<String> projects) { - this.projects = new ArrayList<>(projects); - } + @JsonProperty("logo_white") + public abstract Builder setLogoWhite(String logoWhite); + public abstract Builder setProjects(List<String> projects); + + public abstract Adopter build(); + + } } diff --git a/src/main/java/org/eclipsefoundation/adopters/model/AdopterList.java b/src/main/java/org/eclipsefoundation/adopters/model/AdopterList.java index c2c3a8a4bb07e24d66ae7704f1b6802f80c2e101..b5658b93fa7fdc4723c242169143edcb878a4d5a 100644 --- a/src/main/java/org/eclipsefoundation/adopters/model/AdopterList.java +++ b/src/main/java/org/eclipsefoundation/adopters/model/AdopterList.java @@ -1,40 +1,44 @@ /********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. +* Copyright (c) 2020, 2023 Eclipse Foundation. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * Author: Martin Lowe <martin.lowe@eclipse-foundation.org> +* Zachary Sabourin <zachary.sabourin@eclipse-foundation.org> * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ package org.eclipsefoundation.adopters.model; -import java.util.ArrayList; import java.util.List; +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + /** * Root object for adopters.json serialized content. * * @author Martin Lowe * */ -public class AdopterList { - private List<Adopter> adopters = new ArrayList<>(); +@AutoValue +@JsonDeserialize(builder = AutoValue_AdopterList.Builder.class) +public abstract class AdopterList { - /** - * @return the adopters - */ - public List<Adopter> getAdopters() { - return new ArrayList<>(adopters); - } + public abstract List<Adopter> getAdopters(); - /** - * @param adopters the adopters to set - */ - public void setAdopters(List<Adopter> adopters) { - this.adopters = new ArrayList<>(adopters); + public static Builder builder() { + return new AutoValue_AdopterList.Builder(); } + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setAdopters(List<Adopter> adopters); + + public abstract AdopterList build(); + } } diff --git a/src/main/java/org/eclipsefoundation/adopters/model/Project.java b/src/main/java/org/eclipsefoundation/adopters/model/Project.java index a432a3a6d545844f9a1ad13dbf6ac5394f8024da..08c0fd94e5bd9eb9ce11dcc94f6018b8b6a2c513 100644 --- a/src/main/java/org/eclipsefoundation/adopters/model/Project.java +++ b/src/main/java/org/eclipsefoundation/adopters/model/Project.java @@ -1,19 +1,22 @@ /********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. +* Copyright (c) 2020, 2023 Eclipse Foundation. * * This program and the accompanying materials are made * available under the terms of the Eclipse Public License 2.0 * which is available at https://www.eclipse.org/legal/epl-2.0/ * * Author: Martin Lowe <martin.lowe@eclipse-foundation.org> +* Zachary Sabourin <zachary.sabourin@eclipse-foundation.org> * * SPDX-License-Identifier: EPL-2.0 **********************************************************************/ package org.eclipsefoundation.adopters.model; -import java.util.ArrayList; import java.util.List; -import java.util.Objects; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; /** * Represents a project from the Eclipse API. @@ -21,133 +24,37 @@ import java.util.Objects; * @author Martin Lowe * */ -public class Project { - private String projectId; - private String name; - private String url; - private String logo; - private List<WorkingGroup> workingGroups = new ArrayList<>(); - - /** - * @return the projectId - */ - public String getProjectId() { - return projectId; - } - - /** - * @param projectId the projectId to set - */ - public void setProjectId(String projectId) { - this.projectId = projectId; - } - - /** - * @return the name - */ - public String getName() { - return name; - } - - /** - * @param name the name to set - */ - public void setName(String name) { - this.name = name; - } +@AutoValue +@JsonDeserialize(builder = AutoValue_Project.Builder.class) +public abstract class Project { - /** - * @return the url - */ - public String getUrl() { - return url; - } + public abstract String getProjectId(); - /** - * @param url the url to set - */ - public void setUrl(String url) { - this.url = url; - } + public abstract String getName(); - /** - * @return the logo - */ - public String getLogo() { - return logo; - } + public abstract String getUrl(); - /** - * @param logo the logo to set - */ - public void setLogo(String logo) { - this.logo = logo; - } + public abstract String getLogo(); - /** - * @return the workingGroups - */ - public List<WorkingGroup> getWorkingGroups() { - return new ArrayList<>(workingGroups); - } - - /** - * @param workingGroups the workingGroups to set - */ - public void setWorkingGroups(List<WorkingGroup> workingGroups) { - this.workingGroups = new ArrayList<>(workingGroups); - } - - @Override - public int hashCode() { - return Objects.hash(logo, name, projectId, url, workingGroups); - } + public abstract List<WorkingGroup> getWorkingGroups(); - @Override - public boolean equals(Object obj) { - if (this == obj) - return true; - if (obj == null) - return false; - if (getClass() != obj.getClass()) - return false; - Project other = (Project) obj; - return Objects.equals(logo, other.logo) && Objects.equals(name, other.name) - && Objects.equals(projectId, other.projectId) && Objects.equals(url, other.url) - && Objects.equals(workingGroups, other.workingGroups); + public static Builder builder() { + return new AutoValue_Project.Builder(); } - public static class WorkingGroup { - private String name; - private String id; + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setProjectId(String id); - /** - * @return the name - */ - public String getName() { - return name; - } + public abstract Builder setName(String name); - /** - * @param name the name to set - */ - public void setName(String name) { - this.name = name; - } + public abstract Builder setUrl(String url); - /** - * @return the id - */ - public String getId() { - return id; - } + public abstract Builder setLogo(String logo); - /** - * @param id the id to set - */ - public void setId(String id) { - this.id = id; - } + public abstract Builder setWorkingGroups(List<WorkingGroup> workingGroups); + public abstract Project build(); } } diff --git a/src/main/java/org/eclipsefoundation/adopters/model/WorkingGroup.java b/src/main/java/org/eclipsefoundation/adopters/model/WorkingGroup.java new file mode 100644 index 0000000000000000000000000000000000000000..9a53c2eb1815f1766b77bd44c30d20f36689b96d --- /dev/null +++ b/src/main/java/org/eclipsefoundation/adopters/model/WorkingGroup.java @@ -0,0 +1,40 @@ +/********************************************************************* +* Copyright (c) 2023 Eclipse Foundation. +* +* This program and the accompanying materials are made +* available under the terms of the Eclipse Public License 2.0 +* which is available at https://www.eclipse.org/legal/epl-2.0/ +* +* Author: Zachary Sabourin <zachary.sabourin@eclipse-foundation.org> +* +* SPDX-License-Identifier: EPL-2.0 +**********************************************************************/ +package org.eclipsefoundation.adopters.model; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +@AutoValue +@JsonDeserialize(builder = AutoValue_WorkingGroup.Builder.class) +public abstract class WorkingGroup { + + public abstract String getName(); + + public abstract String getId(); + + public static Builder builder() { + return new AutoValue_WorkingGroup.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + + public abstract Builder setName(String name); + + public abstract Builder setId(String id); + + public abstract WorkingGroup build(); + } +} diff --git a/src/main/java/org/eclipsefoundation/adopters/resource/mappers/RuntimeMapper.java b/src/main/java/org/eclipsefoundation/adopters/resource/mappers/RuntimeMapper.java deleted file mode 100644 index 0f7cadc65343ebc77939389f3b798c259ea5e0c6..0000000000000000000000000000000000000000 --- a/src/main/java/org/eclipsefoundation/adopters/resource/mappers/RuntimeMapper.java +++ /dev/null @@ -1,41 +0,0 @@ -/********************************************************************* -* Copyright (c) 2019 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.adopters.resource.mappers; - -import javax.ws.rs.core.Response; -import javax.ws.rs.core.Response.Status; -import javax.ws.rs.ext.ExceptionMapper; -import javax.ws.rs.ext.Provider; - -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Catch-all exception mapper to ensure that any error thrown by the service - * will log the error and quit out safely while limiting the response to a - * simple error. - * - * @author Martin Lowe - * - */ -@Provider -public class RuntimeMapper implements ExceptionMapper<RuntimeException> { - private static final Logger LOGGER = LoggerFactory.getLogger(RuntimeMapper.class); - - @Override - public Response toResponse(RuntimeException exception) { - LOGGER.error(exception.getMessage(), exception); - // return an empty response with a server error response - return Response.status(Status.INTERNAL_SERVER_ERROR).build(); - } - -} diff --git a/src/main/java/org/eclipsefoundation/adopters/response/PaginatedResultsFilter.java b/src/main/java/org/eclipsefoundation/adopters/response/PaginatedResultsFilter.java deleted file mode 100644 index 5babf7c0002cf3abb49d0fc62530dfbffc2cf9e5..0000000000000000000000000000000000000000 --- a/src/main/java/org/eclipsefoundation/adopters/response/PaginatedResultsFilter.java +++ /dev/null @@ -1,148 +0,0 @@ -/********************************************************************* -* Copyright (c) 2020 Eclipse Foundation. -* -* This program and the accompanying materials are made -* available under the terms of the Eclipse Public License 2.0 -* which is available at https://www.eclipse.org/legal/epl-2.0/ -* -* Author: Martin Lowe <martin.lowe@eclipse-foundation.org> -* -* SPDX-License-Identifier: EPL-2.0 -**********************************************************************/ -package org.eclipsefoundation.adopters.response; - -import java.io.IOException; -import java.util.List; - -import javax.enterprise.inject.Instance; -import javax.ws.rs.container.ContainerRequestContext; -import javax.ws.rs.container.ContainerResponseContext; -import javax.ws.rs.container.ContainerResponseFilter; -import javax.ws.rs.core.MultivaluedMap; -import javax.ws.rs.core.UriBuilder; -import javax.ws.rs.core.UriInfo; -import javax.ws.rs.ext.Provider; - -import org.eclipse.microprofile.config.inject.ConfigProperty; -import org.jboss.resteasy.core.ResteasyContext; -import org.jboss.resteasy.spi.LinkHeader; - - -/** - * Adds pagination and Link headers to the response by slicing the response - * entity if its a list entity. This will not dig into complex entities to avoid - * false positives. - * - * @author Martin Lowe - * - */ -@Provider -public class PaginatedResultsFilter implements ContainerResponseFilter { - @ConfigProperty(name= "eclipse.pagination.page-size.default", defaultValue = "10") - Instance<Integer> defaultPageSize; - - // Force scheme of header links to be a given value, useful for proxied requests - @ConfigProperty(name= "eclipse.pagination.scheme.enforce", defaultValue = "true") - Instance<Boolean> enforceLinkScheme; - @ConfigProperty(name= "eclipse.pagination.scheme.value", defaultValue = "https") - Instance<String> linkScheme; - - @Override - public void filter(ContainerRequestContext requestContext, ContainerResponseContext responseContext) - throws IOException { - int pageSize = defaultPageSize.get(); - Object entity = responseContext.getEntity(); - // only try and paginate if there are multiple entities - if (entity instanceof List) { - List<?> listEntity = (List<?>) entity; - int page = getRequestedPage(listEntity); - int lastPage = (int) Math.ceil((double) listEntity.size() / pageSize); - // set the sliced array as the entity - responseContext.setEntity( - listEntity.subList(getArrayLimitedNumber(listEntity, Math.max(0, page - 1) * pageSize), - getArrayLimitedNumber(listEntity, pageSize * page))); - - // add link headers for paginated page hints - UriBuilder builder = getUriInfo().getRequestUriBuilder(); - LinkHeader lh = new LinkHeader(); - // add first + last page link headers - lh.addLink("this page of results", "self", buildHref(builder, page), ""); - lh.addLink("first page of results", "first", buildHref(builder, 1), ""); - lh.addLink("last page of results", "last", buildHref(builder, lastPage), ""); - // add next/prev if needed - if (page > 1) { - lh.addLink("previous page of results", "prev", buildHref(builder, page - 1), ""); - } - if (page < lastPage) { - lh.addLink("next page of results", "next", buildHref(builder, page + 1), ""); - } - // set the link header to the response - responseContext.getHeaders().add("Link", lh); - } - - } - - /** - * Gets the current requested page, rounding down to max if larger than the max - * page number, and up if below 1. - * - * @param listEntity list entity used to determine the number of pages present - * for current call. - * @return the current page number if set, the last page if greater, or 1 if not - * set or negative. - */ - private int getRequestedPage(List<?> listEntity) { - MultivaluedMap<String, String> params = getUriInfo().getQueryParameters(); - if (params.containsKey("page")) { - try { - int page = Integer.parseInt(params.getFirst("page")); - // use double cast int to allow ceil call to round up for pages - int maxPage = (int) Math.ceil((double) listEntity.size() / defaultPageSize.get()); - // get page, with min of 1 and max of last page - return Math.min(Math.max(1, page), maxPage); - } catch (NumberFormatException e) { - // page isn't a number, just return - return 1; - } - } - return 1; - } - - /** - * Builds an href for a paginated link using the BaseUri UriBuilder from the - * UriInfo object, replacing just the page query parameter. - * - * @param builder base URI builder from the UriInfo object. - * @param page the page to link to in the returned link - * @return fully qualified HREF for the paginated results - */ - private String buildHref(UriBuilder builder, int page) { - if (enforceLinkScheme.get()) { - return builder.scheme(linkScheme.get()).replaceQueryParam("page", page).build().toString(); - - } - return builder.replaceQueryParam("page", page).build().toString(); - } - - /** - * Gets an int bound by the size of a list. - * - * @param list the list to bind the number by - * @param num the number to check for exceeding bounds. - * @return the passed number if its within the size of the given array, 0 if the - * number is negative, and the array size if greater than the maximum - * bounds. - */ - private int getArrayLimitedNumber(List<?> list, int num) { - if (num < 0) { - return 0; - } else if (num > list.size()) { - return list.size(); - } - return num; - } - - private UriInfo getUriInfo() { - return ResteasyContext.getContextData(UriInfo.class); - } -} diff --git a/src/main/java/org/eclipsefoundation/adopters/service/impl/DefaultAdopterService.java b/src/main/java/org/eclipsefoundation/adopters/service/impl/DefaultAdopterService.java index 6941a7b7c8a21316d23f0d22f5ca4a02d88492fe..3c25744544f69c1e3ad79e986d402befcb4876f4 100644 --- a/src/main/java/org/eclipsefoundation/adopters/service/impl/DefaultAdopterService.java +++ b/src/main/java/org/eclipsefoundation/adopters/service/impl/DefaultAdopterService.java @@ -35,7 +35,6 @@ import javax.annotation.PostConstruct; import javax.annotation.PreDestroy; import javax.enterprise.context.ApplicationScoped; import javax.inject.Inject; -import javax.json.bind.Jsonb; import org.eclipse.microprofile.config.inject.ConfigProperty; import org.eclipsefoundation.adopters.model.AdoptedProject; @@ -46,6 +45,8 @@ import org.eclipsefoundation.adopters.service.AdopterService; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.fasterxml.jackson.databind.ObjectMapper; + import io.quarkus.runtime.Startup; /** @@ -68,7 +69,7 @@ public class DefaultAdopterService implements AdopterService { long retryTimeout; @Inject - Jsonb json; + ObjectMapper objectMapper; /** * All updates to the adopters list should be done through the setAdopters @@ -89,7 +90,8 @@ public class DefaultAdopterService implements AdopterService { // read in the initial file if it exists if (Files.exists(adoptersLocation)) { LOGGER.debug("Found an adopters file at path {}, reading in", adoptersLocation); - List<Adopter> initialAdopters = readInAdopters(adoptersLocation, json); + List<Adopter> initialAdopters = readInAdopters(adoptersLocation, objectMapper); + if (initialAdopters != null) { setAdopters(initialAdopters); } @@ -159,7 +161,7 @@ public class DefaultAdopterService implements AdopterService { } else { // indicates an update or created file on the given // retrieve the new adopters and set them if the read operation was successful - List<Adopter> newAdopters = readInAdopters(adoptersLocation, json); + List<Adopter> newAdopters = readInAdopters(adoptersLocation, objectMapper); if (newAdopters != null) { setAdopters(newAdopters); } @@ -167,12 +169,12 @@ public class DefaultAdopterService implements AdopterService { } - private static List<Adopter> readInAdopters(Path adoptersPath, Jsonb json) { + private static List<Adopter> readInAdopters(Path adoptersPath, ObjectMapper mapper) { LOGGER.debug("Detected an update for adopters file, reading in {}", adoptersPath); // get a json processor, and read in the file if (Files.exists(adoptersPath)) { try (InputStream is = new BufferedInputStream(Files.newInputStream(adoptersPath))) { - AdopterList al = json.fromJson(is, AdopterList.class); + AdopterList al = mapper.readValue(is, AdopterList.class); return al.getAdopters(); } catch (IOException e) { LOGGER.warn("Error reading file at path: {}\n", adoptersPath, e); @@ -204,14 +206,14 @@ public class DefaultAdopterService implements AdopterService { } private AdoptedProject getAdoptedProject(Project p) { - AdoptedProject ap = new AdoptedProject(); - ap.setProjectId(p.getProjectId()); - ap.setName(p.getName()); - ap.setLogo(p.getLogo()); - ap.setUrl(p.getUrl()); - ap.setAdopters(getAdopters().stream().filter(a -> a.getProjects().contains(p.getProjectId())) - .sorted(Comparator.comparing(a -> a.getName().toLowerCase())).collect(Collectors.toList())); - return ap; + return AdoptedProject.builder() + .setProjectId(p.getProjectId()) + .setName(p.getName()) + .setLogo(p.getLogo()) + .setUrl(p.getUrl()) + .setAdopters(getAdopters().stream().filter(a -> a.getProjects().contains(p.getProjectId())) + .sorted(Comparator.comparing(a -> a.getName().toLowerCase())).collect(Collectors.toList())) + .build(); } } diff --git a/src/main/java/org/eclipsefoundation/adopters/service/impl/PaginationProjectsService.java b/src/main/java/org/eclipsefoundation/adopters/service/impl/PaginationProjectsService.java index 9bd41a0f21ccd80fe9c30b1f4fcab0a699ab207e..5702946f16e3040ea32c845076147f56a30a1be6 100644 --- a/src/main/java/org/eclipsefoundation/adopters/service/impl/PaginationProjectsService.java +++ b/src/main/java/org/eclipsefoundation/adopters/service/impl/PaginationProjectsService.java @@ -11,7 +11,6 @@ **********************************************************************/ package org.eclipsefoundation.adopters.service.impl; -import java.util.LinkedList; import java.util.List; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -25,6 +24,7 @@ import org.eclipse.microprofile.rest.client.inject.RestClient; import org.eclipsefoundation.adopters.api.ProjectsAPI; import org.eclipsefoundation.adopters.model.Project; import org.eclipsefoundation.adopters.service.ProjectService; +import org.eclipsefoundation.core.service.APIMiddleware; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -53,6 +53,9 @@ public class PaginationProjectsService implements ProjectService { @Inject @RestClient ProjectsAPI projects; + @Inject + APIMiddleware middleware; + // this class has a separate cache as this data is long to load and should be // always available. LoadingCache<String, List<Project>> internalCache; @@ -116,23 +119,12 @@ public class PaginationProjectsService implements ProjectService { } /** - * Logic for retrieving projects from API. Will loop until there are no more - * projects to be found + * Logic for retrieving projects from API. * - * @return list of projects for the + * @return list of projects */ private List<Project> getProjectsInternal() { - int page = 0; - int pageSize = 100; - List<Project> out = new LinkedList<>(); - List<Project> in; - do { - page++; - in = projects.getProject(page, pageSize); - out.addAll(in); - } while (in != null && !in.isEmpty()); - return out; - + return middleware.getAll(p -> projects.getProjects(p), Project.class); } } diff --git a/src/main/k8s/production.yml b/src/main/k8s/production.yml index b6a0406ee089f4a30cd18083035277e77fee2846..96e16f52bafab950232800c064af0d88733800c4 100644 --- a/src/main/k8s/production.yml +++ b/src/main/k8s/production.yml @@ -33,7 +33,7 @@ spec: image: eclipsefdn/eclipsefdn-project-adopters:latest imagePullPolicy: Always ports: - - containerPort: 8080 + - containerPort: 8090 resources: limits: cpu: '1' @@ -63,7 +63,7 @@ spec: - name: "http" port: 80 protocol: "TCP" - targetPort: 8080 + targetPort: 8090 selector: app: eclipsefdn-project-adopters environment: production diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index 00a78f1d7f1a14da329effa78a43ab2b7169d513..1e85c0ab6e458ac6334eafcf5fe4ff79e743c17b 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1,14 +1,15 @@ org.eclipsefoundation.adopters.api.ProjectsAPI/mp-rest/url=https://projects.eclipse.org ## OAUTH CONFIG -quarkus.http.port=8080 quarkus.http.root-path=/adopters ## CORS settings quarkus.http.cors=false +quarkus.log.level=INFO +quarkus.oidc.enabled=false + ## Adopters raw location eclipse.adopters.path.json=/config/adopters.json %dev.eclipse.adopters.path.json=/tmp/config/adopters.json %dev.eclipse.pagination.scheme.enforce=false -%dev.quarkus.http.port=8090 \ No newline at end of file