diff --git a/.gitignore b/.gitignore index 2daa72a3c2353e6102756aa7f9831b2169a34e4f..b23d367bcc7b5148f9f907c8857702dd81b96746 100644 --- a/.gitignore +++ b/.gitignore @@ -81,3 +81,4 @@ docker.secret.properties #Generated resources /.apt_generated/ /.apt_generated_tests/ +src/test/resources/schemas diff --git a/package.json b/package.json index a8168eb75345dda8847d69535312dd32471ed96f..2d4fa8fc55674487174a7516749760a461ea3dfd 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,7 @@ }, "private": true, "scripts": { - "start": "npm run generate-json-schema && npx @redocly/openapi-cli preview-docs spec/openapi.yaml -p 8093", + "start": "npm run generate-json-schema && npx @redocly/openapi-cli preview-docs spec/openapi.yaml -p 8097", "test": "npm run generate-json-schema && npx @redocly/openapi-cli lint spec/openapi.yaml", "generate-json-schema": "npm run clean && node src/main/js/openapi2schema.js -s spec/openapi.yaml -t src/test/resources", "clean": "rm -rf src/test/resources/schemas/" diff --git a/pom.xml b/pom.xml index 0eea4bbc85896af26a9ac3cd0bdeaa6dcba88e06..19697432a0f3195f4d63b2a8affea42889e151a5 100644 --- a/pom.xml +++ b/pom.xml @@ -69,14 +69,6 @@ <groupId>io.quarkus</groupId> <artifactId>quarkus-resteasy-jackson</artifactId> </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-oidc</artifactId> - </dependency> - <dependency> - <groupId>io.quarkus</groupId> - <artifactId>quarkus-oidc-client-filter</artifactId> - </dependency> <dependency> <groupId>io.quarkus</groupId> <artifactId>quarkus-rest-client</artifactId> diff --git a/spec/openapi.yaml b/spec/openapi.yaml new file mode 100644 index 0000000000000000000000000000000000000000..d2908ececb0f4de4fe0918a0fa1200918e3035ae --- /dev/null +++ b/spec/openapi.yaml @@ -0,0 +1,282 @@ +openapi: '3.1.0' +info: + version: 1.0.0 + title: Eclipse Foundation Downloads API + license: + name: Eclipse Public License - 2.0 + url: https://www.eclipse.org/legal/epl-2.0/ +servers: +- url: https://api.eclipse.org/download + description: Production endpoint for the download information +tags: +- name: Files + description: Definitions in relation to retrieval of mailing lists +- name: Releases + description: Definitions in relation to retrieval of mailing lists +paths: + /file/{file_id}: + get: + tags: + - Files + summary: File by ID + description: Returns a file indexes metadata by its file ID + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/File' + 500: + description: Error while retrieving data + /release/{releaseType}: + parameters: + - name: releaseType + in: path + description: The type of release to retrieve + required: true + schema: + type: string + enum: + - epp + - eclipse_packages + - name: release_name + in: query + description: The name of the release to retrieve + required: true + schema: + type: string + - name: release_version + in: query + description: The version of the release to retrieve + schema: + type: string + get: + tags: + - Releases + summary: Releases by release type + description: Returns a list of releases, or a single release, applicable to the query parameters + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Release' + 500: + description: Error while retrieving data + /release/{releaseName}: + parameters: + - name: releaseName + in: path + description: The name of the release to retrieve + required: true + schema: + type: string + get: + tags: + - Releases + summary: Release Versions + description: Returns a list of versions available for the given release + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Releases' + 500: + description: Error while retrieving data + /release/{releaseName}/{releaseVersion}: + parameters: + - name: releaseName + in: path + description: The name of the release to retrieve + required: true + schema: + type: string + - name: releaseVersion + in: path + description: The version of the release to retrieve + required: true + schema: + type: string + get: + tags: + - Releases + summary: Release Version + description: Returns a given version for the named release + responses: + 200: + description: Success + content: + application/json: + schema: + $ref: '#/components/schemas/Release' + 500: + description: Error while retrieving data + +components: + schemas: + NullableString: + description: A nullable String type value + oneOf: + - type: 'null' + - type: string + DateTime: + type: string + format: datetime + description: | + Date string in the RFC 3339 format. Example, `1990-12-31T15:59:60-08:00`. + + More on this standard can be read at https://tools.ietf.org/html/rfc3339. + Files: + type: array + items: + $ref: '#/components/schemas/File' + + File: + type: object + properties: + file_id: + type: integer + description: placeholder + file_name: + type: string + description: placeholder + download_count: + type: integer + description: placeholder + size_disk_bytes: + type: integer + description: placeholder + timestamp_disk: + type: integer + description: placeholder + md5sum: + $ref: '#/components/schemas/NullableString' + description: placeholder + sha1sum: + $ref: '#/components/schemas/NullableString' + description: placeholder + + Releases: + type: array + items: + $ref: '#/components/schemas/Release' + + Release: + type: object + properties: + release_name: + type: string + description: The name of the release for the packages + release_version: + type: string + description: The version of the release for the packages + packages: + type: object + properties: + java-package: + $ref: '#/components/schemas/ReleasePackage' + jee-package: + $ref: '#/components/schemas/ReleasePackage' + cpp-package: + $ref: '#/components/schemas/ReleasePackage' + committers-package: + $ref: '#/components/schemas/ReleasePackage' + php-package: + $ref: '#/components/schemas/ReleasePackage' + dsl-package: + $ref: '#/components/schemas/ReleasePackage' + embedcpp-package: + $ref: '#/components/schemas/ReleasePackage' + modeling-package: + $ref: '#/components/schemas/ReleasePackage' + rcp-package: + $ref: '#/components/schemas/ReleasePackage' + parallel-package: + $ref: '#/components/schemas/ReleasePackage' + scout-package: + $ref: '#/components/schemas/ReleasePackage' + + ReleasePackage: + type: object + properties: + name: + type: string + description: The name of the release package + package_bugzilla_id: + type: string + description: Placeholder + download_count: + type: string + description: Number of times this package has been downloaded + website_url: + type: string + description: The public URL for the package that includes more information about the release. + incubating: + type: boolean + description: placeholder + class: + type: string + description: placeholder + body: + type: string + description: Description of the release package + features: + type: array + items: + type: string + files: + type: object + properties: + mac: + $ref: '#/components/schemas/ReleaseFiles' + windows: + $ref: '#/components/schemas/ReleaseFiles' + linux: + $ref: '#/components/schemas/ReleaseFiles' + + ReleaseFiles: + type: object + properties: + 32: + oneOf: + - $ref: '#/components/schemas/ReleaseFile' + - type: 'null' + 64: + oneOf: + - $ref: '#/components/schemas/ReleaseFile' + - type: 'null' + + ReleaseFile: + type: object + properties: + url: + type: string + description: The publicly available URL for the package + size: + type: string + description: the size of the file in bytes + file_id: + $ref: '#/components/schemas/NullableString' + description: The internal ID of the file + file_url: + type: string + description: the public facing URL for the file (no mirror) + download_count: + type: string + description: The number of times this file has been downloaded + checksum: + type: object + properties: + sha1: + $ref: '#/components/schemas/NullableString' + description: the sha1 checksum for the release file + md5: + $ref: '#/components/schemas/NullableString' + description: the md5 checksum for the release file + sha512: + $ref: '#/components/schemas/NullableString' + description: the sha512 checksum for the release file diff --git a/src/main/java/org/eclipsefoundation/downloads/api/DrupalAPI.java b/src/main/java/org/eclipsefoundation/downloads/api/DrupalAPI.java index ef7eaec7dd736719879c758ad82626f679da8fdb..dcf98c828ab1462ca2a750752938357209f7e658 100644 --- a/src/main/java/org/eclipsefoundation/downloads/api/DrupalAPI.java +++ b/src/main/java/org/eclipsefoundation/downloads/api/DrupalAPI.java @@ -6,6 +6,7 @@ import javax.ws.rs.PathParam; import org.eclipse.microprofile.rest.client.inject.RegisterRestClient; import org.eclipsefoundation.downloads.models.ReleaseTrackerPackages; +import org.eclipsefoundation.downloads.models.TrackedReleases; import org.jboss.resteasy.annotations.GZIP; @GZIP @@ -13,6 +14,10 @@ import org.jboss.resteasy.annotations.GZIP; public interface DrupalAPI { @GET - @Path("downloads/packages/admin/release_tracker/json/{releasePackageName}/all") - ReleaseTrackerPackages get(@PathParam("releasePackageName") String releasePackageName); + @Path("downloads/packages/admin/release_tracker/json/") + TrackedReleases getTrackedReleases(); + + @GET + @Path("downloads/packages/admin/release_tracker/json/{releaseName}%20{version}/all") + ReleaseTrackerPackages get(@PathParam("releaseName") String releaseName, @PathParam("version") String version); } diff --git a/src/main/java/org/eclipsefoundation/downloads/dto/DownloadFileIndex.java b/src/main/java/org/eclipsefoundation/downloads/dto/DownloadFileIndex.java index 42e451aebc6d0c32038a12d07bc606bd847c5f2e..5b32d810ef3a5f4bee00b72d2c6d57c0cf1db4b1 100644 --- a/src/main/java/org/eclipsefoundation/downloads/dto/DownloadFileIndex.java +++ b/src/main/java/org/eclipsefoundation/downloads/dto/DownloadFileIndex.java @@ -8,6 +8,7 @@ import javax.persistence.Table; import javax.validation.constraints.NotNull; import javax.ws.rs.core.MultivaluedMap; +import org.apache.commons.lang3.StringUtils; import org.eclipsefoundation.core.namespace.DefaultUrlParameterNames; import org.eclipsefoundation.downloads.namespaces.DownloadsUrlParameterNames; import org.eclipsefoundation.persistence.dto.BareNode; @@ -148,9 +149,9 @@ public class DownloadFileIndex extends BareNode { if (isRoot) { // ID check String id = params.getFirst(DefaultUrlParameterNames.ID.getName()); - if (id != null) { + if (StringUtils.isNumeric(id)) { stmt.addClause(new ParameterizedSQLStatement.Clause(TABLE.getAlias() + ".fileId = ?", - new Object[] { id })); + new Object[] { Integer.valueOf(id) })); } // file name check String fileName = params.getFirst(DownloadsUrlParameterNames.FILE_NAME.getName()); diff --git a/src/main/java/org/eclipsefoundation/downloads/models/ReleaseTrackerPackages.java b/src/main/java/org/eclipsefoundation/downloads/models/ReleaseTrackerPackages.java index 7503dcd7bb3dce4f3eadc36f4d3302bece732f19..8627bbf70a2ba9f805be34d6d6616820f8f0ccf7 100644 --- a/src/main/java/org/eclipsefoundation/downloads/models/ReleaseTrackerPackages.java +++ b/src/main/java/org/eclipsefoundation/downloads/models/ReleaseTrackerPackages.java @@ -114,7 +114,7 @@ public abstract class ReleaseTrackerPackages { public abstract List<String> getFeatures(); - public abstract Object getFiles(); + public abstract OSReleases getFiles(); public static Builder builder() { return new AutoValue_ReleaseTrackerPackages_ReleaseTrackerPackage.Builder(); @@ -140,7 +140,7 @@ public abstract class ReleaseTrackerPackages { public abstract Builder setFeatures(List<String> features); - public abstract Builder setFiles(Object files); + public abstract Builder setFiles(OSReleases files); public abstract ReleaseTrackerPackage build(); } @@ -206,6 +206,7 @@ public abstract class ReleaseTrackerPackages { public abstract String getSize(); + @Nullable public abstract String getFileId(); public abstract String getFileUrl(); @@ -225,7 +226,7 @@ public abstract class ReleaseTrackerPackages { public abstract Builder setSize(String size); - public abstract Builder setFileId(String fileId); + public abstract Builder setFileId(@Nullable String fileId); public abstract Builder setFileUrl(String fileUrl); @@ -240,10 +241,13 @@ public abstract class ReleaseTrackerPackages { @AutoValue @JsonDeserialize(builder = AutoValue_ReleaseTrackerPackages_Checksums.Builder.class) public abstract static class Checksums { + @Nullable public abstract String getMd5(); + @Nullable public abstract String getSha1(); + @Nullable public abstract String getSha512(); public static Builder builder() { @@ -253,11 +257,11 @@ public abstract class ReleaseTrackerPackages { @AutoValue.Builder @JsonPOJOBuilder(withPrefix = "set") public abstract static class Builder { - public abstract Builder setMd5(String md5); + public abstract Builder setMd5(@Nullable String md5); - public abstract Builder setSha1(String sha1); + public abstract Builder setSha1(@Nullable String sha1); - public abstract Builder setSha512(String sha512); + public abstract Builder setSha512(@Nullable String sha512); public abstract Checksums build(); } diff --git a/src/main/java/org/eclipsefoundation/downloads/models/ReleaseVersionPackages.java b/src/main/java/org/eclipsefoundation/downloads/models/ReleaseVersionPackages.java new file mode 100644 index 0000000000000000000000000000000000000000..bbcc147f8422d3094147abd87b85136c66b7f4b1 --- /dev/null +++ b/src/main/java/org/eclipsefoundation/downloads/models/ReleaseVersionPackages.java @@ -0,0 +1,31 @@ +package org.eclipsefoundation.downloads.models; + +import com.fasterxml.jackson.databind.annotation.JsonDeserialize; +import com.fasterxml.jackson.databind.annotation.JsonPOJOBuilder; +import com.google.auto.value.AutoValue; + +@AutoValue +@JsonDeserialize(builder = AutoValue_ReleaseVersionPackages.Builder.class) +public abstract class ReleaseVersionPackages { + public abstract String getReleaseName(); + + public abstract String getReleaseVersion(); + + public abstract ReleaseTrackerPackages getPackages(); + + public static Builder builder() { + return new AutoValue_ReleaseVersionPackages.Builder(); + } + + @AutoValue.Builder + @JsonPOJOBuilder(withPrefix = "set") + public abstract static class Builder { + public abstract Builder setReleaseName(String releaseName); + + public abstract Builder setReleaseVersion(String releaseVersion); + + public abstract Builder setPackages(ReleaseTrackerPackages packages); + + public abstract ReleaseVersionPackages build(); + } +} diff --git a/src/main/java/org/eclipsefoundation/downloads/models/TrackedReleases.java b/src/main/java/org/eclipsefoundation/downloads/models/TrackedReleases.java index 12d763a5c3ce1bac138c48428c305035d4654479..b84fc339a1643b1f1624ad8440a2c75f0c7837d4 100644 --- a/src/main/java/org/eclipsefoundation/downloads/models/TrackedReleases.java +++ b/src/main/java/org/eclipsefoundation/downloads/models/TrackedReleases.java @@ -31,7 +31,7 @@ public abstract class TrackedReleases { public abstract static class Release { public abstract String getName(); - public abstract List<ReleasePackage> getReleasePackages(); + public abstract List<ReleaseVersion> getVersions(); public static Builder builder() { return new AutoValue_TrackedReleases_Release.Builder(); @@ -42,15 +42,15 @@ public abstract class TrackedReleases { public abstract static class Builder { public abstract Builder setName(String name); - public abstract Builder setReleasePackages(List<ReleasePackage> releases); + public abstract Builder setVersions(List<ReleaseVersion> version); public abstract Release build(); } } @AutoValue - @JsonDeserialize(builder = AutoValue_TrackedReleases_ReleasePackage.Builder.class) - public abstract static class ReleasePackage { + @JsonDeserialize(builder = AutoValue_TrackedReleases_ReleaseVersion.Builder.class) + public abstract static class ReleaseVersion { public abstract String getName(); public abstract String getType(); @@ -61,7 +61,7 @@ public abstract class TrackedReleases { public abstract Boolean getIsCurrent(); public static Builder builder() { - return new AutoValue_TrackedReleases_ReleasePackage.Builder(); + return new AutoValue_TrackedReleases_ReleaseVersion.Builder(); } @AutoValue.Builder @@ -75,7 +75,7 @@ public abstract class TrackedReleases { public abstract Builder setIsCurrent(@Nullable Boolean isCurrent); - public abstract ReleasePackage build(); + public abstract ReleaseVersion build(); } } } diff --git a/src/main/java/org/eclipsefoundation/downloads/namespaces/DownloadsUrlParameterNames.java b/src/main/java/org/eclipsefoundation/downloads/namespaces/DownloadsUrlParameterNames.java index a3dee86d8c6a6f788f4512226e081ff238d1d634..781c72e29d14dd079851a16da1f85f31ea5eb7bc 100644 --- a/src/main/java/org/eclipsefoundation/downloads/namespaces/DownloadsUrlParameterNames.java +++ b/src/main/java/org/eclipsefoundation/downloads/namespaces/DownloadsUrlParameterNames.java @@ -11,14 +11,19 @@ import org.eclipsefoundation.core.namespace.UrlParameterNamespace; @Singleton public final class DownloadsUrlParameterNames implements UrlParameterNamespace { + public static final String RELEASE_NAME_VALUE = "release_name"; + public static final String RELEASE_VERSION_VALUE = "release_version"; + public static final UrlParameter MIRROR_ID = new UrlParameter("mirror_id"); public static final UrlParameter CCODE = new UrlParameter("ccode"); public static final UrlParameter REMOTE_ADDR = new UrlParameter("remote_addr"); public static final UrlParameter REMOTE_HOST = new UrlParameter("remote_host"); public static final UrlParameter FILE_NAME = new UrlParameter("file_name"); + public static final UrlParameter RELEASE_NAME = new UrlParameter(RELEASE_NAME_VALUE); + public static final UrlParameter RELEASE_VERSION = new UrlParameter(RELEASE_VERSION_VALUE); - private static final List<UrlParameter> params = Collections - .unmodifiableList(Arrays.asList(MIRROR_ID, CCODE, REMOTE_ADDR, REMOTE_HOST, FILE_NAME)); + private static final List<UrlParameter> params = Collections.unmodifiableList( + Arrays.asList(MIRROR_ID, CCODE, REMOTE_ADDR, REMOTE_HOST, FILE_NAME, RELEASE_NAME, RELEASE_VERSION)); @Override public List<UrlParameter> getParameters() { diff --git a/src/main/java/org/eclipsefoundation/downloads/resources/DownloadsResource.java b/src/main/java/org/eclipsefoundation/downloads/resources/DownloadsResource.java index 1df0915ddd7fb543071a1fe93be6170e2860a84c..a342b5ccbb2762a321ca58ce7824edbca41df992 100644 --- a/src/main/java/org/eclipsefoundation/downloads/resources/DownloadsResource.java +++ b/src/main/java/org/eclipsefoundation/downloads/resources/DownloadsResource.java @@ -1,26 +1,32 @@ package org.eclipsefoundation.downloads.resources; +import java.util.List; import java.util.Optional; +import java.util.stream.Collectors; import javax.inject.Inject; import javax.ws.rs.GET; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; +import javax.ws.rs.QueryParam; +import javax.ws.rs.WebApplicationException; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.MultivaluedMap; import javax.ws.rs.core.Response; +import org.apache.commons.lang3.StringUtils; import org.eclipse.microprofile.rest.client.inject.RestClient; +import org.eclipsefoundation.core.model.Error; import org.eclipsefoundation.core.model.RequestWrapper; import org.eclipsefoundation.core.namespace.DefaultUrlParameterNames; import org.eclipsefoundation.core.service.CachingService; import org.eclipsefoundation.downloads.api.DrupalAPI; import org.eclipsefoundation.downloads.dto.DownloadFileIndex; import org.eclipsefoundation.downloads.models.ReleaseTrackerPackages; +import org.eclipsefoundation.downloads.models.ReleaseVersionPackages; import org.eclipsefoundation.downloads.models.TrackedReleases.Release; -import org.eclipsefoundation.downloads.models.TrackedReleases.ReleasePackage; -import org.eclipsefoundation.downloads.services.RawHttpClientService; +import org.eclipsefoundation.downloads.namespaces.DownloadsUrlParameterNames; import org.eclipsefoundation.downloads.services.ReleaseTrackerService; import org.eclipsefoundation.persistence.dao.impl.DefaultHibernateDao; import org.eclipsefoundation.persistence.model.RDBMSQuery; @@ -46,7 +52,7 @@ public class DownloadsResource { @Inject @RestClient DrupalAPI api; - + @Inject ReleaseTrackerService releaseTrackerService; @@ -59,31 +65,80 @@ public class DownloadsResource { @GET @Path("file/{id}") public Response byID(@PathParam("id") String id) { - + // filter by ID for the file index MultivaluedMap<String, String> params = new MultivaluedMapImpl<>(); params.add(DefaultUrlParameterNames.ID.getName(), id); - return Response.ok(dao.get(new RDBMSQuery<>(wrap, filters.get(DownloadFileIndex.class), params))).build(); + // get the index, and make sure we have a result + List<DownloadFileIndex> dfis = dao.get(new RDBMSQuery<>(wrap, filters.get(DownloadFileIndex.class), params)); + if (dfis.isEmpty()) { + String message = String.format("No DownloadFileIndex found with id '%s'", id); + LOGGER.debug(message); + return new Error(404, message).asResponse(); + } + return Response.ok(dfis.get(0)).build(); + } + + @GET + @Path("release/{type:(epp|eclipse_packages)}") + public Response packageRelease(@QueryParam(DownloadsUrlParameterNames.RELEASE_NAME_VALUE) String releaseName, + @QueryParam(DownloadsUrlParameterNames.RELEASE_VERSION_VALUE) String releaseVersion) { + if (StringUtils.isNotBlank(releaseName) && StringUtils.isNotBlank(releaseVersion)) { + return releaseVersion(releaseName, releaseVersion); + } else if (StringUtils.isNotBlank(releaseName)) { + return release(releaseName); + } + return new Error(400, "At least the release_name query parameter is required to use this endpoint") + .asResponse(); + } + + @GET + @Path("release/{releaseName}") + public Response release(@PathParam("releaseName") String releaseName) { + Optional<Release> r = releaseTrackerService.getReleaseByName(releaseName); + if (r.isEmpty()) { + String message = String.format("No release named '%s' found in store, cannot continue", releaseName); + LOGGER.debug(message); + return new Error(404, message).asResponse(); + } + // get all of the release version packages to return + try { + return Response.ok(r.get().getVersions().stream() + .map(v -> ReleaseVersionPackages.builder().setPackages(api.get(releaseName, v.getName())) + .setReleaseName(releaseName).setReleaseVersion(v.getName()).build()) + .collect(Collectors.toList())).build(); + } catch (WebApplicationException e) { + String message = String.format("Error while retrieving versioned packages for release '%s': %s", + releaseName, e.getLocalizedMessage()); + LOGGER.error(message); + return new Error(503, message).asResponse(); + } } @GET - @Path("release/{releaseName:[^%]+}%20{packageName}") - public Response release(@PathParam("releaseName") String releaseName, - @PathParam("packageName") String packageName) { + @Path("release/{releaseName}/{version}") + public Response releaseVersion(@PathParam("releaseName") String releaseName, @PathParam("version") String version) { Optional<Release> r = releaseTrackerService.getReleaseByName(releaseName); if (r.isEmpty()) { - return Response.status(404).build(); + String message = String.format("No release named '%s' found in store, cannot continue", releaseName); + LOGGER.debug(message); + return new Error(404, message).asResponse(); } - Optional<ReleasePackage> releasePackage = r.get().getReleasePackages().stream() - .filter(rp -> rp.getName().equals(packageName)).findFirst(); - if (releasePackage.isEmpty()) { - return Response.status(404).build(); + // check if there is a release version that matches the given version + if (r.get().getVersions().stream().noneMatch(rp -> rp.getName().equals(version))) { + String message = String.format("No version named '%s' found for release named '%s'", version, releaseName); + LOGGER.debug(message); + return new Error(404, message).asResponse(); } // get the release package from the Drupal API - ReleaseTrackerPackages rtp = api.get(releaseName + " " + packageName); + ReleaseTrackerPackages rtp = api.get(releaseName, version); if (rtp == null) { - return Response.status(503).build(); + String message = String.format("Could not find package definitions for release '%s', version '%s'", + releaseName, version); + LOGGER.error(message); + return new Error(503, message).asResponse(); } - return Response.ok(rtp).build(); + return Response.ok(ReleaseVersionPackages.builder().setPackages(rtp).setReleaseName(releaseName) + .setReleaseVersion(version).build()).build(); } } \ No newline at end of file diff --git a/src/main/java/org/eclipsefoundation/downloads/services/RawHttpClientService.java b/src/main/java/org/eclipsefoundation/downloads/services/RawHttpClientService.java deleted file mode 100644 index 7cc20c271a3b97acd199a7e4cd6a48b81d75c12d..0000000000000000000000000000000000000000 --- a/src/main/java/org/eclipsefoundation/downloads/services/RawHttpClientService.java +++ /dev/null @@ -1,14 +0,0 @@ -package org.eclipsefoundation.downloads.services; - -import java.util.List; - -import javax.ws.rs.core.Response; - -public interface RawHttpClientService { - - Response getResponse(String url); - - <T> T getTypedResponse(String url, Class<T> type); - - <T> List<T> getTypedResponseList(String url, Class<T> type); -} diff --git a/src/main/java/org/eclipsefoundation/downloads/services/impl/DefaultHttpClientService.java b/src/main/java/org/eclipsefoundation/downloads/services/impl/DefaultHttpClientService.java deleted file mode 100644 index 22c05dacf2a1b78e20acefadb57676cf6cb8c3b5..0000000000000000000000000000000000000000 --- a/src/main/java/org/eclipsefoundation/downloads/services/impl/DefaultHttpClientService.java +++ /dev/null @@ -1,86 +0,0 @@ -package org.eclipsefoundation.downloads.services.impl; - -import java.io.InputStream; -import java.util.Arrays; -import java.util.List; - -import javax.enterprise.context.ApplicationScoped; -import javax.inject.Inject; -import javax.ws.rs.client.Client; -import javax.ws.rs.core.Response; - -import org.eclipsefoundation.downloads.services.RawHttpClientService; -import org.jboss.resteasy.client.jaxrs.ResteasyClientBuilder; -import org.jboss.resteasy.plugins.interceptors.AcceptEncodingGZIPFilter; -import org.jboss.resteasy.plugins.interceptors.GZIPDecodingInterceptor; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.fasterxml.jackson.databind.ObjectMapper; - -/** - * Default implementation for getting HTTP requests by dynamic URL. - * - * @author Martin Lowe - * - */ -@ApplicationScoped -public class DefaultHttpClientService implements RawHttpClientService { - private static final Logger LOGGER = LoggerFactory.getLogger(DefaultHttpClientService.class); - - @Inject - ObjectMapper objectMapper; - - private Client client; - - @Override - public Response getResponse(String url) { - LOGGER.error("Getting raw data for: {}", url); - return getClient().target(url).request().accept("application/json").get(); - } - - @Override - public <T> T getTypedResponse(String url, Class<T> type) { - List<T> out = readInValue(getResponse(url), type); - return out == null || out.isEmpty() ? null : out.get(0); - } - - @Override - public <T> List<T> getTypedResponseList(String url, Class<T> type) { - return readInValue(getResponse(url), type); - } - - /** - * Retrieve an HTTP client with optional SSL support enabled. - * - * TODO: This should use an executor for threaded access, as otherwise this may have issues with threaded access. - * - * @return an HTTP client for retrieving raw responses. - */ - @SuppressWarnings("java:S3252") - private Client getClient() { - if (client == null) { - client = ResteasyClientBuilder.newBuilder().register(AcceptEncodingGZIPFilter.class) - .register(GZIPDecodingInterceptor.class).build(); - } - return client; - } - - @SuppressWarnings({ "unchecked" }) - private <T> List<T> readInValue(Response r, Class<T> type) { - Object o = r.getEntity(); - try { - if (o instanceof InputStream) { - return objectMapper.readerForListOf(String.class).readValue((InputStream) o); - } else if (type.isAssignableFrom(o.getClass())) { - return Arrays.asList((T) o); - } else if (o instanceof List) { - return (List<T>) o; - } - throw new RuntimeException("Unexpected response body type, could not convert body to list"); - } catch (Exception e) { - throw new RuntimeException(e); - } - } - -} diff --git a/src/main/java/org/eclipsefoundation/downloads/services/impl/DefaultReleaseTrackerService.java b/src/main/java/org/eclipsefoundation/downloads/services/impl/DefaultReleaseTrackerService.java index 69d490bd6d36d5a3cd29764ac3946ef320d06d4f..987c306ae942022e325d91990358b989ef7d693e 100644 --- a/src/main/java/org/eclipsefoundation/downloads/services/impl/DefaultReleaseTrackerService.java +++ b/src/main/java/org/eclipsefoundation/downloads/services/impl/DefaultReleaseTrackerService.java @@ -10,7 +10,7 @@ import javax.enterprise.context.ApplicationScoped; import org.eclipsefoundation.downloads.models.TrackedReleases; import org.eclipsefoundation.downloads.models.TrackedReleases.Release; -import org.eclipsefoundation.downloads.models.TrackedReleases.ReleasePackage; +import org.eclipsefoundation.downloads.models.TrackedReleases.ReleaseVersion; import org.eclipsefoundation.downloads.services.ReleaseTrackerService; @ApplicationScoped @@ -21,7 +21,7 @@ public class DefaultReleaseTrackerService implements ReleaseTrackerService { @PostConstruct void init() { List<Release> tmp = new ArrayList<>(); - tmp.add(Release.builder().setName("2021-12").setReleasePackages(generateFakePackages()).build()); + tmp.add(Release.builder().setName("2021-12").setVersions(generateFakePackages()).build()); this.releases = TrackedReleases.builder().setReleases(tmp).build(); } @@ -33,7 +33,7 @@ public class DefaultReleaseTrackerService implements ReleaseTrackerService { @Override public List<Release> getActiveReleases() { return releases().getReleases().stream() - .filter(r -> r.getReleasePackages().stream().anyMatch(rp -> rp.getIsCurrent())) + .filter(r -> r.getVersions().stream().anyMatch(ReleaseVersion::getIsCurrent)) .collect(Collectors.toList()); } @@ -42,12 +42,12 @@ public class DefaultReleaseTrackerService implements ReleaseTrackerService { return releases; } - private List<ReleasePackage> generateFakePackages() { - List<ReleasePackage> out = new ArrayList<>(); - out.add(ReleasePackage.builder().setName("r").setType("release") + private List<ReleaseVersion> generateFakePackages() { + List<ReleaseVersion> out = new ArrayList<>(); + out.add(ReleaseVersion.builder().setName("r").setType("release") .setUrl("https://www.eclipse.org/downloads/packages/admin/release_tracker/json/2021-12%20r/all") .setIsCurrent(true).build()); - out.add(ReleasePackage.builder().setName("m1").setType("milestone") + out.add(ReleaseVersion.builder().setName("m1").setType("milestone") .setUrl("https://www.eclipse.org/downloads/packages/admin/release_tracker/json/2021-12%20m1/all") .build()); return out; diff --git a/src/main/js/openapi2schema.js b/src/main/js/openapi2schema.js new file mode 100644 index 0000000000000000000000000000000000000000..2680ae038916273e5f7f6ab942ac47bff04fc3e5 --- /dev/null +++ b/src/main/js/openapi2schema.js @@ -0,0 +1,53 @@ +/** + * Copyright (c) 2021 Eclipse + * + * 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@eclipsefoundation.org> + * + * SPDX-License-Identifier: EPL-2.0 + */ +const toJsonSchema = require('@openapi-contrib/openapi-schema-to-json-schema'); +const Resolver = require('@stoplight/json-ref-resolver'); +const yaml = require('js-yaml'); +const fs = require('fs'); +const decamelize = require('decamelize'); +const args = require('yargs') + .option('s', { + alias: 'src', + desc: 'The fully qualified path to the YAML spec.', + }) + .option('t', { + alias: 'target', + desc: 'The fully qualified path to write the JSON schema to', + }).argv; +if (!args.s || !args.t) { + process.exit(1); +} + +run(); + +/** + * Generates JSON schema files for consumption of the Java tests. + */ +async function run() { + try { + // load in the openapi yaml spec as an object + const doc = yaml.load(fs.readFileSync(args.s, 'utf8')); + // resolve $refs in openapi spec + let resolvedInp = await new Resolver.Resolver().resolve(doc); + const out = toJsonSchema(resolvedInp.result); + // if folder doesn't exist, create it + if (!fs.existsSync(`${args.t}/schemas`)) { + fs.mkdirSync(`${args.t}/schemas`); + } + // for each of the schemas, generate a JSON schema file + for (let schemaName in out.components.schemas) { + fs.writeFileSync(`${args.t}/schemas/${decamelize(schemaName, { separator: '-' })}-schema.json`, JSON.stringify(out.components.schemas[schemaName])); + } + } catch (e) { + console.log(e); + } +} diff --git a/src/test/java/org/eclipsefoundation/downloads/resources/DownloadsResourceTest.java b/src/test/java/org/eclipsefoundation/downloads/resources/DownloadsResourceTest.java new file mode 100644 index 0000000000000000000000000000000000000000..616af102da8ea6c675c25e029def9cd043973d27 --- /dev/null +++ b/src/test/java/org/eclipsefoundation/downloads/resources/DownloadsResourceTest.java @@ -0,0 +1,122 @@ +package org.eclipsefoundation.downloads.resources; + +import static io.restassured.RestAssured.given; +import static io.restassured.module.jsv.JsonSchemaValidator.matchesJsonSchemaInClasspath; + +import javax.inject.Inject; + +import org.eclipsefoundation.downloads.namespaces.DownloadsUrlParameterNames; +import org.eclipsefoundation.downloads.test.helper.SchemaNamespaceHelper; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.TestInstance; +import org.junit.jupiter.api.TestInstance.Lifecycle; + +import com.fasterxml.jackson.databind.ObjectMapper; + +import io.quarkus.test.junit.QuarkusTest; + +@TestInstance(Lifecycle.PER_CLASS) +@QuarkusTest +public class DownloadsResourceTest { + public static final String DOWNLOADS_BASE_URL = "/downloads"; + public static final String FILE_BY_ID_URL = DOWNLOADS_BASE_URL + "/file/{id}"; + + public static final String RELEASE_BASE_URL = DOWNLOADS_BASE_URL + "/release"; + public static final String LEGACY_RELEASE_URL = RELEASE_BASE_URL + "/{releaseType}"; + public static final String RELEASES_URL = RELEASE_BASE_URL + "/{releaseName}"; + public static final String RELEASE_VERSION_URL = RELEASES_URL + "/{releaseVersion}"; + + @Inject + ObjectMapper json; + + @Test + void getFileByID_missingFile() { + given().when().get(FILE_BY_ID_URL, "99999").then().statusCode(404); + } + + @Test + void getFileByID_success() { + given().when().get(FILE_BY_ID_URL, "1").then().statusCode(200); + } + + @Test + void getFileByID_success_format() { + given().when().get(FILE_BY_ID_URL, "1").then().assertThat() + .body(matchesJsonSchemaInClasspath(SchemaNamespaceHelper.FILE_SCHEMA_PATH)); + } + + @Test + void getReleaseLegacy_missingReleaseName() { + given().when().get(LEGACY_RELEASE_URL, "epp").then().statusCode(400); + given().when().get(LEGACY_RELEASE_URL, "eclipse_packages").then().statusCode(400); + given().when().queryParam(DownloadsUrlParameterNames.RELEASE_VERSION_VALUE, "r").get(LEGACY_RELEASE_URL, "epp") + .then().statusCode(400); + given().when().queryParam(DownloadsUrlParameterNames.RELEASE_VERSION_VALUE, "r") + .get(LEGACY_RELEASE_URL, "eclipse_packages").then().statusCode(400); + } + + @Test + void getReleaseLegacy_success() { + given().when().queryParam(DownloadsUrlParameterNames.RELEASE_NAME_VALUE, "2021-12") + .get(LEGACY_RELEASE_URL, "epp").then().statusCode(200); + given().when().queryParam(DownloadsUrlParameterNames.RELEASE_NAME_VALUE, "2021-12") + .get(LEGACY_RELEASE_URL, "eclipse_packages").then().statusCode(200); + given().when().queryParam(DownloadsUrlParameterNames.RELEASE_NAME_VALUE, "2021-12") + .queryParam(DownloadsUrlParameterNames.RELEASE_VERSION_VALUE, "r").get(LEGACY_RELEASE_URL, "epp").then() + .statusCode(200); + given().when().queryParam(DownloadsUrlParameterNames.RELEASE_NAME_VALUE, "2021-12") + .queryParam(DownloadsUrlParameterNames.RELEASE_VERSION_VALUE, "r") + .get(LEGACY_RELEASE_URL, "eclipse_packages").then().statusCode(200); + } + + @Test + void getReleaseLegacy_success_format() { + given().when().queryParam(DownloadsUrlParameterNames.RELEASE_NAME_VALUE, "2021-12") + .get(LEGACY_RELEASE_URL, "epp").then().assertThat() + .body(matchesJsonSchemaInClasspath(SchemaNamespaceHelper.RELEASES_SCHEMA_PATH)); + } + + @Test + void getReleaseLegacyWithVersion_success_format() { + given().when().queryParam(DownloadsUrlParameterNames.RELEASE_NAME_VALUE, "2021-12") + .queryParam(DownloadsUrlParameterNames.RELEASE_VERSION_VALUE, "r").get(LEGACY_RELEASE_URL, "epp").then() + .assertThat().body(matchesJsonSchemaInClasspath(SchemaNamespaceHelper.RELEASE_SCHEMA_PATH)); + } + + @Test + void getRelease_missingRelease() { + given().when().get(RELEASES_URL, "missing-version").then().statusCode(404); + } + + @Test + void getRelease_success() { + given().when().get(RELEASES_URL, "2021-12").then().statusCode(200); + } + + @Test + void getRelease_success_format() { + given().when().get(RELEASES_URL, "2021-12").then().assertThat() + .body(matchesJsonSchemaInClasspath(SchemaNamespaceHelper.RELEASES_SCHEMA_PATH)); + } + + @Test + void getReleaseVersion_missingRelease() { + given().when().get(RELEASE_VERSION_URL, "missing-version", "r").then().statusCode(404); + } + + @Test + void getReleaseVersion_missingVersion() { + given().when().get(RELEASE_VERSION_URL, "2021-12", "some-release").then().statusCode(404); + } + + @Test + void getReleaseVersion_success() { + given().when().get(RELEASE_VERSION_URL, "2021-12", "r").then().statusCode(200); + } + + @Test + void getReleaseVersion_success_format() { + given().when().get(RELEASE_VERSION_URL, "2021-12", "r").then().assertThat() + .body(matchesJsonSchemaInClasspath(SchemaNamespaceHelper.RELEASE_SCHEMA_PATH)); + } +} diff --git a/src/test/java/org/eclipsefoundation/downloads/test/helper/SchemaNamespaceHelper.java b/src/test/java/org/eclipsefoundation/downloads/test/helper/SchemaNamespaceHelper.java new file mode 100644 index 0000000000000000000000000000000000000000..9c60845b6f642f6d5cc7c2f78e3c156f847034ec --- /dev/null +++ b/src/test/java/org/eclipsefoundation/downloads/test/helper/SchemaNamespaceHelper.java @@ -0,0 +1,10 @@ +package org.eclipsefoundation.downloads.test.helper; + +public final class SchemaNamespaceHelper { + public static final String BASE_SCHEMAS_PATH = "schemas/"; + public static final String BASE_SCHEMAS_PATH_SUFFIX = "-schema.json"; + public static final String FILES_SCHEMA_PATH = BASE_SCHEMAS_PATH + "files" + BASE_SCHEMAS_PATH_SUFFIX; + public static final String FILE_SCHEMA_PATH = BASE_SCHEMAS_PATH + "file" + BASE_SCHEMAS_PATH_SUFFIX; + public static final String RELEASES_SCHEMA_PATH = BASE_SCHEMAS_PATH + "releases" + BASE_SCHEMAS_PATH_SUFFIX; + public static final String RELEASE_SCHEMA_PATH = BASE_SCHEMAS_PATH + "release" + BASE_SCHEMAS_PATH_SUFFIX; +} diff --git a/src/test/resources/application.properties b/src/test/resources/application.properties new file mode 100644 index 0000000000000000000000000000000000000000..4e10abb9f979e15bab9892f4818dbb7fb561cdab --- /dev/null +++ b/src/test/resources/application.properties @@ -0,0 +1,9 @@ +## DATASOURCE CONFIG +quarkus.datasource.db-kind=h2 +eclipse.db.default.limit=25 +eclipse.db.default.limit.max=100 +quarkus.hibernate-orm.database.generation=none + +# Flyway configuration for the default datasource +quarkus.flyway.locations=classpath:database/default +quarkus.flyway.migrate-at-start=true diff --git a/src/test/resources/database/default/V1.0.0__default.sql b/src/test/resources/database/default/V1.0.0__default.sql new file mode 100644 index 0000000000000000000000000000000000000000..a1266ecc842464ac44881e5a9d1a2a9809d66f70 --- /dev/null +++ b/src/test/resources/database/default/V1.0.0__default.sql @@ -0,0 +1,22 @@ +CREATE TABLE downloads ( + file_id int(10) unsigned NOT NULL DEFAULT 0, + download_date datetime DEFAULT NULL, + remote_host varchar(100) DEFAULT NULL, + remote_addr varchar(15) DEFAULT NULL, + mirror_id int(10) unsigned DEFAULT NULL, + ccode char(2) DEFAULT NULL +); +INSERT INTO downloads(file_id, download_date, remote_host, remote_addr,mirror_id, ccode) + VALUES (1, NOW(),'http://somehost.co/file/location', '127.0.0.1', 101, 'ca'); + +CREATE TABLE download_file_index ( + file_id int(10) unsigned NOT NULL AUTO_INCREMENT, + file_name varchar(255) NOT NULL DEFAULT '', + download_count int(10) unsigned NOT NULL DEFAULT 0, + size_disk_bytes bigint(20) NOT NULL DEFAULT 0, + timestamp_disk bigint(20) NOT NULL DEFAULT 0, + md5sum char(32) DEFAULT NULL, + sha1sum char(40) DEFAULT NULL +); +INSERT INTO download_file_index(file_id, file_name, download_count, size_disk_bytes, timestamp_disk, md5sum, sha1sum) + VALUES (1, 'sample_file.zip',123456, 987654, 1642175700000, 'md5sum_sample', 'sha1sum_sample'); \ No newline at end of file