Skip to content
Snippets Groups Projects
Commit 302fcef6 authored by Martin Lowe's avatar Martin Lowe :flag_ca:
Browse files

Merge branch 'zacharysabourin/main/190' into 'main'

feat: Upgrade Quarkus to 2.14.2 + integrate api-common library

Closes #190

See merge request !199
parents 23b034cd cfe12284
No related branches found
No related tags found
1 merge request!199feat: Upgrade Quarkus to 2.14.2 + integrate api-common library
Pipeline #15551 passed
Showing
with 390 additions and 726 deletions
......@@ -5,7 +5,7 @@ services:
context: .
dockerfile: ./src/main/docker/Dockerfile.jvm
ports:
- "8090:8080"
- "10126:8090"
volumes:
- ./config:/config
deploy:
......
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
......
<?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
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.
......@@ -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
......@@ -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);
}
/*********************************************************************
* 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);
}
}
/*********************************************************************
* 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();
}
}
/*********************************************************************
* 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();
}
}
/*********************************************************************
* 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();
}
}
/*********************************************************************
* 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();
}
}
/*********************************************************************
* 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();
}
}
/*********************************************************************
* 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();
}
}
/*********************************************************************
* 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);
}
}
......@@ -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();
}
}
......@@ -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);
}
}
......@@ -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
......
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
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment